android中开关控件,Android一个自定义开关的控件

本文介绍了一种自定义的Android开关控件实现方法,该控件支持通过点击数字或滑动来改变状态,并详细解释了控件的绘制、触摸事件处理及平滑过渡等关键技术点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android一个自定义开关的控件

支持点击【1、2、3、4】等数字箭头移动到相应的位置

以及手指滑动,跳转到相应的位置

效果图:

0818b9ca8b590ca3270a3433284dd417.png

素材:

由于没有UI切图,中间也懒得画,所以中间的图片我用的是一整个的,如果你需要到时候可以自己叠上去

0818b9ca8b590ca3270a3433284dd417.png

测量高宽

自定义控件,首先测量控件的高宽

@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地址,里面有详细的注释说明。

随便转载吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值