首页 > 代码库 > Android自定义控件系列(一)—Button七十二变

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

转载请注明出处:http://www.cnblogs.com/landptf/p/6290791.html

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

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

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

技术分享

 

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

 1 <?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,代码如下:

  1 package com.landptf.view;
  2 
  3 import android.content.Context;
  4 import android.content.res.ColorStateList;
  5 import android.content.res.TypedArray;
  6 import android.graphics.drawable.Drawable;
  7 import android.graphics.drawable.GradientDrawable;
  8 import android.util.AttributeSet;
  9 import android.view.MotionEvent;
 10 import android.view.View;
 11 import android.widget.Button;
 12 
 13 import com.landptf.R;
 14 
 15 /**
 16  * Created by landptf on 2016/10/25.
 17  * 自定义Button,支持圆角矩形,圆形按钮等样式,可通过配置文件改变按下后的样式
 18  * 若通过代码设置圆角或者圆形,需要先调用setFillet方法将fillet设置为true
 19  */
 20 public class ButtonM extends Button {
 21     private static String TAG = "ButtonM";
 22     /**
 23      * 按钮的背景色
 24      */
 25     private int backColor = 0;
 26     /**
 27      * 按钮被按下时的背景色
 28      */
 29     private int backColorPress = 0;
 30     /**
 31      * 按钮的背景图片
 32      */
 33     private Drawable backGroundDrawable = null;
 34     /**
 35      * 按钮被按下时显示的背景图片
 36      */
 37     private Drawable backGroundDrawablePress = null;
 38     /**
 39      * 按钮文字的颜色
 40      */
 41     private ColorStateList textColor = null;
 42     /**
 43      * 按钮被按下时文字的颜色
 44      */
 45     private ColorStateList textColorPress = null;
 46     private GradientDrawable gradientDrawable = null;
 47     /**
 48      * 是否设置圆角或者圆形等样式
 49      */
 50     private boolean fillet = false;
 51     /**
 52      * 标示onTouch方法的返回值,用来解决onClick和onTouch冲突问题
 53      */
 54     private boolean isCost = true;
 55 
 56     public ButtonM(Context context) {
 57         super(context, null);
 58     }
 59 
 60     public ButtonM(Context context, AttributeSet attrs) {
 61         this(context, attrs, 0);
 62     }
 63 
 64     public ButtonM(Context context, AttributeSet attrs, int defStyle) {
 65         super(context, attrs, defStyle);
 66         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.buttonM, defStyle, 0);
 67         if (a != null) {
 68             //设置背景色
 69             ColorStateList colorList = a.getColorStateList(R.styleable.buttonM_backColor);
 70             if (colorList != null) {
 71                 backColor = colorList.getColorForState(getDrawableState(), 0);
 72                 if (backColor != 0) {
 73                     setBackgroundColor(backColor);
 74                 }
 75             }
 76             //记录按钮被按下时的背景色
 77             ColorStateList colorListPress = a.getColorStateList(R.styleable.buttonM_backColorPress);
 78             if (colorListPress != null){
 79                 backColorPress = colorListPress.getColorForState(getDrawableState(), 0);
 80             }
 81             //设置背景图片,若backColor与backGroundDrawable同时存在,则backGroundDrawable将覆盖backColor
 82             backGroundDrawable = a.getDrawable(R.styleable.buttonM_backGroundImage);
 83             if (backGroundDrawable != null){
 84                 setBackgroundDrawable(backGroundDrawable);
 85             }
 86             //记录按钮被按下时的背景图片
 87             backGroundDrawablePress = a.getDrawable(R.styleable.buttonM_backGroundImagePress);
 88             //设置文字的颜色
 89             textColor = a.getColorStateList(R.styleable.buttonM_textColor);
 90             if (textColor != null){
 91                 setTextColor(textColor);
 92             }
 93             //记录按钮被按下时文字的颜色
 94             textColorPress = a.getColorStateList(R.styleable.buttonM_textColorPress);
 95             //设置圆角或圆形等样式的背景色
 96             fillet = a.getBoolean(R.styleable.buttonM_fillet, false);
 97             if (fillet){
 98                 getGradientDrawable();
 99                 if (backColor != 0) {
100                     gradientDrawable.setColor(backColor);
101                     setBackgroundDrawable(gradientDrawable);
102                 }
103             }
104             //设置圆角矩形的角度,fillet为true时才生效
105             float radius = a.getFloat(R.styleable.buttonM_radius, 0);
106             if (fillet && radius != 0){
107                 setRadius(radius);
108             }
109             //设置按钮形状,fillet为true时才生效
110             int shape = a.getInteger(R.styleable.buttonM_shape, 0);
111             if (fillet && shape != 0) {
112                 setShape(shape);
113             }
114             a.recycle();
115         }
116         setOnTouchListener(new OnTouchListener() {
117             @Override
118             public boolean onTouch(View arg0, MotionEvent event) {
119                 //根据touch事件设置按下抬起的样式
120                 return setTouchStyle(event.getAction());
121             }
122         });
123     }
124 
125     /**
126      * 根据按下或者抬起来改变背景和文字样式
127      * @param state
128      * @return isCost
129      *  为解决onTouch和onClick冲突的问题
130      *  根据事件分发机制,如果onTouch返回true,则不响应onClick事件
131      *  因此采用isCost标识位,当用户设置了onClickListener则onTouch返回false
132      */
133     private boolean setTouchStyle(int state){
134         if (state == MotionEvent.ACTION_DOWN) {
135             if (backColorPress != 0) {
136                 if (fillet){
137                     gradientDrawable.setColor(backColorPress);
138                     setBackgroundDrawable(gradientDrawable);
139                 }else {
140                     setBackgroundColor(backColorPress);
141                 }
142             }
143             if (backGroundDrawablePress != null) {
144                 setBackgroundDrawable(backGroundDrawablePress);
145             }
146             if (textColorPress != null) {
147                 setTextColor(textColorPress);
148             }
149         }
150         if (state == MotionEvent.ACTION_UP) {
151             if (backColor != 0) {
152                 if (fillet){
153                     gradientDrawable.setColor(backColor);
154                     setBackgroundDrawable(gradientDrawable);
155                 }else {
156                     setBackgroundColor(backColor);
157                 }
158             }
159             if (backGroundDrawable != null) {
160                 setBackgroundDrawable(backGroundDrawable);
161             }
162             if (textColor != null) {
163                 setTextColor(textColor);
164             }
165         }
166         return isCost;
167     }
168 
169     /**
170      * 重写setOnClickListener方法,解决onTouch和onClick冲突问题
171      * @param l
172      */
173     @Override
174     public void setOnClickListener(OnClickListener l) {
175         super.setOnClickListener(l);
176         isCost = false;
177     }
178 
179     /**
180      * 设置按钮的背景色
181      * @param backColor
182      */
183     public void setBackColor(int backColor) {
184         this.backColor = backColor;
185         if (fillet){
186             gradientDrawable.setColor(backColor);
187             setBackgroundDrawable(gradientDrawable);
188         }else {
189             setBackgroundColor(backColor);
190         }
191     }
192 
193     /**
194      * 设置按钮被按下时的背景色
195      * @param backColorPress
196      */
197     public void setBackColorPress(int backColorPress) {
198         this.backColorPress = backColorPress;
199     }
200 
201     /**
202      * 设置按钮的背景图片
203      * @param backGroundDrawable
204      */
205     public void setBackGroundDrawable(Drawable backGroundDrawable) {
206         this.backGroundDrawable = backGroundDrawable;
207         setBackgroundDrawable(backGroundDrawable);
208     }
209 
210     /**
211      * 设置按钮被按下时的背景图片
212      * @param backGroundDrawablePress
213      */
214     public void setBackGroundDrawablePress(Drawable backGroundDrawablePress) {
215         this.backGroundDrawablePress = backGroundDrawablePress;
216     }
217 
218     /**
219      * 设置文字的颜色
220      * @param textColor
221      */
222     public void setTextColor(int textColor) {
223         if (textColor == 0) return;
224         this.textColor = ColorStateList.valueOf(textColor);
225         //此处应加super关键字,调用父类的setTextColor方法,否则会造成递归导致内存溢出
226         super.setTextColor(this.textColor);
227     }
228 
229     /**
230      * 设置按钮被按下时文字的颜色
231      * @param textColorPress
232      */
233     public void setTextColorPress(int textColorPress) {
234         if (textColorPress == 0) return;
235         this.textColorPress = ColorStateList.valueOf(textColorPress);
236     }
237 
238     /**
239      * 设置按钮是否设置圆角或者圆形等样式
240      * @param fillet
241      */
242     public void setFillet(boolean fillet){
243         this.fillet = fillet;
244         getGradientDrawable();
245     }
246 
247     /**
248      * 设置圆角按钮的角度
249      * @param radius
250      */
251     public void setRadius(float radius){
252         if (!fillet) return;
253         getGradientDrawable();
254         gradientDrawable.setCornerRadius(radius);
255         setBackgroundDrawable(gradientDrawable);
256     }
257 
258     /**
259      * 设置按钮的形状
260      * @param shape
261      */
262     public void setShape(int shape){
263         if (!fillet) return;
264         getGradientDrawable();
265         gradientDrawable.setShape(shape);
266         setBackgroundDrawable(gradientDrawable);
267     }
268 
269     private void getGradientDrawable() {
270         if (gradientDrawable == null){
271             gradientDrawable = new GradientDrawable();
272         }
273     }
274 
275 }

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

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

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

1 xml

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

2 java

 1 ButtonM btmRadiusColorJava = (ButtonM) findViewById(R.id.btm_radius_color_java);
 2 if (btmRadiusColorJava != null) {
 3     btmRadiusColorJava.setFillet(true);
 4     btmRadiusColorJava.setRadius(30);
 5     btmRadiusColorJava.setTextColor(Color.parseColor("#ffffff"));
 6     btmRadiusColorJava.setBackColor(Color.parseColor("#ff3300"));
 7     btmRadiusColorJava.setBackColorPress(Color.parseColor("#ff33ff"));
 8     btmRadiusColorJava.setOnClickListener(new View.OnClickListener() {
 9         @Override
10         public void onClick(View v) {
11             Toast.makeText(ButtonMTestActivity.this, "java代码实现", Toast.LENGTH_SHORT).show();
12         }
13     });
14 }

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

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