自定义控件--自定义ToggleButton
- 自定义类继承View,并复写三个构造方法
- 在构造方法中对背景图片,按钮图片,按钮滑动最大距离进行初始化,设置点击事件
- 在点击事件中,对开关状态进行反向操作,并使用invalidate(),重新调用onDraw(),在onDraw()中改变按钮图片的位置,实现开关状态的效果
- 实现触摸事件,(注意要调用父类的触摸事件,将事件继续向下转发,在处理结束后要返回true已表示事件已经被消耗)在第一次按下按下控件的时候记录初始的X的坐标,在移动过程中不断的根据X坐标的偏移量刷新视图,在抬起动作中对X坐标进行判断,超过最大滑动距离的一半,滑动到最大距离,不超过,滑动到最小0的位置上
- bug解决
bug描述
在移动按钮过程中,不管是否移动超过最大滑动距离的一半,都会导致按钮状态往反方向变化.
bug原因
当前view在响应触摸事件的同时,又响应的点击事件,原因是调用super.onTouchEvent(event);,按照安卓系统的事件传递机制,在触摸事件响应时将触摸事件传递给点击事件,使得在滑动按钮过程中按照触摸事件响应,在抬起时,又响应了点击事件,点击事件导致按钮开关状态的反向.
bug解决
增加开关变量,用于表示在抬起时,点击事件对按钮开关状态的控制权,true表示点击事件获得对按钮开关状态的控制权,false表示点击事件放弃按钮开关状态的控制权
在触摸事件处理中:
按下时将开关置为true,表示此时点击事件与触摸事件都有权控制按钮的开关状态
移动时,如果移动距离超过一定值,触摸事件接管,将开关置为false,让点击事件放弃控制权
抬起时,对开关进行判断,如果触摸事件有控制权,则刷新按钮状态,否则什么也不做,交给点击事件去处理
代码实现
注:里面有两张图片,需要导入到工程中
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class MyToggleButton extends View implements View.OnClickListener {
private Bitmap backgroundBitmap;
private Bitmap slideBitmap;
private Paint paint;
// 图片距离左边的距离
private float maxSlide;
/**
* 距离左边的最大距离
*/
private int maxLeft ;
/**
* 按钮的开关状态 true:开 flase:关
*/
private boolean isStatus = true;
// 测量view
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置测量的值宽和高,采用背景图片的宽高
setMeasuredDimension(backgroundBitmap.getWidth(),
backgroundBitmap.getHeight());
}
// 绘制View
@Override
protected void onDraw(Canvas canvas) {
// 画出背景
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
//画出滑动按钮
canvas.drawBitmap(slideBitmap, maxSlide, 0, paint);
}
private void initView() {
paint = new Paint();
paint.setColor(Color.RED);
// 设置抗锯齿
paint.setAntiAlias(true);
//初始化背景图片
backgroundBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.switch_background);
//初始化按钮图片
slideBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.slide_button);
//设置按钮距离左边最大滑动距离
maxLeft = backgroundBitmap.getWidth() - slideBitmap.getWidth();
// 设置点击事件
setOnClickListener(this);
}
//第一次按下的X轴坐标
private float startX;
//第一次按下的X轴坐标的历史记录
private float lastX;
/**点击事件是否生效,如果生效滑动事件就无效
* true生效
* false无效
*/
private boolean isClicked = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
//调用父类的方法,将点击事件进行转发
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:// 按下
//1.第一次按下坐标
lastX = startX = event.getX();
isClicked = true;
break;
case MotionEvent.ACTION_MOVE:// 移动
//2.来到新的X轴
float newX = event.getX();
//3.计算偏移量
float dX = newX - startX;
//4.更新控件的位置
maxSlide += dX;
if(Math.abs(event.getX()-lastX)>5){
isClicked = false;
}
flushView();
//5.重新记录X轴坐标
startX = event.getX();
break;
case MotionEvent.ACTION_UP:// 离开
if(!isClicked){
if(maxSlide >maxLeft/2){
//开
isStatus = true;
}else if(maxSlide <=maxLeft/2){
//关
isStatus = false;
}
flushStatus();
}
break;
}
return true;
}
private void flushView() {
//屏蔽非法值
if(maxSlide <=0){
maxSlide = 0;
}
if(maxSlide >=maxLeft){
maxSlide = maxLeft;
}
//导致onDraw执行
invalidate();
}
// 设置自己的样式的时候用到
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
// Android系统通过布局文件使用全类名使用控件的时候,使用这个构造方法对控件进行实例化
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
// 在代码中使用创建对象的时候使用
public MyToggleButton(Context context) {
super(context);
initView();
}
@Override
public void onClick(View v) {
if(isClicked){
isStatus = !isStatus;
flushStatus();
}
}
private void flushStatus() {
if (isStatus) {
// 开
maxSlide = maxLeft;
} else {
// 关
maxSlide = 0;
}
flushView();
}
}
学习笔记
浙公网安备 33010602011771号