自定义控件(视图)28期笔记07:自定义控件之 自定义属性(开关按钮案例的优化)
2015-09-29 20:21
537 查看
1.先前,我们编好的开关按钮的项目工程,如下:
2. 下面我们要使用自定义的属性优化这个开关按钮,如下:
(1)第1步,我们在res/values文件夹下,新建一个attrs.xml文件,如下:
其中attrs.xml,如下:
(2)第2步,在布局文件activity_main.xml文件中使用上面设置的属性,如下:
(3)接下来就是来到MyToggleButton代码中,获得上面自定义的属性my_background、my_slide_btn和curr_state,如下:
运行效果如下:
3. 上面的是标准的自定义属性的使用,还有不太正规,但是很方便的使用自定义属性的方法,如下:
(1)在上面的"开关按钮"工程的activity_main.xml文件中,添加如下:
(2)然后在MyToggleButton.java使用在xml文件中定义的testAttrs,如下:
测试logcat输出为:
2. 下面我们要使用自定义的属性优化这个开关按钮,如下:
(1)第1步,我们在res/values文件夹下,新建一个attrs.xml文件,如下:
其中attrs.xml,如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- 声明一个属性集的名称 --> <declare-styleable name="MyToggleBtn"> <!-- 声明一个属性name是my_background 类型为引用类型 引用资源ID--> <attr name="my_background" format="reference"></attr> <!-- 声明一个属性name是my_slide_btn 类型为引用类型 引用资源ID --> <attr name="my_slide_btn" format="reference"></attr> <!-- 声明一个属性name是curr_state 类型为布尔值 --> <attr name="curr_state" format="boolean"></attr> </declare-styleable> </resources>
(2)第2步,在布局文件activity_main.xml文件中使用上面设置的属性,如下:
(3)接下来就是来到MyToggleButton代码中,获得上面自定义的属性my_background、my_slide_btn和curr_state,如下:
package com.himi.togglebtn; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; public class MyToggleButton extends View implements OnClickListener { //作为背景的图片 private Bitmap backgroundBitmap; //滑动的开关图片 private Bitmap slidebtn; private Paint paint; //滑动按钮的左边界 private float slidebtn_left; /** * 当前开关的状态 * true :为开 * false:为关 */ private boolean currState = false; /** * 背景图的资源ID */ private int backgroundId; /** * 滑动图片的资源ID */ private int slideBtnId; /** * 我们在代码里面创建对象的时候,使用此构造方法 * @param context */ public MyToggleButton(Context context) { super(context); // TODO 自动生成的构造函数存根 } /** * 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。 * 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造) * @param context * @param attrs */ public MyToggleButton(Context context, AttributeSet attrs) { super(context, attrs); //获得自定义的属性,这里获得在xml文件中定义的属性值,然后进行相应的设置 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn); int N = ta.getIndexCount(); for(int i=0; i<N; i++) { /* * 获得某个属性的ID值 */ int itemId = ta.getIndex(i); switch (itemId) { case R.styleable.MyToggleBtn_curr_state: currState = ta.getBoolean(itemId, false); break; case R.styleable.MyToggleBtn_my_background: backgroundId = ta.getResourceId(itemId, -1); if(backgroundId == -1) { throw new RuntimeException("请设置背景图片"); } backgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundId); break; case R.styleable.MyToggleBtn_my_slide_btn: slideBtnId = ta.getResourceId(itemId, -1); if(slideBtnId == -1) { throw new RuntimeException("请设置滑动按钮图片"); } slidebtn = BitmapFactory.decodeResource(getResources(), slideBtnId); break; } } initView(); } /** * 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle * 改变生成自定义的View的样式style * @param context * @param attrs * @param defStyle */ public MyToggleButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO 自动生成的构造函数存根 } //初始化 private void initView() { //初始化图片 //backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); //slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button); //初始化画笔 paint = new Paint(); paint.setAntiAlias(true);//打开抗锯齿 //添加Onclick事件监听 setOnClickListener(this); //刷新当前状态,因为上面构造方法中设置了currState,所以这里必须设置一下flushState(),每次改变了currState,自然要刷新,才能生效 flushState(); } /* * View对象显示在屏幕上,有几个重要步骤: * 1. 构造方法 创建 对象. * 2. 测量View的大小. onMeasure(int, int):系统调用的方法,获知View的大小 * 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用 * 4. 绘制View的内容 onDraw(canvas) * */ /** * * 测量尺寸时候的回调方法 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); /** * 设置当前View的大小 * width :当前View的宽度 * height:当前view的高度(单位:像素) */ setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); } /** * 自定义的View,作用不大 * 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法 */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // TODO 自动生成的方法存根 super.onLayout(changed, left, top, right, bottom); } /** * 绘制当前View的内容 */ @Override protected void onDraw(Canvas canvas) { // TODO 自动生成的方法存根 //super.onDraw(canvas); //绘制背景图 /* * backgroundBitmap:要绘制的图片 * left 图片的左边界 * top 图片的上边界 * paint 绘制图片要使用的画笔 */ canvas.drawBitmap(backgroundBitmap, 0, 0, paint); //绘制可滑动的按钮 canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint); } /** * 判断是否发生拖到 * 如果拖动了,就不再响应Onclick事件 * true:发生拖动 * false:没有发生拖动 */ private boolean isDrag = false; /** * onClick事件在view.onTouchEvent中被解析 * 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件 */ public void onClick(View v) { /* * 如果没有拖动,才执行改变状态的动作 */ if(!isDrag) { currState = ! currState; flushState();//刷新当前开关状态 } } /** * 刷新当前开关视图 */ private void flushState() { if(currState) { slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth(); }else { slidebtn_left =0; } flushView(); } public void flushView() { /** * 对slidebtn_left的值进行判断 * 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界) * */ int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值 //确保slidebtn_left >= 0 slidebtn_left =(slidebtn_left>0)?slidebtn_left:0; //确保slidebtn_left <=maxLeft slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft; //告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果 invalidate(); } /** * down事件时的x值 */ private int firstX; /** * touch事件的上一个x值 */ private int lastX; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch(event.getAction()) { case MotionEvent.ACTION_DOWN: firstX = lastX = (int) event.getX(); isDrag = false; break; case MotionEvent.ACTION_MOVE: //判断是否发生拖动 if(Math.abs(event.getX()-lastX)>5) { isDrag = true; } //计算手指在屏幕上移动的距离 int dis = (int) (event.getX()-lastX); //将本次的位置设置给lastX lastX = (int) event.getX(); //根据手指移动的距离,改变slidebtn_left的值 slidebtn_left = slidebtn_left+dis; break; case MotionEvent.ACTION_UP: //在发生拖动的情况下,根据最后的位置,判断当前的开关的状态 if(isDrag){ int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值 /** * 根据slidebtn_left判断,当前应该是什么状态 * */ if(slidebtn_left>maxLeft/2) {//应为打开状态 currState = true; }else { currState = false; } flushState(); } break; } flushView(); return true; } }
运行效果如下:
3. 上面的是标准的自定义属性的使用,还有不太正规,但是很方便的使用自定义属性的方法,如下:
(1)在上面的"开关按钮"工程的activity_main.xml文件中,添加如下:
(2)然后在MyToggleButton.java使用在xml文件中定义的testAttrs,如下:
package com.himi.togglebtn; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; public class MyToggleButton extends View implements OnClickListener { //作为背景的图片 private Bitmap backgroundBitmap; //滑动的开关图片 private Bitmap slidebtn; private Paint paint; //滑动按钮的左边界 private float slidebtn_left; /** * 当前开关的状态 * true :为开 * false:为关 */ private boolean currState = false; /** * 背景图的资源ID */ private int backgroundId; /** * 滑动图片的资源ID */ private int slideBtnId; /** * 我们在代码里面创建对象的时候,使用此构造方法 * @param context */ public MyToggleButton(Context context) { super(context); // TODO 自动生成的构造函数存根 } /** * 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。 * 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造) * @param context * @param attrs */ public MyToggleButton(Context context, AttributeSet attrs) { super(context, attrs); //无命名空间测试,测试我们在activity_main.xml文件中定义的属性-----testAttrs="hello" String testAttrs = attrs.getAttributeValue(null, "testAttrs"); System.out.println("testAttrs==:"+testAttrs); //获得自定义的属性 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn); int N = ta.getIndexCount(); for(int i=0; i<N; i++) { /* * 获得某个属性的ID值 */ int itemId = ta.getIndex(i); switch (itemId) { case R.styleable.MyToggleBtn_curr_state: currState = ta.getBoolean(itemId, false); break; case R.styleable.MyToggleBtn_my_background: backgroundId = ta.getResourceId(itemId, -1); if(backgroundId == -1) { throw new RuntimeException("请设置背景图片"); } backgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundId); break; case R.styleable.MyToggleBtn_my_slide_btn: slideBtnId = ta.getResourceId(itemId, -1); if(slideBtnId == -1) { throw new RuntimeException("请设置滑动按钮图片"); } slidebtn = BitmapFactory.decodeResource(getResources(), slideBtnId); break; } } initView(); } /** * 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle * 改变生成自定义的View的样式style * @param context * @param attrs * @param defStyle */ public MyToggleButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO 自动生成的构造函数存根 } //初始化 private void initView() { //初始化图片 //backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); //slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button); //初始化画笔 paint = new Paint(); paint.setAntiAlias(true);//打开抗锯齿 //添加Onclick事件监听 setOnClickListener(this); //刷新当前状态 flushState(); } /* * View对象显示在屏幕上,有几个重要步骤: * 1. 构造方法 创建 对象. * 2. 测量View的大小. onMeasure(int, int):系统调用的方法,获知View的大小 * 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用 * 4. 绘制View的内容 onDraw(canvas) * */ /** * * 测量尺寸时候的回调方法 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); /** * 设置当前View的大小 * width :当前View的宽度 * height:当前view的高度(单位:像素) */ setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); } /** * 自定义的View,作用不大 * 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法 */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // TODO 自动生成的方法存根 super.onLayout(changed, left, top, right, bottom); } /** * 绘制当前View的内容 */ @Override protected void onDraw(Canvas canvas) { // TODO 自动生成的方法存根 //super.onDraw(canvas); //绘制背景图 /* * backgroundBitmap:要绘制的图片 * left 图片的左边界 * top 图片的上边界 * paint 绘制图片要使用的画笔 */ canvas.drawBitmap(backgroundBitmap, 0, 0, paint); //绘制可滑动的按钮 canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint); } /** * 判断是否发生拖到 * 如果拖动了,就不再响应Onclick事件 * true:发生拖动 * false:没有发生拖动 */ private boolean isDrag = false; /** * onClick事件在view.onTouchEvent中被解析 * 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件 */ public void onClick(View v) { /* * 如果没有拖动,才执行改变状态的动作 */ if(!isDrag) { currState = ! currState; flushState();//刷新当前开关状态 } } /** * 刷新当前开关视图 */ private void flushState() { if(currState) { slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth(); }else { slidebtn_left =0; } flushView(); } public void flushView() { /** * 对slidebtn_left的值进行判断 * 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界) * */ int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值 //确保slidebtn_left >= 0 slidebtn_left =(slidebtn_left>0)?slidebtn_left:0; //确保slidebtn_left <=maxLeft slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft; //告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果 invalidate(); } /** * down事件时的x值 */ private int firstX; /** * touch事件的上一个x值 */ private int lastX; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch(event.getAction()) { case MotionEvent.ACTION_DOWN: firstX = lastX = (int) event.getX(); isDrag = false; break; case MotionEvent.ACTION_MOVE: //判断是否发生拖动 if(Math.abs(event.getX()-lastX)>5) { isDrag = true; } //计算手指在屏幕上移动的距离 int dis = (int) (event.getX()-lastX); //将本次的位置设置给lastX lastX = (int) event.getX(); //根据手指移动的距离,改变slidebtn_left的值 slidebtn_left = slidebtn_left+dis; break; case MotionEvent.ACTION_UP: //在发生拖动的情况下,根据最后的位置,判断当前的开关的状态 if(isDrag){ int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值 /** * 根据slidebtn_left判断,当前应该是什么状态 * */ if(slidebtn_left>maxLeft/2) {//应为打开状态 currState = true; }else { currState = false; } flushState(); } break; } flushView(); return true; } }
测试logcat输出为:
相关文章推荐
- 导航地图3_定位
- android中日期加天数的方法
- Java Map按照Value 排序
- hdu 5452 Minimum Cut(树链剖分)
- hdu5493
- POJ 2828 线段树
- [转载]java面试中经常会被问到的一些算法的问题
- 第一层内容:杂题
- 深入剖析Java中的装箱和拆箱
- 扩展函数
- Tarena - 一个页面的制作
- kafka的文件存储
- 导航地图2_自定义标注
- Xcode 中的main.m
- 活用各种数据结构——线段树篇
- 当幸福来敲门
- scrollView的contentSize,contentInsert,contentOffset
- python--tile函数
- c++实现数据结构4.双循环链表
- 数码管 键盘 十进制计数