【主要步骤】
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