自定义控件:滑动开关按钮(自定义属性)

     

【主要步骤】

1、自定义类MyToggleButton继承自view。

2、重写onMeasure方法,指定控件大小。

3、重写onDraw方法,绘制控件内容。

4、重写onTouchEvent方法,对touch事件进行解析。

【为新控件添加自定义的属性】

1、在attrs.xml文件中声明属性,有属性名:name和格式:format=如:

<declare-styleable name="MyToggleBtn">

        <attr name="curr_state" format="boolean" />

</declare-styleable>

2、在布局文件中使用新属性,使用之前必须先声明命名空间,如:

xmlns:heihei=http://schemas.android.com/apk/res/com.example.testdemo

说明:xmlns是XML name space的缩写;

         heihei 可以任意写;

         http://schemas.android.com/apk/res/   此为android固定格式;

         com.example.testdemo   此应用的包名,如manifest配置文件中一致。

3、在自定义view的构造方法当中,通过解析AttributeSet对象,获得所需要的属性值。

  

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <!-- res/values/attrs.xml -->
 3 <resources>
 4     
 5     <!-- 声名属性集的名称 -->
 6     <declare-styleable name="MyToggleBtn">
 7 
 8         <!-- 声名一个属性  name是my_background   类型为 引用类型      引用资源ID -->
 9         <attr name="my_background" format="reference" />
10 
11         <!-- 声名一个属性  name是my_slide_btn   类型为 引用类型      引用资源ID -->
12         <attr name="my_slide_btn" format="reference" />
13 
14         <!-- 声名一个属性  name是curr_state   类型为 boolean 类型-->
15         <attr name="curr_state" format="boolean" />
16         
17     </declare-styleable>
18     
20 </resources>
 1 <RelativeLayout 
 2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:heihei="http://schemas.android.com/apk/res/com.example.testdemo"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent" >
 8     <com.example.testdemo.MyToggleButton
 9         android:id="@+id/my_toggle_btn"
10         android:layout_width="wrap_content"
11         android:layout_height="wrap_content"
12         android:layout_centerHorizontal="true"
13         android:layout_centerVertical="true"
14         heihei:my_background="@drawable/switch_background"   开关图片
15         heihei:my_slide_btn="@drawable/slide_button"  灰色图片
16         heihei:curr_state="false"
17         testAttrs="@drawable/ic_launcher" />
19 </RelativeLayout>
  1 package com.example.testdemo;
  2 
  3 import android.content.Context;
  4 import android.content.res.TypedArray;
  5 import android.graphics.Bitmap;
  6 import android.graphics.BitmapFactory;
  7 import android.graphics.Canvas;
  8 import android.graphics.Paint;
  9 import android.util.AttributeSet;
 10 import android.util.Log;
 11 import android.view.MotionEvent;
 12 import android.view.View;
 13 import android.view.View.OnClickListener;
 14 
 15 public class MyToggleButton extends View implements OnClickListener {
 16     public static final String TAG = "MyToggleButton";
 17 
 18     // 做为背景的图片
 19     private Bitmap backgroundBitmap;
 21     // 可以滑动的图片
 22     private Bitmap slideBtn;
 23     private Paint paint;
 25     // 滑动按钮的左边届
 26     private float slideBtn_left;
 28     // 背景图的资源ID
 29     private int backgroundId;
 31     // 滑动图片的资源ID
 32     private int slideBtnId; 
 34     // 当前开关的状态 true 为开
 35     private boolean currState = false;
 37     // 判断是否发生拖动, 如果拖动了,就不再响应 onclick 事件
 38     private boolean isDrag = false;
 40     // down 事件时的x值
 41     private int firstX;
 43     // touch 事件的上一个x值
 44     private int lastX;
 45 
 46     // 在代码里面创建对象的时候,使用此构造方法
 47     public MyToggleButton(Context context) {
 48         super(context);
 49     }
 50 
 51     /**
 52      * 在布局文件中声名的view,创建时由系统自动调用。
 53      * 
 54      * @param context
 55      *            上下文对象
 56      * @param attrs
 57      *            属性集
 58      */
 59     public MyToggleButton(Context context, AttributeSet attrs) {
 60         super(context, attrs);
 61        //-------------------
 62         int count = attrs.getAttributeCount();
 63         for (int i = 0; i < count; i++) {
 64             String name = attrs.getAttributeName(i);
 65             String value = attrs.getAttributeValue(i);
 66             Log.i(TAG, "name:" + name+ "value:" + value);
 67         }
 68        //-------------------
 69 
 70         // 无命名空间测试
 71         String testAttrs = attrs.getAttributeValue(null, "testAttrs");
 72 
 73         System.out.println("testAttrs===:" + testAttrs);
 74 
 75         // 获得自定义的属性
 76         // (比如)第一个参数:原材料,第二个参数:图纸。
 77         // TypedArray相当于是一个加工厂,把原材料加工成图纸上面说的。
 78         TypedArray ta = context.obtainStyledAttributes(attrs,
 79                 R.styleable.MyToggleBtn);
 80 
 81         int N = ta.getIndexCount();
 82         for (int i = 0; i < N; i++) {
 83             // 获得某个属性的ID值
 84             int itemId = ta.getIndex(i);
 85             switch (itemId) {
 86             case R.styleable.MyToggleBtn_curr_state:
 87                 currState = ta.getBoolean(itemId, false);
 88 
 89                 break;
 90             case R.styleable.MyToggleBtn_my_background:
 91                 backgroundId = ta.getResourceId(itemId, -1);
 92                 if (backgroundId == -1) {
 93                     throw new RuntimeException("请设置背景图片");
 94                 }
 95                 backgroundBitmap = BitmapFactory.decodeResource(getResources(),
 96                         backgroundId);
 97 
 98                 break;
 99             case R.styleable.MyToggleBtn_my_slide_btn:
100                 slideBtnId = ta.getResourceId(itemId, -1);
101                 slideBtn = BitmapFactory.decodeResource(getResources(),
102                         slideBtnId);
103 
104                 break;
105 
106             default:
107                 break;
108             }
109 
110         }
111         initView();
112     }
113 
114     /**
115      * 初始化
116      */
117     private void initView() {
118         // 初始化图片
119         // backgroundBitmap = BitmapFactory.decodeResource(getResources(),
120         // R.drawable.switch_background);
121         // slideBtn = BitmapFactory.decodeResource(getResources(),
122         // R.drawable.slide_button);
123 
124         // 初始化 画笔
125         paint = new Paint();
126         // 打开抗矩齿
127         paint.setAntiAlias(true);
128         // 添加onclick事件监听
129         setOnClickListener(this);
130 
131         flushState();
132     }
133 
134     /*
135      * view 对象显示的屏幕上,有几个重要步骤: 
136      * 1、构造方法 创建 对象。 
137      * 2、测量view的大小。 onMeasure(int,int);
138      * 3、确定view的位置 ,view自身有一些建议权,决定权在 父view手中。 onLayout(); 
139      * 4、绘制 view 的内容 。
140      * onDraw(Canvas)
141      */
142 
143     @Override
144     // 测量尺寸时的回调方法
145     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
146         // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
147 
148         // 设置当前view的大小 width :view的宽度 height :view的高度 (单位:像素)
149         setMeasuredDimension(backgroundBitmap.getWidth(),
150                 backgroundBitmap.getHeight());
151     }
152 
153     // 确定位置的时候调用此方法
154     // 自定义view的时候,作用不大
155     // @Override
156     // protected void onLayout(boolean changed, int left, int top, int right,
157     // int bottom) {
158     // super.onLayout(changed, left, top, right, bottom);
159     // }
160 
161     @Override
162     /**
163      * 绘制当前view的内容
164      */
165     protected void onDraw(Canvas canvas) {
166         // super.onDraw(canvas);
167 
168         // 绘制 背景
169         /*
170          * backgroundBitmap 要绘制的图片 left 图片的左边届 top 
171          * 图片的上边届 paint 绘制图片要使用的画笔
172          */
173         canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
174 
175         // 绘制 可滑动的按钮
176         canvas.drawBitmap(slideBtn, slideBtn_left, 0, paint);
177     }
178 
179     @Override
180     /**
181      * onclick 事件在View.onTouchEvent 中被解析。
182      * 系统对onclick 事件的解析,过于简陋,只要有down 事件  up 事件,
183      * 系统即认为 发生了click 事件
184      * 
185      */
186     public void onClick(View v) {
187         /*
188          * 如果没有拖动,才执行改变状态的动作
189          */
190         if (!isDrag) {
191             currState = !currState;
192             flushState();
193         }
194     }
195 
196     @Override
197     public boolean onTouchEvent(MotionEvent event) {
198         super.onTouchEvent(event);
199 
200         switch (event.getAction()) {
201         case MotionEvent.ACTION_DOWN:
202             firstX = lastX = (int) event.getX();
203             isDrag = false;
204 
205             break;
206         case MotionEvent.ACTION_MOVE:
207 
208             // 判断是否发生拖动
209             if (Math.abs(event.getX() - firstX) > 5) {
210                 isDrag = true;
211             }
212 
213             // 计算 手指在屏幕上移动的距离
214             int dis = (int) (event.getX() - lastX);
215 
216             // 将本次的位置 设置给lastX
217             lastX = (int) event.getX();
218 
219             // 根据手指移动的距离,改变slideBtn_left 的值
220             slideBtn_left = slideBtn_left + dis;
221             break;
222         case MotionEvent.ACTION_UP:
223 
224             // 在发生拖动的情况下,根据最后的位置,判断当前开关的状态
225             if (isDrag) {
226                 // slideBtn左边届最大值
227                 int maxLeft = backgroundBitmap.getWidth() - slideBtn.getWidth();
228                 // 根据 slideBtn_left 判断,当前应是什么状态
229                 // 此时应为 打开的状态
230                 if (slideBtn_left > maxLeft / 2) {
231                     currState = true;
232                 } else {
233                     currState = false;
234                 }
235 
236                 flushState();
237             }
238             break;
239         }
240 
241         flushView();
242 
243         return true;
244     }
245 
246     /**
247      * 刷新当前状态
248      */
249     private void flushState() {
250         if (currState) {
251             slideBtn_left = backgroundBitmap.getWidth() - slideBtn.getWidth();
252         } else {
253             slideBtn_left = 0;
254         }
255 
256         flushView();
257     }
258 
259     /**
260      * 刷新当前视力
261      */
262     private void flushView() {
263         /*
264          * 对 slideBtn_left 的值进行判断 ,确保其在合理的位置 即 0<=slideBtn_left <= maxLeft
265          * slideBtn左边届最大值
266          */
267         int maxLeft = backgroundBitmap.getWidth() - slideBtn.getWidth();
268 
269         // 确保 slideBtn_left >= 0
270         slideBtn_left = (slideBtn_left > 0) ? slideBtn_left : 0;
271 
272         // 确保 slideBtn_left <=maxLeft
273         slideBtn_left = (slideBtn_left < maxLeft) ? slideBtn_left : maxLeft;
274 
275         // 刷新当前视图 导致 执行onDraw执行
276         invalidate();
277     }
278 
279 }

DEMO下载地址:http://pan.baidu.com/s/1jGsrvM2

原文地址:https://www.cnblogs.com/androidsj/p/3995206.html