Android一个自定义开关的控件
支持点击【1、2、3、4】等数字箭头移动到相应的位置
以及手指滑动,跳转到相应的位置
效果图:
素材:
由于没有UI切图,中间也懒得画,所以中间的图片我用的是一整个的,如果你需要到时候可以自己叠上去
测量高宽
自定义控件,首先测量控件的高宽
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
width=widthSize;
height=heightSize;
if (heightMode==MeasureSpec.AT_MOST||heightMode==MeasureSpec.UNSPECIFIED){
height=widthSize;
}
setMeasuredDimension(width, height);
}
我们这里只处理两个情况,第一个是MeasureSpec.AT_MOST是当高度为:wrap_content的时候会调用的,MeasureSpec.UNSPECIFIED是在scrollview里面或者listview里面的时候用的,我们这里强制设置它的高度跟宽度一样。这样控件就能按照正常的显示了。
定义最外层圆弧图片等大小以及圆心的位置。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//变更图片的大小
int bitmapWidth=w*4/9;
int bitmapHeight=h*4/9;
Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.bitmap);
pointerBitmap=zoomImg(bitmap,bitmapWidth,bitmapHeight);
//设置外层小圆点的弧度大小,下面的值是padding我各取高宽的一定比例
int middleL = w/10;
int middleT = w/4;
int middleR = w - w/4;
int middleB = w- w/4;
rectF = new RectF(middleL, middleT, middleR, middleB);
//获取圆心的坐标
point=new Point((int)rectF.centerX(),(int)rectF.centerY());
}
绘制onDraw
绘制中间图片指针
既然知道圆心的位置了,那么我们就画上中间的指针。我们要画一个能随传入角度改变角度的指针,我这里使用的是通过旋转画布并且不断绘制来达到图片的旋转效果
/** * *@param canvas *@param currentDegree 角度 */
private synchronized void rotateBitmap(Canvas canvas,float currentDegree){
canvas.save();
//设置画布的旋转中心
canvas.rotate(currentDegree,point.x,point.y);
textAndPointPaint.setColor(circlePointColor);
canvas.drawBitmap(pointerBitmap,point.x-pointerBitmap.getWidth()/2,point.y-pointerBitmap.getHeight()/2,null);
canvas.restore();
}
drawBitmap中间是图片中心坐标,然后减去一半的图片高度和宽度,使图片在中间显示
绘制最外层的小点跟数字
/** * 画点和字 *@param canvas *@param degree */
private synchronized void drawPointAndText(Canvas canvas, float degree){
float r=rectF.width()/2; //小圆点所在位置与圆心距离
int circleRadii=DensityUtils.dp2px(getContext(),pointRadii);//小圆点的半径
float x;//小圆点圆心x
float y;//小圆点圆心Y
float textX=0;//字体的位置x
float textY=0;//字体的位置y
int position= degreeToShift(degree);//获得大概的位置
textAndPointPaint.setTextSize(DensityUtils.sp2px(getContext(),strsSize));//字体大小
for (int i=0;i
if (i==position){
textAndPointPaint.setColor(textAndPointColorS);
}else {
textAndPointPaint.setColor(textAndPointColorN);
}
x= (float) (point.x+r*Math.cos(shiftToDegree(i)*Math.PI/180));//三角函数 得出x坐标
y= (float) (point.y+r*Math.sin(shiftToDegree(i)*Math.PI/180));//三角函数 得出y坐标
canvas.drawCircle(x,y,circleRadii, textAndPointPaint);//画圆
String string = strs[i];
textAndPointPaint.getTextBounds(string,0,string.length(),rect);
switch (i){//根据设计图计算字体摆放的位置
case 0:
textX= x-rect.width()-circleRadii*2;
textY=y+rect.height()/2;
break;
case 1:
textX=x-rect.width()-circleRadii*2;
textY=y+rect.height()/2;
break;
case 2:
textX=x-rect.width()/2;
textY=y-circleRadii*2;
break;
case 3:
textX=x+circleRadii;
textY=y+rect.height()/2;
break;
case 4:
textX=x+circleRadii;
textY=y+rect.height()/2;
break;
}
canvas.drawText(string,textX,textY, textAndPointPaint);
if (!isSaveRects){//记录字体点击的区域 如果想增加点击的区域 这里增加
int left= (int) textX-circleRadii*2;
int top= (int) (textY-rect.height())-circleRadii;
int right= (int) (textX+rect.width()+circleRadii*3);
int bottom= (int) textY+circleRadii;
Rect rect=new Rect(left,top,right,bottom);
rects.add(i,rect);
if (i==strs.length-1){
isSaveRects=true;
}
}
}
}
根据外面字的多少,用三角函数计算出每个点的坐标位置。然后画小点,再根据小点的坐标位置以及字的长度,画出相应位置的文字。最后,再给相应的文字画上一个长方形,保存有效点击范围。
onTouch事件的重写
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!onOffSwitch){
return false;
}
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if (smoothSlide!=null){
smoothSlide.threadStop();//停止
}
oldX= (int) event.getX();
oldY= (int) event.getY();
Rect rect;
for (int i=0;i
rect=rects.get(i);
if (rect.contains(oldX,oldY)){
setShift(i);//当前档位
if (callBack!=null){
callBack.selected(getShift());
}
smooth(shiftToDegree(i));
return false;
}
}
float zhengqie = (float) Math.atan2(point.y-oldY,point.x-oldX);//三角函数 计算两点位置
if(zhengqie < 0){
zhengqie = (float) (zhengqie + 2*Math.PI);//如果得到正切是负数,则把它转换
}
oldDegree= (float) (180*zhengqie/Math.PI);//转换成角度
break;
case MotionEvent.ACTION_MOVE:
float x=event.getX();
float y=event.getY();
float zhengqie2 = (float) Math.atan2(point.y-y,point.x-x);
if(zhengqie2 < 0){
zhengqie2 = (float) (zhengqie2 + 2*Math.PI);
}
float newDegree= (float) (180*zhengqie2/Math.PI);
float changeDegree=newDegree-oldDegree;//角度变更的大小
oldDegree=newDegree;//替换旧的相对位置
changeDegree=getCurrentDegree()+changeDegree;//当前需要变更的大小
if (changeDegree<0){//如果角度是负数,则加上360
changeDegree+=360;
}
changeDegree=changeDegree%360;//取模
if (changeDegree>0&&changeDegree<180){//如果超出最大最小值,则不处理
return true;
}
setCurrentDegree(changeDegree);
postInvalidate();
return true;
case MotionEvent.ACTION_UP:
int position= degreeToShift(getCurrentDegree());//获取当前档位
setShift(position);
if (callBack!=null){
callBack.selected(getShift());
}
smooth(shiftToDegree(getShift()));
break;
}
return true;
}
首先是判断onOffSwitch开关是否打开,如果没打开就直接return掉,不接收点击事件。
根据保存的矩形,来判断用户点击的坐标是否是在外面几个文字的范围内,如果是,则进行滑动的操作。如果不是,则记录下点的位置与圆心的角度。
如果没触发点击字体的事件,则触发Move,则用三角函数不断的去获取当前的角度,然后对照前面的角度进行旋转。并且进行重绘postInvalidate
在抬起动作时候,判断根据角度去判断与哪个档位最近,然后滑动到相应档位。
平滑过渡
在点击文字或者抬起动作做档位滑动的时候,我们做平滑过渡。开一个线程
@Override
public void run() {
super.run();
float current=getCurrentDegree();//当前角度
float speed=Math.abs(degree-current)/60;//设置当前速度
if (speed<1){//如果速度小于1 则强制设为1
speed=1;
}
while (current!=degree&&!threadStop){
if (current
current+=speed;
if (current > degree) {
current = degree;
}
}else if (current>degree){
current-=speed;
if (current < degree) {
current = degree;
}
}else {
current=degree;
}
setCurrentDegree(current);
postInvalidate();
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
首先计算速度。然后里面一直做平滑操作。直到结束。当然,这里开启新线程的时候记得关闭旧的线程噢。否则。。。。
我是里面添加一个标记来结束的
/** * 停止线程标记 */
public void threadStop(){
this.threadStop=true;
}
然后设置相应的callback就好了。
终于完了~以下是完整demo地址,里面有详细的注释说明。
随便转载吧。