首页 > 代码库 > 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七十二变