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

时间: 2024-10-11 14:39:44

Android自定义控件系列(一)—Button七十二变的相关文章

Android自定义控件系列二:如何自定义属性

上一篇Android自定义控件系列一:如何测量控件尺寸 我们讲了如何确定控件的属性,这篇接着也是讲个必要的知识-如何自定义属性.对于一个完整的或者说真正有实用价值的控件,自定义属性是必不可少的. 如何为控件定义属性 在res/values/attrs.xml(attrs.xml如果不存在,可以创建个)中使用<declare-styleable>标签定义属性,比如我想定义个显示头像的圆形的图片控件(AvatarImageView): 01.<?xml version="1.0&q

Android自定义控件系列 十:利用添加自定义布局来搞定触摸事件的分发,解决组合界面中特定控件响应特定方向的事件

这个例子是比较有用的,基本上可以说,写完这一次,以后很多情况下,直接拿过来addView一下,然后再addInterceptorView一下,就可以轻轻松松的达到组合界面中特定控件来响应特定方向的触摸事件了. 请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45198549,非允许请勿用于商业或盈利用途,违者必究. 在写Android应用的过程之中,经常会遇到这样的情况:界面包含了多个控件,我们希望触摸在界面上的不

[转]Android自定义控件系列五:自定义绚丽水波纹效果

出处:http://www.2cto.com/kf/201411/353169.html 今天我们来利用Android自定义控件实现一个比较有趣的效果:滑动水波纹.先来看看最终效果图: 图一 效果还是很炫的:饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现 一.实现单击出现水波纹单圈效果: 图二 照例来说,还是一个自定义控件,这里我们直接让这个控件撑满整个屏幕(对自定义控件不熟悉的可以参看我之前的一篇文章:Android自定义控件系列二:自定义开关按钮(一)).观察这个效果,发现应该

Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)

转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45027641 自定义view/viewgroup要重写的几个方法:onMeasure(),onLayout(),onDraw().(不熟悉的话可以查看专栏的前几篇文章:Android自定义控件系列二:自定义开关按钮(一)). 今天的任务就是详细研究一下protected void onMeasure(int widthMeasureSpec, int heightMeasureSpe

android自定义控件系列教程----视图的测量和布局

前面说点什么 当我们的一个视图界面绘制在android屏幕上面的时候其实都必须经过这几步measure. layout.draw这几个阶段,我们可以在view类里面看到这几个函数,然后里面有几个函数是onmeasure.onlayout.ondraw这几个函数是我们重写控件需要注意的这几个函数,下面我们就来讲讲这几个函数的功能和作用. onMeasure 正如这个函数的名子一样就是测量,所有的图示其实系统在绘制之前都不知道它到底有多大的,所以在很多时候我们在初始化界面oncreate的时候直接去

Android自定义控件系列三:自定义开关按钮(三)--- 自定义属性

尊重原创,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/40855377 接之前的:Android自定义控件系列二:自定义开关按钮(一)和Android自定义控件系列三:自定义开关按钮(二)继续,今天要讲的就是如何在自定义控件中使用自定义属性,实际上这里有两种方法,一种是配合XML属性资源文件的方式,另一种是不需要XML资源文件的方式:下面我们分别来看看: 一.配合XML属性资源文件来使用自定义属性: 那么还是针对我们之前写的自定义

Android自定义控件系列之应用篇——圆形进度条

一.概述 在上一篇博文中,我们给大家介绍了Android自定义控件系列的基础篇.链接:http://www.cnblogs.com/jerehedu/p/4360066.html 这一篇博文中,我们将在基础篇的基础上,再通过重写ondraw()方法和自定义属性实现圆形进度条,效果如图所示: 二.实现步骤   1.  编写自定义组件MyCircleProgress扩展View public class MyCircleProgress extends View { - } 2.  在MyCircl

Android自定义控件系列五:自定义绚丽水波纹效果

尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自定义控件实现一个比较有趣的效果:滑动水波纹.先来看看最终效果图: 图一 效果还是很炫的:饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现 一.实现单击出现水波纹单圈效果: 图二 照例来说,还是一个自定义控件,这里我们直接让这个控件撑满整个屏幕(对自定义控件不熟悉的可以参看我之前的一篇文章:Android自定义控件系列二

Android自定义控件系列八:详解onMeasure()(二)--利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题

上一篇文章详细讲解了一下onMeasure/measure方法在Android自定义控件时的原理和作用,参看博文:Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一),今天就来真正实践一下,让这两个方法大显神威来帮我们搞定图片的屏幕适配问题. 请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45038329,非允许请勿用于商业或盈利用途,违者必究. 使用ImageView会遇到