文档章节

Android自定义控件系列(一)—Button七十二变

landptf
 landptf
发布于 2017/01/16 20:06
字数 1672
阅读 322
收藏 8

转载请注明出处:https://my.oschina.net/landptf/blog/825960

忙了一段时间,终于有时间整理整理之前所用到的一些知识,分享给大家,希望给同学们有些帮助,同时也是对自己的知识有个巩固的过程。

在Android的开发中比较常用的控件就是Button了,但是我们平时使用Button时是怎样来设置按下和抬起显示不同的效果呢?我想一般的实现方式就是定义一个selector的xml文件,然后在里面根据不同的state来设置不同的图片,但是当Button控件非常多的时候,就要写对应数量的xml文件,导致大码非常臃肿。

今天我们换种方式来改变这个样式,只需要两行代码即可实现按下的效果,同时支持圆角和圆形的按钮的样式。先看下效果图,这是我写的一个demo

接下来讲一下主要代码: 
第一步 自定义属性 
在res/values/目录下新建attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
 2 <resources>
 3     <!--公共属性-->
 4     <attr name="backColor" format="color" />
 5     <attr name="backColorPress" format="color" />
 6     <attr name="backGroundImage" format="reference" />
 7     <attr name="backGroundImagePress" format="reference" />
 8     <attr name="textColor" format="color" />
 9     <attr name="textColorPress" format="color" />
10 
11     <declare-styleable name="buttonM">
12         <attr name="backColor" />
13         <attr name="backColorPress" />
14         <attr name="backGroundImage"  />
15         <attr name="backGroundImagePress" />
16         <attr name="textColor" />
17         <attr name="textColorPress" />
18         <attr name="fillet" format="boolean" />
19         <attr name="radius" format="float" />
20         <attr name="shape">
21             <enum name="rectangle" value="0" />
22             <enum name="oval" value="1" />
23             <enum name="line" value="2" />
24             <enum name="ring" value="3" />
25         </attr>
26     </declare-styleable>
27 
28 </resources>

具体属性的含义在java代码中都有描述 
第二步 创建ButtonM类使其继承Button,代码如下:

package com.landptf.view;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

import com.landptf.R;

/**
 * Created by landptf on 2016/10/25.
 * 自定义Button,支持圆角矩形,圆形按钮等样式,可通过配置文件改变按下后的样式
 * 若通过代码设置圆角或者圆形,需要先调用setFillet方法将fillet设置为true
 */
public class ButtonM extends Button {
    private static String TAG = "ButtonM";
    /**
     * 按钮的背景色
     */
    private int backColor = 0;
    /**
     * 按钮被按下时的背景色
     */
    private int backColorPress = 0;
    /**
     * 按钮的背景图片
     */
    private Drawable backGroundDrawable = null;
    /**
     * 按钮被按下时显示的背景图片
     */
    private Drawable backGroundDrawablePress = null;
    /**
     * 按钮文字的颜色
     */
    private ColorStateList textColor = null;
    /**
     * 按钮被按下时文字的颜色
     */
    private ColorStateList textColorPress = null;
    private GradientDrawable gradientDrawable = null;
    /**
     * 是否设置圆角或者圆形等样式
     */
    private boolean fillet = false;
    /**
     * 标示onTouch方法的返回值,用来解决onClick和onTouch冲突问题
     */
    private boolean isCost = true;

    public ButtonM(Context context) {
        super(context, null);
    }

    public ButtonM(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ButtonM(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.buttonM, defStyle, 0);
        if (a != null) {
            //设置背景色
            ColorStateList colorList = a.getColorStateList(R.styleable.buttonM_backColor);
            if (colorList != null) {
                backColor = colorList.getColorForState(getDrawableState(), 0);
                if (backColor != 0) {
                    setBackgroundColor(backColor);
                }
            }
            //记录按钮被按下时的背景色
            ColorStateList colorListPress = a.getColorStateList(R.styleable.buttonM_backColorPress);
            if (colorListPress != null){
                backColorPress = colorListPress.getColorForState(getDrawableState(), 0);
            }
            //设置背景图片,若backColor与backGroundDrawable同时存在,则backGroundDrawable将覆盖backColor
            backGroundDrawable = a.getDrawable(R.styleable.buttonM_backGroundImage);
            if (backGroundDrawable != null){
                setBackgroundDrawable(backGroundDrawable);
            }
            //记录按钮被按下时的背景图片
            backGroundDrawablePress = a.getDrawable(R.styleable.buttonM_backGroundImagePress);
            //设置文字的颜色
            textColor = a.getColorStateList(R.styleable.buttonM_textColor);
            if (textColor != null){
                setTextColor(textColor);
            }
            //记录按钮被按下时文字的颜色
            textColorPress = a.getColorStateList(R.styleable.buttonM_textColorPress);
            //设置圆角或圆形等样式的背景色
            fillet = a.getBoolean(R.styleable.buttonM_fillet, false);
            if (fillet){
                getGradientDrawable();
                if (backColor != 0) {
                    gradientDrawable.setColor(backColor);
                    setBackgroundDrawable(gradientDrawable);
                }
            }
            //设置圆角矩形的角度,fillet为true时才生效
            float radius = a.getFloat(R.styleable.buttonM_radius, 0);
            if (fillet && radius != 0){
                setRadius(radius);
            }
            //设置按钮形状,fillet为true时才生效
            int shape = a.getInteger(R.styleable.buttonM_shape, 0);
            if (fillet && shape != 0) {
                setShape(shape);
            }
            a.recycle();
        }
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View arg0, MotionEvent event) {
                //根据touch事件设置按下抬起的样式
                return setTouchStyle(event.getAction());
            }
        });
    }

    /**
     * 根据按下或者抬起来改变背景和文字样式
     * @param state
     * @return isCost
     *  为解决onTouch和onClick冲突的问题
     *  根据事件分发机制,如果onTouch返回true,则不响应onClick事件
     *  因此采用isCost标识位,当用户设置了onClickListener则onTouch返回false
     */
    private boolean setTouchStyle(int state){
        if (state == MotionEvent.ACTION_DOWN) {
            if (backColorPress != 0) {
                if (fillet){
                    gradientDrawable.setColor(backColorPress);
                    setBackgroundDrawable(gradientDrawable);
                }else {
                    setBackgroundColor(backColorPress);
                }
            }
            if (backGroundDrawablePress != null) {
                setBackgroundDrawable(backGroundDrawablePress);
            }
            if (textColorPress != null) {
                setTextColor(textColorPress);
            }
        }
        if (state == MotionEvent.ACTION_UP) {
            if (backColor != 0) {
                if (fillet){
                    gradientDrawable.setColor(backColor);
                    setBackgroundDrawable(gradientDrawable);
                }else {
                    setBackgroundColor(backColor);
                }
            }
            if (backGroundDrawable != null) {
                setBackgroundDrawable(backGroundDrawable);
            }
            if (textColor != null) {
                setTextColor(textColor);
            }
        }
        return isCost;
    }

    /**
     * 重写setOnClickListener方法,解决onTouch和onClick冲突问题
     * @param l
     */
    @Override
    public void setOnClickListener(OnClickListener l) {
        super.setOnClickListener(l);
        isCost = false;
    }

    /**
     * 设置按钮的背景色
     * @param backColor
     */
    public void setBackColor(int backColor) {
        this.backColor = backColor;
        if (fillet){
            gradientDrawable.setColor(backColor);
            setBackgroundDrawable(gradientDrawable);
        }else {
            setBackgroundColor(backColor);
        }
    }

    /**
     * 设置按钮被按下时的背景色
     * @param backColorPress
     */
    public void setBackColorPress(int backColorPress) {
        this.backColorPress = backColorPress;
    }

    /**
     * 设置按钮的背景图片
     * @param backGroundDrawable
     */
    public void setBackGroundDrawable(Drawable backGroundDrawable) {
        this.backGroundDrawable = backGroundDrawable;
        setBackgroundDrawable(backGroundDrawable);
    }

    /**
     * 设置按钮被按下时的背景图片
     * @param backGroundDrawablePress
     */
    public void setBackGroundDrawablePress(Drawable backGroundDrawablePress) {
        this.backGroundDrawablePress = backGroundDrawablePress;
    }

    /**
     * 设置文字的颜色
     * @param textColor
     */
    public void setTextColor(int textColor) {
        if (textColor == 0) return;
        this.textColor = ColorStateList.valueOf(textColor);
        //此处应加super关键字,调用父类的setTextColor方法,否则会造成递归导致内存溢出
        super.setTextColor(this.textColor);
    }

    /**
     * 设置按钮被按下时文字的颜色
     * @param textColorPress
     */
    public void setTextColorPress(int textColorPress) {
        if (textColorPress == 0) return;
        this.textColorPress = ColorStateList.valueOf(textColorPress);
    }

    /**
     * 设置按钮是否设置圆角或者圆形等样式
     * @param fillet
     */
    public void setFillet(boolean fillet){
        this.fillet = fillet;
        getGradientDrawable();
    }

    /**
     * 设置圆角按钮的角度
     * @param radius
     */
    public void setRadius(float radius){
        if (!fillet) return;
        getGradientDrawable();
        gradientDrawable.setCornerRadius(radius);
        setBackgroundDrawable(gradientDrawable);
    }

    /**
     * 设置按钮的形状
     * @param shape
     */
    public void setShape(int shape){
        if (!fillet) return;
        getGradientDrawable();
        gradientDrawable.setShape(shape);
        setBackgroundDrawable(gradientDrawable);
    }

    private void getGradientDrawable() {
        if (gradientDrawable == null){
            gradientDrawable = new GradientDrawable();
        }
    }

}

注释基本上写的比较详细,下面主要说一下这里面涉及的一些知识点 
1 关于onTouch返回值问题,如果返回true表示要消费该点击事件,后续的所有事件都交给他处理,同时onTouchEvent将不会执行,因此onClick也得不到执行,在这里通过重写setOnClickListener设置变量来改变返回值。具体关于View的事件分发机制可以查阅有关文档,网上很多这方面的教程。

2 如果想要通过java代码来设置圆角或者圆形时,必须先设置setFillet(true),然后再设置背景色,形状或者角度等参数。通过xml文件则无限制

最后讲一下怎么使用,这里以设置圆角矩形为例,分别通过xml和java代码实现,其他的可参考源码。

1 xml

<com.landptf.view.ButtonM
    android:id="@+id/btm_radius_color_xml"
    android:layout_width="0dp"
    android:layout_height="50dp"
    android:layout_weight="1"
    android:gravity="center"
    android:text="点击改变背景色"
    landptf:backColor="#ff3300"
    landptf:backColorPress="#ff33ff"
    landptf:fillet="true"
    landptf:radius="30"
    landptf:textColor="@android:color/white" />

2 java

ButtonM btmRadiusColorJava = (ButtonM) findViewById(R.id.btm_radius_color_java);
if (btmRadiusColorJava != null) {
    btmRadiusColorJava.setFillet(true);
    btmRadiusColorJava.setRadius(30);
    btmRadiusColorJava.setTextColor(Color.parseColor("#ffffff"));
    btmRadiusColorJava.setBackColor(Color.parseColor("#ff3300"));
    btmRadiusColorJava.setBackColorPress(Color.parseColor("#ff33ff"));
    btmRadiusColorJava.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(ButtonMTestActivity.this, "java代码实现", Toast.LENGTH_SHORT).show();
        }
    });
}

代码已托管到开源中国的码云上,欢迎下载,地址:https://git.oschina.net/landptf/landptf.git

© 著作权归作者所有

landptf
粉丝 42
博文 17
码字总数 23398
作品 0
深圳
程序员
私信 提问
landptf/landptf

自定义控件 #一 Android Studio 引用方式 在build.gradle文件中加入 compile 'com.landptf:landptf:1.0.2' #二 控件效果及实现 1 Android自定义控件系列(一)—Button七十二变 2 Android自定义......

landptf
2017/03/06
0
0
android 打包自己的自定义组件成JAR包

在项目开发过程中,我们难免会用到自己去制作自定义的VIEW控件,之后我们别的项目如果需要的话就直接将其复制到对应的项目中使用,虽说这么做是一个解决问题的方法,但毕竟不是很好。 原因是...

刘遇安
2013/04/19
285
3
仿MIUI音量变化环形进度条实现

Android中使用环形进度条的业务场景其实蛮多的,比如下载文件的时候使用环形进度条,会给用户眼前一亮的感觉;再比如我大爱的MIUI系统,它的音量进度条就是使用环形进度条,尽显小米"为发烧而...

Jack_1900
2014/07/25
753
0
超酷的计步器APP(一)——炫酷功能实现,自定义水波纹特效、自定义炫酷开始按钮、属性动画的综合体验

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m366917/article/details/52976877 超酷的计步器APP(一)——炫酷功能实现,自定义水波纹特效、自定义炫酷开...

Aduroidpc
2016/10/31
0
0
开源项目学习与分析系列——ArcMenu

从现在开始,我将写下由于项目而接触到的优秀的Android开源项目的学习理解。一来有助于自己的提高,方便以后的查阅;二来学习Android需要有开源的精神,和别人分享是很重要的。我现在对Andro...

墨迹天下
2014/04/03
3.4K
1

没有更多内容

加载失败,请刷新页面

加载更多

Spring Cloud Sleuth 整合 feign 源码分析

org.springframework.cloud.sleuth.instrument.web.client.feign.TraceFeignClient 包括创建span一些参数

xiaomin0322
17分钟前
2
0
Less 延伸

extend 是一个 Less 伪类,它通过使用 :extend 选择器在一个选择器中扩展其他选择器样式。 扩展语法 扩展可以是附加到选择器,也可以是集中放置在规则,看上去像是带有选择器参数的可选伪类,...

凌兮洛
18分钟前
2
0
RedHat 7.0系统中安装mysql 5.7.22

在安装之前,首先要查看的是,你的系统中有没有已经安装过的情况。键入rpm -qa|grep mysql,如果无任何显示,则表示没有安装过相关组件,如果有,则根据显示出来的名字,键入rpm -e --nodeps...

最菜最菜之小菜鸟
23分钟前
2
0
RPA:企业信息孤岛的“克星”

为了降本增效,近来世界范围内掀起一股流程优化的热潮,转型升级成为众多企业时刻挂在嘴边的热词。不过在企业数字化转型的过程中,信息孤岛的出现,往往成为了企业升级的绊脚石。 信息孤岛:...

UiBot
23分钟前
2
0
我的测试

我的测试

daiison
23分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部