安卓自定义控件_方向控制面板

近期公司需要做一个控制面板对摄像头进行方向控制,之前用一个圆形背景加4个圆形Button做的,同事可能感觉做的太敷衍了,而且点击效果都被手指挡住了,所以他给领导打小报告说让我重做,既然领导说做,哪有不做的道理

先看效果,左边的是按下时的效果, 没有按键时,颜色和上下右边的颜色是一致的,按键灵敏,使用简单

下面我们看看怎么用画布画出这个Panel(当然,颜色什么的可以可以换)

首先我们需要继承一个View,给View初始化一个isTouch数组,这个数组在你点击的时候记录你点击的那个方向,然后重绘面板的时候,会将点击的方向颜色变深;

然后初始化画笔,和一些默认尺寸,arrowLeft 和 arrowLeftDP 都是表示尺寸的, 为了自适应不同的屏幕大小,我们用arrowLeftDP来记录默认dp尺寸的大小,在初始化的时候将arrowLeftDP转化成PX(像素)赋给arrowLeft,所以下面所有的带DP和不带DP的尺寸变量都是这个原理

初始化的时候我们顺便设置一下触摸事件,后面我们要点击用到

public class PanelView extends View implements View.OnTouchListener {

    private final String TAG = "PanelView";
    boolean[] isTouch = new boolean[]{false,false,false,false};
    private Paint paint;
    private int smallCircleRadius = 180;
    private final int  strokeWidth = 12;
    private final int  halfStrokeWidth = strokeWidth/2;
    private int arrowLeft = -70;
    private int arrowTop = 280;
    private int arrowRight = 70;
    private int arrowBottom = 450;
    private final int smallCircleRadiusDP = 51;
    private int arrowLeftDP = -20;
    private int arrowTopDP = 80;
    private int arrowRightDP = 20;
    private int arrowBottomDP = 125;

    public PanelView(Context context) {
        super(context);
        setOnTouchListener(this);
        dpTopx(context);
    }

    public PanelView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setOnTouchListener(this);
        dpTopx(context);
    }

    public PanelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOnTouchListener(this);
        dpTopx(context);
    }

    public PanelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setOnTouchListener(this);
        dpTopx(context);
    }

    private void dpTopx(Context context){
        Log.i(TAG,"small Circle Radius " + DensityUtil.px2dip(context,smallCircleRadius) + "\r"
        + "stroke Width " + DensityUtil.px2dip(context,strokeWidth) + "\r"
        + "arrow Left " + DensityUtil.px2dip(context,arrowLeft)+ "\r"
        + "arrow Top " + DensityUtil.px2dip(context,arrowTop)+ "\r"
        + "arrow Right " + DensityUtil.px2dip(context,arrowRight)+ "\r"
        + "arrow Bottom " + DensityUtil.px2dip(context,arrowBottom));
        smallCircleRadius = DensityUtil.dip2px(context,smallCircleRadiusDP);
        arrowLeft = DensityUtil.dip2px(context,arrowLeftDP);
        arrowTop = DensityUtil.dip2px(context,arrowTopDP);
        arrowRight = DensityUtil.dip2px(context,arrowRightDP);
        arrowBottom = DensityUtil.dip2px(context,arrowBottomDP);
    }
}

接下来我们要画出面板的图形,整个绘制都在Ondraw里面

第一步我们先要画出1个扇形,扇形在画的时候要判断下是不是点击状态,根据状态的不同我们画扇形时的颜色不一样,rectF是画扇形时用的矩形框

再画出扇形的边框,rectFStroke时画边框的矩形,因为画笔有宽度,所以设置矩形时,需要将矩形的大小减少画笔宽度的一半,才能将边框完整的显示出来,

然后每画一个扇形就旋转90度,直到4个扇形全部画完

画完扇形将箭头旋转画到画布上,箭头的大小和位置是根据类初始化的时候设定的数值去绘制的

最后我们在中心画一个小圆就完成了我们的面板了

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint = new Paint();
        int width = getWidth();
        int height = getHeight();
        int radius = Math.min(width,height);
        paint.setAntiAlias(true);
        @SuppressLint("DrawAllocation")
        RectF rectF = new RectF((width-radius)/2.0f,
                (height-radius)/2.0f,
                width - (width-radius)/2.0f,
                height - (height-radius)/2.0f);

        @SuppressLint("DrawAllocation")
        RectF rectFStroke = new RectF((width-radius)/2.0f + halfStrokeWidth,
                (height-radius)/2.0f + halfStrokeWidth,
                width - (width-radius)/2.0f - halfStrokeWidth,
                height - (height-radius)/2.0f - halfStrokeWidth);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
                R.drawable.jiantou);

        float startAngle = 45.0f;
        for(int i = 0; i< 4; i++) {
            if(isTouch[i])
                paint.setColor(0xff8a8a8a);
            else
                paint.setColor(0xffcdcdcd);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawArc(rectF, startAngle, 90.0f, true, paint);

            paint.setStrokeWidth(strokeWidth);
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(0xffbfbfbf);
            canvas.drawArc(rectFStroke, startAngle, 90.0f, true, paint);
            startAngle += 90;
        }

        float bitmapStartAngle = 90f;
        Rect rectFBitmap = new Rect(0,
                0,
                bitmap.getWidth(),
                bitmap.getHeight());
        //RectF rect = new RectF(- 80,200,80,radius/2-40);
        RectF rect = new RectF(arrowLeft,arrowTop,arrowRight,arrowBottom);
        Log.i(TAG, rect.toString());
        canvas.save();
        canvas.translate(width/2.0f,height/2.0f);
        for(int i = 0; i< 4; i++){
            canvas.rotate(bitmapStartAngle);
            canvas.drawBitmap(bitmap,rectFBitmap,rect,paint);
        }
        canvas.restore();

        paint.setColor(0xffe6e6e6);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(width/2.0f,height/2.0f,smallCircleRadius,paint);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(0xffbfbfbf);
        canvas.drawCircle(width/2.0f,height/2.0f,smallCircleRadius-halfStrokeWidth,paint);
    }

这时候面板是没有点击效果的,我们需要给面板添加点击事件

当点击面板的时候,我们首先判断是不是在圆内(中间的小圆点击不算在范围内),如果不在有效范围内,则点击无效,然后判断点击的是哪个区域,根据点击区域设置对应的isTouch标记为true,并重绘面板,当手指抬起时,将所有的isTouch全置为false,并重绘面板,让面板恢复初始状态

判断是不是在圆内,根据勾股定理计算半径,如果半径小于大圆的半径且大于小圆的半径即在圆内

判断在哪个方向也很简单,只需要判断X>Y还是X<Y即可,因为我们的面板像是一个圆上在45度和-45度的地方画了2条直经,当我们手指点在45度的分割线上, X==Y;但是稍微偏一点的话 X要么大于Y,要么小于Y,根据这个原理判断我们点击的范围

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.i(TAG,"Motion Event is "+event.getAction());
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                boolean isInCircle = IsPointInCircle(event.getX(),event.getY());
                if(!isInCircle) break;
                int area = inWitchArea(event.getX(),event.getY());
                if(area == -1) break;
                isTouch[area] = true;
                invalidate();
                return true;
            case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                isTouch[0] = false;
                isTouch[1] = false;
                isTouch[2] = false;
                isTouch[3] = false;
                invalidate();
                break;
        }
        return false;
    }

    private int inWitchArea(float x1,float y1){
        int width = getWidth();
        int height = getHeight();
        float x = width/2.0f;
        float y = height/2.0f;
        boolean XGreaterThanY = Math.abs((x - x1)) > Math.abs((y - y1));
        if(x1 > x && y1 < y){
            //第一象限
            if(XGreaterThanY)
                return 3;
            else
                return 2;
        }else if(x1 > x && y1 > y){
            if(XGreaterThanY)
                return 3;
            else
                return 0;
            //第四象限
        }else if(x1 < x && y1 > y){
            if(XGreaterThanY)
                return 1;
            else
                return 0;
            //第三象限
        }else if(x1 < x && y1 < y){
            //第二象限
            if(XGreaterThanY)
                return 1;
            else
                return 2;
        }
        return -1;
    }


    private boolean IsPointInCircle(float x1,float y1)
    {
        int width = getWidth();
        int height = getHeight();
        int radius = Math.min(width,height);
        //到圆心的距离 是否大于半径。半径是R
        //如O(x,y)点圆心,任意一点P(x1,y1) (x-x1)*(x-x1)+(y-y1)*(y-y1)>R*R 那么在圆外 反之在圆内
        float x = width/2.0f;
        float y = height/2.0f;
        float r = radius/2.0f;
        if(!((x - x1)*(x - x1) + (y - y1)*(y - y1) > smallCircleRadius*smallCircleRadius))
            return false;
        else if (!((x - x1)*(x - x1) + (y - y1)*(y - y1) > r*r))
            return true;        //当前点在圆内
        else
            return false;       //当前点在圆外
    }

现在我们的面板画完了,你可以愉快的在你的布局文件中引用它了,如果觉得文章有用,请你点个赞吧

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

baoolong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值