自定义控件(视图)2期笔记05:自定义控件之继承自View(滑动开关)

1.  开关按钮点击效果,如下:

2. 继承已有View实现自定义View

3. 下面通过一个案例实现滑动开关的案例:

(1)新建一个新的Android工程,命名为" 开关按钮",接下来我们按照上面的步骤来:自定义类MyToggleButton继承自View

(2)编写设计activity_main.xml布局文件,如下:

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     tools:context="com.himi.togglebtn.MainActivity" >
 6 
 7     <com.himi.togglebtn.MyToggleButton
 8         android:layout_width="wrap_content"
 9         android:layout_height="wrap_content"
10         android:id="@+id/my_toggle_btn" 
11         android:layout_centerHorizontal="true"
12         android:layout_centerVertical="true"/>
13 
14 </RelativeLayout>

注意这里使用的自定的MyToggleButton(View),要使用全路径

(3)编写自定义的MyToggleButton。

• 重写onMeasure()方法,指定控件大小

• 重写onDraw()方法,绘制控件内容

  1 package com.himi.togglebtn;
  2 
  3 import android.content.Context;
  4 import android.graphics.Bitmap;
  5 import android.graphics.BitmapFactory;
  6 import android.graphics.Canvas;
  7 import android.graphics.Paint;
  8 import android.util.AttributeSet;
  9 import android.view.View;
 10 import android.view.View.OnClickListener;
 11 
 12 public class MyToggleButton extends View implements OnClickListener {
 13 
 14     //作为背景的图片
 15     private Bitmap backgroundBitmap;
 16     //滑动的开关图片
 17     private Bitmap slidebtn;
 18     private Paint paint;
 19     
 20     //滑动按钮的左边界
 21     private float slidebtn_left;
 22     
 23     /**
 24      * 当前开关的状态
 25      * true :为开
 26      * false:为关
 27      */
 28     private boolean currState = false;
 29     
 30 
 31     /**
 32      * 我们在代码里面创建对象的时候,使用此构造方法
 33      * @param context
 34      */
 35     public MyToggleButton(Context context) {
 36         super(context);
 37         // TODO 自动生成的构造函数存根
 38     }
 39     
 40     /**
 41      * 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。
 42      * 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造)
 43      * @param context
 44      * @param attrs
 45      */
 46     public MyToggleButton(Context context, AttributeSet attrs) {
 47         super(context, attrs);
 48         initView();
 49     }
 50 
 51     
 52     /**
 53      * 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle
 54      * 改变生成自定义的View的样式style
 55      * @param context
 56      * @param attrs
 57      * @param defStyle
 58      */
 59     public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
 60         super(context, attrs, defStyle);
 61         // TODO 自动生成的构造函数存根
 62     }
 63 
 64     
 65     //初始化
 66     private void initView() {
 67         //初始化图片
 68         backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
 69         slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
 70         //初始化画笔
 71         paint = new Paint();
 72         paint.setAntiAlias(true);//打开抗锯齿
 73         //添加Onclick事件监听
 74         setOnClickListener(this);
 75     }
 76     
 77     /*
 78      * View对象显示在屏幕上,有几个重要步骤:
 79      * 1. 构造方法 创建  对象.
 80      * 2. 测量View的大小.  onMeasure(int, int):系统调用的方法,获知View的大小
 81      * 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用
 82      * 4. 绘制View的内容           onDraw(canvas)
 83      * 
 84      */
 85     
 86     
 87     
 88     /**
 89      * 
 90      * 测量尺寸时候的回调方法
 91      */
 92     @Override
 93     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 94         
 95         //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 96         /**
 97          * 设置当前View的大小
 98          * width :当前View的宽度
 99          * height:当前view的高度(单位:像素)
100          */
101         setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
102     }
103     
104     
105     /**
106      * 自定义的View,作用不大
107      * 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法
108      */
109     @Override
110     protected void onLayout(boolean changed, int left, int top, int right,
111             int bottom) {
112         // TODO 自动生成的方法存根
113         super.onLayout(changed, left, top, right, bottom);
114     }
115     
116     /**
117      * 绘制当前View的内容
118      */
119     @Override
120     protected void onDraw(Canvas canvas) {
121         // TODO 自动生成的方法存根
122         //super.onDraw(canvas);
123         //绘制背景图
124         /*
125          * backgroundBitmap:要绘制的图片
126          * left 图片的左边界
127          * top 图片的上边界
128          * paint 绘制图片要使用的画笔
129          */
130         canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
131         //绘制可滑动的按钮
132         canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint);
133     }
134 
135     public void onClick(View v) {
136         currState = ! currState;
137         flushState();//刷新当前开关状态
138         
139     }
140 
141     /**
142      * 刷新当前开关视图
143      */
144     private void flushState() {
145         if(currState) {
146             slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth();
147         }else {
148             slidebtn_left =0;
149         }
150         
151         /*
152          * invalidate():刷新当前View,会导致onDraw方法的执行
153          * 上面只是设置参数设置,下面必须将上面的参数传递到onDraw方法中,利用onDraw方法重新绘制,才能实现刷新的效果
154          * 
155          */
156         invalidate();
157         
158     }
159     
160 
161 
162 
163 }

代码逻辑如下:

备注:

  初始状态slideBtn 左边为0

          

  开的时候slideBtn left值为background.width-slidebtn.width

          

与此同时,MainActivity.java,如下:

 1 package com.himi.togglebtn;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 
 6 public class MainActivity extends Activity {
 7 
 8     @Override
 9     protected void onCreate(Bundle savedInstanceState) {
10         super.onCreate(savedInstanceState);
11         setContentView(R.layout.activity_main);
12     }
13 
14 }

(4)布署程序到模拟器上如下:

(5)实现开关的弹性滑动,上面的只能让开关左右切换不能手机拖动滑动开关,用户体验不好,我们要优化。

MainActivity.java,修改为:

  1 package com.himi.togglebtn;
  2 
  3 import android.content.Context;
  4 import android.graphics.Bitmap;
  5 import android.graphics.BitmapFactory;
  6 import android.graphics.Canvas;
  7 import android.graphics.Paint;
  8 import android.util.AttributeSet;
  9 import android.view.MotionEvent;
 10 import android.view.View;
 11 import android.view.View.OnClickListener;
 12 
 13 public class MyToggleButton extends View implements OnClickListener {
 14 
 15     //作为背景的图片
 16     private Bitmap backgroundBitmap;
 17     //滑动的开关图片
 18     private Bitmap slidebtn;
 19     private Paint paint;
 20     
 21     //滑动按钮的左边界
 22     private float slidebtn_left;
 23     
 24     /**
 25      * 当前开关的状态
 26      * true :为开
 27      * false:为关
 28      */
 29     private boolean currState = false;
 30     
 31 
 32     /**
 33      * 我们在代码里面创建对象的时候,使用此构造方法
 34      * @param context
 35      */
 36     public MyToggleButton(Context context) {
 37         super(context);
 38         // TODO 自动生成的构造函数存根
 39     }
 40     
 41     /**
 42      * 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。
 43      * 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造)
 44      * @param context
 45      * @param attrs
 46      */
 47     public MyToggleButton(Context context, AttributeSet attrs) {
 48         super(context, attrs);
 49         initView();
 50     }
 51 
 52     
 53     /**
 54      * 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle
 55      * 改变生成自定义的View的样式style
 56      * @param context
 57      * @param attrs
 58      * @param defStyle
 59      */
 60     public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
 61         super(context, attrs, defStyle);
 62         // TODO 自动生成的构造函数存根
 63     }
 64 
 65     
 66     //初始化
 67     private void initView() {
 68         //初始化图片
 69         backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
 70         slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
 71         //初始化画笔
 72         paint = new Paint();
 73         paint.setAntiAlias(true);//打开抗锯齿
 74         //添加Onclick事件监听
 75         setOnClickListener(this);
 76     }
 77     
 78     /*
 79      * View对象显示在屏幕上,有几个重要步骤:
 80      * 1. 构造方法 创建  对象.
 81      * 2. 测量View的大小.  onMeasure(int, int):系统调用的方法,获知View的大小
 82      * 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用
 83      * 4. 绘制View的内容           onDraw(canvas)
 84      * 
 85      */
 86     
 87     
 88     
 89     /**
 90      * 
 91      * 测量尺寸时候的回调方法
 92      */
 93     @Override
 94     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 95         
 96         //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 97         /**
 98          * 设置当前View的大小
 99          * width :当前View的宽度
100          * height:当前view的高度(单位:像素)
101          */
102         setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
103     }
104     
105     
106     /**
107      * 自定义的View,作用不大
108      * 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法
109      */
110     @Override
111     protected void onLayout(boolean changed, int left, int top, int right,
112             int bottom) {
113         // TODO 自动生成的方法存根
114         super.onLayout(changed, left, top, right, bottom);
115     }
116     
117     /**
118      * 绘制当前View的内容
119      */
120     @Override
121     protected void onDraw(Canvas canvas) {
122         // TODO 自动生成的方法存根
123         //super.onDraw(canvas);
124         //绘制背景图
125         /*
126          * backgroundBitmap:要绘制的图片
127          * left 图片的左边界
128          * top 图片的上边界
129          * paint 绘制图片要使用的画笔
130          */
131         canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
132         //绘制可滑动的按钮
133         canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint);
134     }
135     
136     /**
137      * 判断是否发生拖到
138      * 如果拖动了,就不再响应Onclick事件
139      * true:发生拖动
140      * false:没有发生拖动
141      */
142     private boolean isDrag = false;
143 
144     /**
145      * onClick事件在view.onTouchEvent中被解析
146      * 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件
147      */
148     public void onClick(View v) {
149         /*
150          * 如果没有拖动,才执行改变状态的动作
151          */
152         if(!isDrag) {
153             currState = ! currState;
154             flushState();//刷新当前开关状态
155         }
156     }
157 
158     /**
159      * 刷新当前开关视图
160      */
161     private void flushState() {
162         if(currState) {
163             slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth();
164         }else {
165             slidebtn_left =0;
166         }
167         
168         flushView();    
169     }
170     
171     public void flushView() {
172         /**
173          * 对slidebtn_left的值进行判断
174          * 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界)
175          *             
176          */
177         int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值
178         //确保slidebtn_left >= 0
179         slidebtn_left =(slidebtn_left>0)?slidebtn_left:0;
180         //确保slidebtn_left <=maxLeft
181         slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft;
182         
183         //告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果
184         invalidate();
185     }
186     
187     /**
188      * down事件时的x值
189      */
190     private int firstX;
191     /**
192      * touch事件的上一个x值
193      */
194     private int lastX;
195     
196     @Override
197     public boolean onTouchEvent(MotionEvent event) {
198         super.onTouchEvent(event);
199         switch(event.getAction()) {
200         case MotionEvent.ACTION_DOWN:
201             firstX = lastX = (int) event.getX();
202             isDrag = false;
203             break;
204         case MotionEvent.ACTION_MOVE:
205             //判断是否发生拖动
206             if(Math.abs(event.getX()-lastX)>5) {
207                 isDrag = true;
208             }
209             
210             //计算手指在屏幕上移动的距离
211             int dis = (int) (event.getX()-lastX);
212             //将本次的位置设置给lastX
213             lastX = (int) event.getX();
214             //根据手指移动的距离,改变slidebtn_left的值
215             slidebtn_left = slidebtn_left+dis;
216             break;
217         case MotionEvent.ACTION_UP:
218             
219             //在发生拖动的情况下,根据最后的位置,判断当前的开关的状态
220             if(isDrag){
221                 int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值    
222                 /**
223                  * 根据slidebtn_left判断,当前应该是什么状态
224                  * 
225                  */
226                 if(slidebtn_left>maxLeft/2) {//应为打开状态
227                     currState = true;
228                 }else {
229                     currState = false;
230                 }
231                 flushState();
232             }
233             
234             break;
235         
236         }
237         flushView();
238         return  true;
239     }
240 
241     
242     
243 
244 
245 
246 }

代码逻辑如下:

布署程序到模拟器上,如下:

原文地址:https://www.cnblogs.com/hebao0514/p/4842166.html