1. 自定义View的项目地址
效果图:可以滑动,可以点击,然后移动
2.原理分析
1.图形:
2.移动:点击时让draw重新绘制,就实现了移动
3.步骤:
1.实现构造方法:改2个super为this
/**
* 1.实现构造方法,将两个super,改为this,然后再添加参数
*/
/**
* new的时候执行
* @param context
*/
public MyButtonImprove(Context context) {
this(context, null);//改为this后,这里会执行下面的构造方法
}
/**
* xml布局的时候使用
*
* @param context
* @param attrs
*/
public MyButtonImprove(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);//改为this后,这里会执行下面的构造方法
}
/**
* R.attr.buttonStyle的时候执行
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyButtonImprove(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.d(TAG, "MyButton: 3");
initView();
initListener();
}
2.画图形:一个背景,一个可移动的圆
测量:自定义控件的长宽:
setMeasuredDimension是可以设置自定义View的长宽大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 设置view的宽高,这个会设置MyButtonImprove长宽
setMeasuredDimension((int) (4*mR), (int) (2*mR));
}
先初始化要用的:
//设置按钮距离左边的最大距离
mSlidLeftMax = 3*mR;
mPaint = new Paint();
mPaint.setAntiAlias(true);
//画弧,基本轮廓(形状,大小)
ovalLeft = new RectF(0,0,2*mR,2*mR);
ovalRight = new RectF(2*mR,0,4*mR,2*mR);
画背景:
/** canvas.drawArc
* @param oval 椭圆边界的确定和大小
* @param startAngle 画弧开始的角度,这里是以X轴为开始角度,顺时针角度增加
* @param sweepAngle 要扫描多少度
* @param useCenter 弧的中点是否使用,使用的话,弧点到中心连成一条线,这样可以实现饼图
* @param paint
*/
canvas.drawArc(ovalLeft,startAngle,sweepAngle,true,mPaint);
/** canvas.drawRect 画矩形
* @param left 距离(自定义View)的左边位置
* @param top 距离(自定义View)的顶部位置
* @param right 要扫描的距离,右
* @param bottom 要扫描的距离,低
* @param paint
*/
canvas.drawRect(mR,0,3*mR,2*mR,mPaint);
canvas.drawArc(ovalRight,270,180,true,mPaint);
图解:
画弧:
画矩形:
3.设置点击事件
其实就是改变移动圆的中心X的坐标,然后重新绘制
//是否开关,默认为false,表示关
boolean isOpen = false;
//如果是开的
if (isOpen) {
close();
}
//如果是关的
else {
open();
}
invalidate();//强制让draw方法重新绘制
//设置slideBitmap距离背景左边的大小,默认为半径mR,即关闭状态
float slideLeft = mR;
// 距离左边最大的距离3*mR
private float mSlidLeftMax;
private void open() {
isOpen = true;
slideLeft = mSlidLeftMax;
}
private void close() {
isOpen = false;
slideLeft = mR;
}
4.设置滑动(Touch事件)事件,解决触摸事件与点击事件的冲突
/**
* 4.设置滑动事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent: ");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://手指按下
isEnableClick=true;
//记录下第一次点击时的位置
downX = event.getX();
break;
case MotionEvent.ACTION_MOVE://手移动,有多个移动
//获取move的位置
float moveX = event.getX();
//移动多少:现在的位置-down时的位置
//屏蔽非法的移动
float translateX = moveX - downX;
slideLeft+=translateX;
block_illegal_movement();
//刷新draw
invalidate();
downX=moveX;
break;
case MotionEvent.ACTION_UP://手抬起
float upX = event.getX();
float upDownX = upX - downX;
//获取绝对值
if (Tool.getAbsoluteValue(upDownX)>5){
isEnableClick=false;
if (slideLeft>=mSlidLeftMax/2) {
slideLeft=mSlidLeftMax;
isOpen=true;
}else {
slideLeft=mR;
isOpen=false;
}
//重新draw
invalidate();
}
break;
}
return super.onTouchEvent(event);//true表示事件被消费了,就不会向下传递啦
}
因为触摸事件为:DOWN -----> MOVE……MOVE -------> UP
- DOWN到MOVE事件,记录之间的位移,然后设置移动圆中心点位置偏移,然后重新绘制
- MOVE到MOVE事件,记录之间的位移,然后设置移动圆中心点位置偏移,然后重新绘制,设置move点为原点(downX=moveX,目的是执行下一次滑动的时候,以这个点开始)
……MOVE到MOVE事件…… - MOVED到UP 事件,其实就是获取移动圆距离左边的距离slideLeft,根据偏左还是右来设置到左边还是右边。
- 解决冲突:因为手指按下的时候会执行点击事件和触摸事件:onClick与onTouchEvent。所以我们要进行判断什么时候执行点击事件,什么时候执行触摸事件。我们肯定是滑动的时候不执行点击事件,因为手指触摸的时候可能会发生细微的移动,肉眼不可见,所以只为0的时候不能判断就表示不移动,一改移动的距离为5以内的时候,表示不移动。
实现过程是设置一个标记,默认为true,表示可以点击
//这是一个是否允许执行点击事件,默认true,允许
boolean isEnableClick=true;
因为是先执行touch再执行click的,所以我们在touch事件里来判断是否执行click事件: