标签:
1.先前,我们编好的开关按钮的项目工程,如下:
2. 下面我们要使用自定义的属性优化这个开关按钮,如下:
(1)第1步,我们在res/values文件夹下,新建一个attrs.xml文件,如下:
其中attrs.xml,如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <resources> 3 <!-- 声明一个属性集的名称 --> 4 <declare-styleable name="MyToggleBtn"> 5 6 <!-- 声明一个属性name是my_background 类型为引用类型 引用资源ID--> 7 <attr name="my_background" format="reference"></attr> 8 9 <!-- 声明一个属性name是my_slide_btn 类型为引用类型 引用资源ID --> 10 <attr name="my_slide_btn" format="reference"></attr> 11 12 <!-- 声明一个属性name是curr_state 类型为布尔值 --> 13 <attr name="curr_state" format="boolean"></attr> 14 </declare-styleable> 15 16 </resources>
(2)第2步,在布局文件activity_main.xml文件中使用上面设置的属性,如下:
(3)接下来就是来到MyToggleButton代码中,获得上面自定义的属性my_background、my_slide_btn和curr_state,如下:
1 package com.himi.togglebtn; 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.view.MotionEvent; 11 import android.view.View; 12 import android.view.View.OnClickListener; 13 14 public class MyToggleButton extends View implements OnClickListener { 15 16 //作为背景的图片 17 private Bitmap backgroundBitmap; 18 //滑动的开关图片 19 private Bitmap slidebtn; 20 private Paint paint; 21 22 //滑动按钮的左边界 23 private float slidebtn_left; 24 25 /** 26 * 当前开关的状态 27 * true :为开 28 * false:为关 29 */ 30 private boolean currState = false; 31 /** 32 * 背景图的资源ID 33 */ 34 private int backgroundId; 35 /** 36 * 滑动图片的资源ID 37 */ 38 private int slideBtnId; 39 40 41 /** 42 * 我们在代码里面创建对象的时候,使用此构造方法 43 * @param context 44 */ 45 public MyToggleButton(Context context) { 46 super(context); 47 // TODO 自动生成的构造函数存根 48 } 49 50 /** 51 * 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。 52 * 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造) 53 * @param context 54 * @param attrs 55 */ 56 public MyToggleButton(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 59 //获得自定义的属性,这里获得在xml文件中定义的属性值,然后进行相应的设置 60 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn); 61 62 int N = ta.getIndexCount(); 63 for(int i=0; i<N; i++) { 64 /* 65 * 获得某个属性的ID值 66 */ 67 int itemId = ta.getIndex(i); 68 switch (itemId) { 69 case R.styleable.MyToggleBtn_curr_state: 70 currState = ta.getBoolean(itemId, false); 71 72 break; 73 74 case R.styleable.MyToggleBtn_my_background: 75 backgroundId = ta.getResourceId(itemId, -1); 76 if(backgroundId == -1) { 77 throw new RuntimeException("请设置背景图片"); 78 } 79 backgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundId); 80 break; 81 case R.styleable.MyToggleBtn_my_slide_btn: 82 slideBtnId = ta.getResourceId(itemId, -1); 83 if(slideBtnId == -1) { 84 throw new RuntimeException("请设置滑动按钮图片"); 85 } 86 slidebtn = BitmapFactory.decodeResource(getResources(), slideBtnId); 87 break; 88 } 89 } 90 91 92 93 initView(); 94 } 95 96 97 /** 98 * 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle 99 * 改变生成自定义的View的样式style 100 * @param context 101 * @param attrs 102 * @param defStyle 103 */ 104 public MyToggleButton(Context context, AttributeSet attrs, int defStyle) { 105 super(context, attrs, defStyle); 106 // TODO 自动生成的构造函数存根 107 } 108 109 110 //初始化 111 private void initView() { 112 //初始化图片 113 //backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); 114 //slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button); 115 116 //初始化画笔 117 paint = new Paint(); 118 paint.setAntiAlias(true);//打开抗锯齿 119 120 //添加Onclick事件监听 121 setOnClickListener(this); 122 //刷新当前状态,因为上面构造方法中设置了currState,所以这里必须设置一下flushState(),每次改变了currState,自然要刷新,才能生效 123 flushState(); 124 } 125 126 /* 127 * View对象显示在屏幕上,有几个重要步骤: 128 * 1. 构造方法 创建 对象. 129 * 2. 测量View的大小. onMeasure(int, int):系统调用的方法,获知View的大小 130 * 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用 131 * 4. 绘制View的内容 onDraw(canvas) 132 * 133 */ 134 135 136 137 /** 138 * 139 * 测量尺寸时候的回调方法 140 */ 141 @Override 142 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 143 144 //super.onMeasure(widthMeasureSpec, heightMeasureSpec); 145 /** 146 * 设置当前View的大小 147 * width :当前View的宽度 148 * height:当前view的高度(单位:像素) 149 */ 150 setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); 151 } 152 153 154 /** 155 * 自定义的View,作用不大 156 * 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法 157 */ 158 @Override 159 protected void onLayout(boolean changed, int left, int top, int right, 160 int bottom) { 161 // TODO 自动生成的方法存根 162 super.onLayout(changed, left, top, right, bottom); 163 } 164 165 /** 166 * 绘制当前View的内容 167 */ 168 @Override 169 protected void onDraw(Canvas canvas) { 170 // TODO 自动生成的方法存根 171 //super.onDraw(canvas); 172 173 //绘制背景图 174 /* 175 * backgroundBitmap:要绘制的图片 176 * left 图片的左边界 177 * top 图片的上边界 178 * paint 绘制图片要使用的画笔 179 */ 180 canvas.drawBitmap(backgroundBitmap, 0, 0, paint); 181 //绘制可滑动的按钮 182 canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint); 183 } 184 185 /** 186 * 判断是否发生拖到 187 * 如果拖动了,就不再响应Onclick事件 188 * true:发生拖动 189 * false:没有发生拖动 190 */ 191 private boolean isDrag = false; 192 193 /** 194 * onClick事件在view.onTouchEvent中被解析 195 * 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件 196 */ 197 public void onClick(View v) { 198 /* 199 * 如果没有拖动,才执行改变状态的动作 200 */ 201 if(!isDrag) { 202 currState = ! currState; 203 flushState();//刷新当前开关状态 204 } 205 } 206 207 /** 208 * 刷新当前开关视图 209 */ 210 private void flushState() { 211 if(currState) { 212 slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth(); 213 }else { 214 slidebtn_left =0; 215 } 216 217 flushView(); 218 } 219 220 public void flushView() { 221 /** 222 * 对slidebtn_left的值进行判断 223 * 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界) 224 * 225 */ 226 int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值 227 //确保slidebtn_left >= 0 228 slidebtn_left =(slidebtn_left>0)?slidebtn_left:0; 229 //确保slidebtn_left <=maxLeft 230 slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft; 231 232 //告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果 233 invalidate(); 234 } 235 236 /** 237 * down事件时的x值 238 */ 239 private int firstX; 240 /** 241 * touch事件的上一个x值 242 */ 243 private int lastX; 244 245 @Override 246 public boolean onTouchEvent(MotionEvent event) { 247 super.onTouchEvent(event); 248 switch(event.getAction()) { 249 case MotionEvent.ACTION_DOWN: 250 firstX = lastX = (int) event.getX(); 251 isDrag = false; 252 break; 253 case MotionEvent.ACTION_MOVE: 254 //判断是否发生拖动 255 if(Math.abs(event.getX()-lastX)>5) { 256 isDrag = true; 257 } 258 259 //计算手指在屏幕上移动的距离 260 int dis = (int) (event.getX()-lastX); 261 //将本次的位置设置给lastX 262 lastX = (int) event.getX(); 263 //根据手指移动的距离,改变slidebtn_left的值 264 slidebtn_left = slidebtn_left+dis; 265 break; 266 case MotionEvent.ACTION_UP: 267 268 //在发生拖动的情况下,根据最后的位置,判断当前的开关的状态 269 if(isDrag){ 270 int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值 271 /** 272 * 根据slidebtn_left判断,当前应该是什么状态 273 * 274 */ 275 if(slidebtn_left>maxLeft/2) {//应为打开状态 276 currState = true; 277 }else { 278 currState = false; 279 } 280 flushState(); 281 } 282 283 break; 284 285 } 286 flushView(); 287 return true; 288 } 289 }
运行效果如下:
3. 上面的是标准的自定义属性的使用,还有不太正规,但是很方便的使用自定义属性的方法,如下:
(1)在上面的"开关按钮"工程的activity_main.xml文件中,添加如下:
(2)然后在MyToggleButton.java使用在xml文件中定义的testAttrs,如下:
1 package com.himi.togglebtn; 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.view.MotionEvent; 11 import android.view.View; 12 import android.view.View.OnClickListener; 13 14 public class MyToggleButton extends View implements OnClickListener { 15 16 //作为背景的图片 17 private Bitmap backgroundBitmap; 18 //滑动的开关图片 19 private Bitmap slidebtn; 20 private Paint paint; 21 22 //滑动按钮的左边界 23 private float slidebtn_left; 24 25 /** 26 * 当前开关的状态 27 * true :为开 28 * false:为关 29 */ 30 private boolean currState = false; 31 /** 32 * 背景图的资源ID 33 */ 34 private int backgroundId; 35 /** 36 * 滑动图片的资源ID 37 */ 38 private int slideBtnId; 39 40 41 /** 42 * 我们在代码里面创建对象的时候,使用此构造方法 43 * @param context 44 */ 45 public MyToggleButton(Context context) { 46 super(context); 47 // TODO 自动生成的构造函数存根 48 } 49 50 /** 51 * 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。 52 * 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造) 53 * @param context 54 * @param attrs 55 */ 56 public MyToggleButton(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 59 //无命名空间测试,测试我们在activity_main.xml文件中定义的属性-----testAttrs="hello" 60 String testAttrs = attrs.getAttributeValue(null, "testAttrs"); 61 System.out.println("testAttrs==:"+testAttrs); 62 63 //获得自定义的属性 64 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn); 65 66 int N = ta.getIndexCount(); 67 for(int i=0; i<N; i++) { 68 /* 69 * 获得某个属性的ID值 70 */ 71 int itemId = ta.getIndex(i); 72 switch (itemId) { 73 case R.styleable.MyToggleBtn_curr_state: 74 currState = ta.getBoolean(itemId, false); 75 76 break; 77 78 case R.styleable.MyToggleBtn_my_background: 79 backgroundId = ta.getResourceId(itemId, -1); 80 if(backgroundId == -1) { 81 throw new RuntimeException("请设置背景图片"); 82 } 83 backgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundId); 84 break; 85 case R.styleable.MyToggleBtn_my_slide_btn: 86 slideBtnId = ta.getResourceId(itemId, -1); 87 if(slideBtnId == -1) { 88 throw new RuntimeException("请设置滑动按钮图片"); 89 } 90 slidebtn = BitmapFactory.decodeResource(getResources(), slideBtnId); 91 break; 92 } 93 } 94 95 96 97 initView(); 98 } 99 100 101 /** 102 * 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle 103 * 改变生成自定义的View的样式style 104 * @param context 105 * @param attrs 106 * @param defStyle 107 */ 108 public MyToggleButton(Context context, AttributeSet attrs, int defStyle) { 109 super(context, attrs, defStyle); 110 // TODO 自动生成的构造函数存根 111 } 112 113 114 //初始化 115 private void initView() { 116 //初始化图片 117 //backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); 118 //slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button); 119 120 //初始化画笔 121 paint = new Paint(); 122 paint.setAntiAlias(true);//打开抗锯齿 123 124 //添加Onclick事件监听 125 setOnClickListener(this); 126 //刷新当前状态 127 flushState(); 128 } 129 130 /* 131 * View对象显示在屏幕上,有几个重要步骤: 132 * 1. 构造方法 创建 对象. 133 * 2. 测量View的大小. onMeasure(int, int):系统调用的方法,获知View的大小 134 * 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用 135 * 4. 绘制View的内容 onDraw(canvas) 136 * 137 */ 138 139 140 141 /** 142 * 143 * 测量尺寸时候的回调方法 144 */ 145 @Override 146 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 147 148 //super.onMeasure(widthMeasureSpec, heightMeasureSpec); 149 /** 150 * 设置当前View的大小 151 * width :当前View的宽度 152 * height:当前view的高度(单位:像素) 153 */ 154 setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); 155 } 156 157 158 /** 159 * 自定义的View,作用不大 160 * 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法 161 */ 162 @Override 163 protected void onLayout(boolean changed, int left, int top, int right, 164 int bottom) { 165 // TODO 自动生成的方法存根 166 super.onLayout(changed, left, top, right, bottom); 167 } 168 169 /** 170 * 绘制当前View的内容 171 */ 172 @Override 173 protected void onDraw(Canvas canvas) { 174 // TODO 自动生成的方法存根 175 //super.onDraw(canvas); 176 177 //绘制背景图 178 /* 179 * backgroundBitmap:要绘制的图片 180 * left 图片的左边界 181 * top 图片的上边界 182 * paint 绘制图片要使用的画笔 183 */ 184 canvas.drawBitmap(backgroundBitmap, 0, 0, paint); 185 //绘制可滑动的按钮 186 canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint); 187 } 188 189 /** 190 * 判断是否发生拖到 191 * 如果拖动了,就不再响应Onclick事件 192 * true:发生拖动 193 * false:没有发生拖动 194 */ 195 private boolean isDrag = false; 196 197 /** 198 * onClick事件在view.onTouchEvent中被解析 199 * 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件 200 */ 201 public void onClick(View v) { 202 /* 203 * 如果没有拖动,才执行改变状态的动作 204 */ 205 if(!isDrag) { 206 currState = ! currState; 207 flushState();//刷新当前开关状态 208 } 209 } 210 211 /** 212 * 刷新当前开关视图 213 */ 214 private void flushState() { 215 if(currState) { 216 slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth(); 217 }else { 218 slidebtn_left =0; 219 } 220 221 flushView(); 222 } 223 224 public void flushView() { 225 /** 226 * 对slidebtn_left的值进行判断 227 * 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界) 228 * 229 */ 230 int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值 231 //确保slidebtn_left >= 0 232 slidebtn_left =(slidebtn_left>0)?slidebtn_left:0; 233 //确保slidebtn_left <=maxLeft 234 slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft; 235 236 //告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果 237 invalidate(); 238 } 239 240 /** 241 * down事件时的x值 242 */ 243 private int firstX; 244 /** 245 * touch事件的上一个x值 246 */ 247 private int lastX; 248 249 @Override 250 public boolean onTouchEvent(MotionEvent event) { 251 super.onTouchEvent(event); 252 switch(event.getAction()) { 253 case MotionEvent.ACTION_DOWN: 254 firstX = lastX = (int) event.getX(); 255 isDrag = false; 256 break; 257 case MotionEvent.ACTION_MOVE: 258 //判断是否发生拖动 259 if(Math.abs(event.getX()-lastX)>5) { 260 isDrag = true; 261 } 262 263 //计算手指在屏幕上移动的距离 264 int dis = (int) (event.getX()-lastX); 265 //将本次的位置设置给lastX 266 lastX = (int) event.getX(); 267 //根据手指移动的距离,改变slidebtn_left的值 268 slidebtn_left = slidebtn_left+dis; 269 break; 270 case MotionEvent.ACTION_UP: 271 272 //在发生拖动的情况下,根据最后的位置,判断当前的开关的状态 273 if(isDrag){ 274 int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值 275 /** 276 * 根据slidebtn_left判断,当前应该是什么状态 277 * 278 */ 279 if(slidebtn_left>maxLeft/2) {//应为打开状态 280 currState = true; 281 }else { 282 currState = false; 283 } 284 flushState(); 285 } 286 287 break; 288 289 } 290 flushView(); 291 return true; 292 } 293 294 295 }
测试logcat输出为:
自定义控件(视图)28期笔记07:自定义控件之使用系统控件(自定义属性之开关按钮案例的优化)
标签:
原文地址:http://www.cnblogs.com/hebao0514/p/4847204.html