自定义逐格走旋转圆盘

  1. 前言

    最近在不断学习自定义控件,项目中有个旋转圆盘的需求,需要一个刻度一个刻度的走,网上没发现有符合要求的,遂自己实现一下,先看下效果(录完才发现没有触摸轨迹,,前面旋转的是手滑动的,大家懂哈~)

    这里写图片描述

  2. 需求
    1,圆盘可控制顺时针与逆时针旋转,圆盘中间需要设置图片
    2,旋转需要按一个刻度一个刻度的走,具有跳跃性
    3,需要提供顺时针与逆时针旋转的方法,以及可以禁止与开启旋转

  3. 实现
    首先当然是继承View啦,这里偷懒只定义了一个属性,用来设置圆中的图片

<resources>
    <declare-styleable name="ControlableCircleView">
        <attr name="circle_bg" format="reference"/>
    </declare-styleable>
</resources>

控件中获取资源

    private void initAttrs(AttributeSet attrs) {
        TypedArray attribute = getContext().obtainStyledAttributes(attrs, R.styleable.ControlableCircleView);
        Drawable bg = attribute.getDrawable(R.styleable.ControlableCircleView_circle_bg);
        if (null != bg)
            bg_bitmap = drawableToBitmap(bg);
        attribute.recycle();
    }

在onMeasure方法中初始化宽高以及指针长度、大圆半径和背景圆半径

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        //长短指针所在的圆半径
        mClockRadio = Math.min(mWidth, mHeight) / 2 - mLongClockLine;
        //背景图片圆半径
        mBackRadio = mClockRadio - 20;
    }

然后再onDraw中画刻度和图片,画刻度是通过旋转画布实现的,需要计算出每一格的角度

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!mHasInit) {
            mClockPaint = new Paint();
            mClockPaint.setAntiAlias(true);
            mClockPaint.setStrokeWidth(3);
            mClockPaint.setColor(mPaintColor);

            mPreAngle = 360f / mClockNum;
            mHasInit = true;
        }
        //画背景图片
        drawBg(canvas);
        canvas.rotate(touchRotate, mWidth / 2, mHeight / 2);
        //画长短指针
        for (int i = 0; i < mClockNum; i++) {
            if (i % 2 == 0)
                canvas.drawLine(mWidth / 2, mHeight / 2 - mClockRadio, mWidth / 2, mHeight / 2 - mClockRadio - mLongClockLine, mClockPaint);
            else
                canvas.drawLine(mWidth / 2, mHeight / 2 - mClockRadio, mWidth / 2, mHeight / 2 - mClockRadio - mLongClockLine / 2, mClockPaint);
            canvas.rotate(mPreAngle, mWidth / 2, mHeight / 2);
        }
    }

这里是上面的drawBg方法,用来画圆形图片,通过画布交叉叠加的方式获得圆形bitmap,在头像显示中经常用到

    /**
     * 画背景图片
     *
     * @param canvas
     */
    private void drawBg(Canvas canvas) {
        if (null != bg_bitmap) {
            Bitmap squareBitmap;
            Bitmap scaledSrcBmp;
            int bgWidth = bg_bitmap.getWidth();
            int bgHeight = bg_bitmap.getHeight();
            int scaleWidth = 0, scaleHeight = 0;
            int x = 0, y = 0;
            if (bgWidth > bgHeight) {
                scaleWidth = scaleHeight = bgHeight;
                x = (bgWidth - bgHeight) / 2;
                y = 0;
                squareBitmap = Bitmap.createBitmap(bg_bitmap, x, y, scaleWidth, scaleHeight);
            } else if (bgWidth < bgHeight) {
                scaleWidth = scaleHeight = bgWidth;
                y = (bgHeight - bgWidth) / 2;
                x = 0;
                squareBitmap = Bitmap.createBitmap(bg_bitmap, x, y, scaleWidth, scaleHeight);
            } else {
                squareBitmap = bg_bitmap;
            }

            if (squareBitmap.getWidth() != mBackRadio * 2 || squareBitmap.getHeight() != mBackRadio * 2) {
                scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, (int) mBackRadio * 2,
                        (int) mBackRadio * 2, true);
            } else {
                scaledSrcBmp = squareBitmap;
            }

            Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(),
                    scaledSrcBmp.getHeight(), Bitmap.Config.ARGB_8888);
            Canvas bgCanvas = new Canvas(output);

            Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight());
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setFilterBitmap(true);
            paint.setDither(true);
            bgCanvas.drawARGB(0, 0, 0, 0);
            bgCanvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint);
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
            bgCanvas.drawBitmap(scaledSrcBmp, rect, rect, paint);

            //画圆形背景
            final Rect rectSrc = new Rect(0, 0, output.getWidth(), output.getHeight());
            final Rect rectDest = new Rect((int) (mWidth / 2 - mBackRadio), (int) (mHeight / 2 - mBackRadio),
                    (int) (mWidth / 2 + mBackRadio), (int) (mHeight / 2 + mBackRadio));
            mBgPaint = new Paint();
            canvas.drawBitmap(output, rectSrc, rectDest, mBgPaint);
        }
    }

对了,获取图片属性中使用了一个drawable转bitmap的方法,这里也贴一下

    /**
     * drawable转bitmap
     *
     * @param drawable
     * @return
     */
    public Bitmap drawableToBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(
                drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(),
                drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
                        : Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(bitmap);
        //canvas.setBitmap(bitmap);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);
        return bitmap;
    }

然后就是处理触摸事件了,这里应该是最好玩的地方了,在这里才可以让它鲜活起来嘛。当手指触摸滑动后,计算手指落下点、圆心和滑动到的点三点的夹角,根据正负判断顺时针还是逆时针。注意这里有个坑,因为刻度值是长短相间的,当目前处于短刻度时,旋转一个刻度处于长刻度,显示的没问题,但是当再次旋转一个刻度时,你会发现圆盘没有变化,这是因为每次绘画刻度时都是从长刻度开始画的,所以旋转时需要增加(顺时针)或者减少(逆时针)一个刻度与两个刻度相间的方法来显示效果。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mForbitRotate)
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downX = event.getX();
                    downY = event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    movingX = event.getX();
                    movingY = event.getY();
                    //计算下落点、圆心、移动点三点间的夹角
                    float angle = calcAngle(downX, downY, mWidth / 2, mHeight / 2, movingX, movingY) / 18;
                    if (Math.abs(angle) >= mPreAngle) {
                        if (angle > 0) {
                            if (touchRotate / mPreAngle % 2 == 0)
                                touchRotate = mPreAngle;
                            else
                                touchRotate = 2 * mPreAngle;
                        } else {
                            if (touchRotate / mPreAngle % 2 == 0)
                                touchRotate = -mPreAngle;
                            else
                                touchRotate = -2 * mPreAngle;
                        }
                        //旋转监听
                        if (null != mOnRotateListener) {
                            if (angle > 0 && movingY < mHeight / 2 || angle < 0 && movingY > mHeight / 2)
                                mOnRotateListener.onRotateRight(1);
                            else
                                mOnRotateListener.onRotateLeft(1);
                        }

                        downX = movingX;
                        downY = movingY;

                        invalidate();
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    break;
            }

        return super.onTouchEvent(event);
    }

为了计算角度又重学了一遍三角函数,毕竟以前学的都还给老师了…

    /**
     * 计算三个点的夹角
     *
     * @param p1X
     * @param p1Y
     * @param centerX
     * @param centerY
     * @param p2X
     * @param p2Y
     * @return
     */
    private float calcAngle(float p1X, float p1Y, float centerX, float centerY, float p2X, float p2Y) {
        float dis1 = calcDisBetweenPoint(p1X, p1Y, centerX, centerY);
        float sin1 = (centerX - p1X) / dis1;
        float dis2 = calcDisBetweenPoint(centerX, centerY, p2X, p2Y);
        float sin2 = (p2X - centerX) / dis2;
        return (float) ((Math.asin(sin1) + Math.asin(sin2)) / 2 * Math.PI * 360);
    }

    /**
     * 计算两个点中间的距离
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return
     */
    private float calcDisBetweenPoint(float x1, float y1, float x2, float y2) {
        float disX = Math.abs(x1 - x2);
        float disY = Math.abs(y1 - y2);
        return (float) Math.sqrt(disX * disX + disY * disY);
    }

你会发现上面添加了监听,没错,有监听才有存在的意义嘛,下面是一些方法和监听初始化

    /**
     * 向左旋转一格
     */
    public void rotateLeft() {
        if (!mForbitRotate) {
            if (touchRotate / mPreAngle % 2 == 0)
                touchRotate = -mPreAngle;
            else
                touchRotate = -2 * mPreAngle;
            if (null != mOnRotateListener)
                mOnRotateListener.onRotateLeft(1);
            invalidate();
        }
    }

    /**
     * 向右旋转一格
     */
    public void rotateRight() {
        if (!mForbitRotate) {
            if (touchRotate / mPreAngle % 2 == 0)
                touchRotate = mPreAngle;
            else
                touchRotate = 2 * mPreAngle;
            if (null != mOnRotateListener)
                mOnRotateListener.onRotateRight(1);
            invalidate();
        }
    }


    /**
     * 设置是否禁止旋转
     *
     * @param forbitRotate
     */
    public void setForbitRotate(boolean forbitRotate) {
        mForbitRotate = forbitRotate;
    }


    /**
     * 获取控件是否禁止了旋转
     *
     * @return
     */
    public boolean getForbitRotate() {
        return mForbitRotate;
    }


    //设置滚动监听
    private onRotateListener mOnRotateListener;

    public interface onRotateListener {
        void onRotateLeft(int num);

        void onRotateRight(int num);
    }

    public void setOnRotate(onRotateListener onRotateListener) {
        mOnRotateListener = onRotateListener;
    }

到此就完事了,控件的一些变量都没有提供属性设置,都是很简单的步骤,大家有需求的可以自己添加一下,使之适用性更强。觉得有用就赞一下下啦

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值