美文网首页UI效果仿写Android自定义View
Android自定义惯性滚动圆盘

Android自定义惯性滚动圆盘

作者: 小白丿丶 | 来源:发表于2017-03-09 23:44 被阅读738次

    前言

    最近公司有个需要需要实现一个圆盘以及能根据手滑动转动,没辙只能自定义一个控件来实现这个功能了,接下来我会分析我的思路以及如何实现,如果有什么好的建议和什么问题的话可以在下方留言我会及时回复的.

    功能

    可以在xml配置自定义控件的圆盘外圆的大小颜色以及内圆的大小和颜色,图片圆环的旋转初始角度以及图片的padding距离,添加了控件的旋转最大速度以及最小速度(来屏蔽点击事件)可以在xml来配置想要显示的图片资源,在转盘上的图片可以添加点击事件。

    效果实现图如下

    QQ20170309-203557-HD.gif

    实现步骤

    • 构造方法添加子控件

    我这边自定义布局继承的为ViewGroup来实现的所以在一开始的构造方法中我们需要先定义一些基本的参数以及添加布局.

    
     public RingView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            //获取自定义控件设置的值
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ringview, 0, 0);
            mMax_Speed = array.getInteger(R.styleable.ringview_max_speed, 300);
            mMin_Speed = array.getInteger(R.styleable.ringview_min_speed, 3);
            outCirWidth = array.getInteger(R.styleable.ringview_out_circle_width, 10);
            intCirWidth = array.getInteger(R.styleable.ringview_in_circle_width, 20);
            mImageAngle = array.getInteger(R.styleable.ringview_image_angle, 15);
            mPadding = array.getInteger(R.styleable.ringview_image_padding, 20);
            mOutColor = array.getColor(R.styleable.ringview_out_circle_color, 0xffEFF0EB);
            mInColor = array.getColor(R.styleable.ringview_in_circle_color, 0xffEFF0EB);
            //获取xml定义的资源文件
            TypedArray mList = context.getResources().obtainTypedArray(array.getResourceId(R.styleable.ringview_list, 0));
            int len = mList.length();
            if (len > 0) {
                for (int i = 0; i < len; i++)
                    mImageList.add(mList.getResourceId(i, 0));
            } else {
                mImageList.add(R.mipmap.ic_launcher);
                mImageList.add(R.mipmap.ic_launcher);
                mImageList.add(R.mipmap.ic_launcher);
            }
            mList.recycle();
            array.recycle();
            mRectF = new RectF();
            mPaint = new Paint();
            imagelogo();
            /**
             *  因为默认不走ondraw 所以设置背景透明
             */
            setBackgroundColor(0x00000000);
        }
    
    
    
    
       
    

    添加子控件到布局

    /**
         * 添加子控件
         */
        private void imagelogo() {
            for (int i = 1; i < mImageList.size() + 1; i++) {
                //新建imageview
                ImageView mImageView = new ImageView(getContext());
                mImageView.setImageResource(mImageList.get(i - 1));
                mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                mImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
                final int finalI = i;
                //添加点击事件
                mImageView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (isCanClick) {
                            ToastUtils.showShortToast(finalI + "   ---");
                            if (mOnLogoItemClick != null)
                                mOnLogoItemClick.onItemClick(view, finalI - 1);
                        }
    
                    }
                });
                //添加view
                addView(mImageView);
            }
            //添加view点击事件
            setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (isCanClick) {
                    }
                }
            });
    
        }
    
    
    • 在onMeasure中测量子布局的尺寸
      @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int childCount = this.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = this.getChildAt(i);
                this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
                child.getMeasuredWidth();
            }
    
        }
    
    
    • 在onLayout中放置子控件位置
      @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (!isChekc) {
                initView();
                mRadius = getWidth();
                isChekc = true;
            }
    
        }
    
         /**
         * 排版布局
         */
        private void initView() {
            int width = this.getWidth();
            int height = this.getHeight();
            if (width != height) {
                int min = Math.min(width, height);
                width = min;
                height = min;
            }
            //图片摆放的圆弧半径
            mCircleLineStrokeWidth = getChildAt(0).getMeasuredHeight() + outCirWidth + mPadding;
            //计算图片圆的半径
            final int mContent = width / 2 - mCircleLineStrokeWidth / 2;
            for (int i = 0; i < getChildCount(); i++) {
                View child = this.getChildAt(i);
                //计算每个图片摆放的角度
                int mAnGle = 360 / mImageList.size() * (i + 1) + mImageAngle;
                //获取每个图片摆放的左上角的x和y坐标
                float left = (float) (width / 2 + mContent * Math.cos(mAnGle * Math.PI / 180)) - child.getMeasuredWidth() / 2;
                float top = (float) (height / 2 + mContent * Math.sin(mAnGle * Math.PI / 180)) - child.getMeasuredHeight() / 2;
                /**
                 * 一四象限
                 */
                if (getQuadrantByAngle(mAnGle) == 1 || getQuadrantByAngle(mAnGle) == 4) {
                    child.setRotation(mAnGle - 270);
                    /**
                     * 二三象限
                     */
                } else {
                    child.setRotation(mAnGle + 90);
                }
                child.layout((int) left, (int) top, (int) left + child.getMeasuredWidth(), (int) top + child.getMeasuredHeight());
            }
        }
    
    
    • 在onDraw中画内圆外圆以及圆环

    由于公司需求需要在控件中划入外圆和内圆以及放图片控件的圆环所以我在onDraw方法中绘制对应的圆以及圆环

     @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            int width = this.getWidth();
            int height = this.getHeight();
    
            if (width != height) {
                int min = Math.min(width, height);
                width = min;
                height = min;
            }
            // 设置画笔相关属性
            mPaint.setAntiAlias(true);
            mPaint.setColor(0xfffafafa);
            //设置画布颜色
            canvas.drawColor(Color.TRANSPARENT);
            //设置圆环宽度
            mPaint.setStrokeWidth(mCircleLineStrokeWidth);
            //设置为空心圆
            mPaint.setStyle(Paint.Style.STROKE);
            // 设置位置
            mRectF.left = mCircleLineStrokeWidth / 2; // 左上角x
            mRectF.top = mCircleLineStrokeWidth / 2; // 左上角y
            mRectF.right = width - mCircleLineStrokeWidth / 2; // 左下角x
            mRectF.bottom = height - mCircleLineStrokeWidth / 2; // 右下角y
            //画三个弧形
            mPaint.setColor(0xfff89326);
            canvas.drawArc(mRectF, -90, 120, false, mPaint);
            mPaint.setColor(0xff4daae7);
            canvas.drawArc(mRectF, 30, 120, false, mPaint);
            mPaint.setColor(0xff687df2);
            canvas.drawArc(mRectF, 150, 120, false, mPaint);
            //每根线的弧长
            int mVale = (15 / (int) (width / 2 * Math.PI / 180));
            //画三条分割线线
            mPaint.setColor(0xfffafafa);
            canvas.drawArc(mRectF, -90, mVale, false, mPaint);
            canvas.drawArc(mRectF, 30, mVale, false, mPaint);
            canvas.drawArc(mRectF, 150, mVale, false, mPaint);
            //画外圆
            mPaint.setColor(mOutColor);
            mPaint.setStrokeWidth(outCirWidth);
            canvas.drawCircle(width / 2, width / 2, (width - outCirWidth) / 2, mPaint);
            //画内圆
            mPaint.setStrokeWidth(intCirWidth);
            mPaint.setColor(mInColor);
            canvas.drawCircle(width / 2, width / 2, width / 2 - mCircleLineStrokeWidth, mPaint);
            // drawLogo(canvas);
        }
    

    我这边还提供了在canvas上画图标的方法暂时没点击事件所以不推荐

     /**
         * 用canvas 画图标
         *
         * @param canvas
         */
        private void drawLogo(Canvas canvas) {
            int width = this.getWidth();
            int height = this.getHeight();
            if (width != height) {
                int min = Math.min(width, height);
                width = min;
                height = min;
            }
            //图标半径
            int mContent = getWidth() / 2 - mCircleLineStrokeWidth / 2;
            //直接画图标
            for (int i = 1; i < mImageList.size(); i++) {
                Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),
                        mImageList.get(i));
                //获取圆点上 xy坐标 mAngle 为图片圆半径和大圆半径差值
                float x = (float) (width / 2 + mContent * Math.cos((360 / mImageList.size() * (i + 1) + mImageAngle) * Math.PI / 180)) - bitmap.getWidth() / 2;
                float y = (float) (height / 2 + mContent * Math.sin((360 / mImageList.size() * (i + 1) + mImageAngle) * Math.PI / 180)) - bitmap.getHeight() / 2;
                canvas.drawBitmap(bitmap, null, new RectF(x, y, x + bitmap.getWidth(), y + bitmap.getHeight()), new Paint());
                bitmap.recycle();
            
    
    • 滑动圆盘

    我这边根据手指滑动算出手指滑动的角度以及速度来根据角度滚动布局以及惯性滑动

       /**
         * 触摸监听
         */
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            float x = event.getX();
            float y = event.getY();
    
    
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
    
                    mLastX = x;
                    mLastY = y;
                    mDownTime = System.currentTimeMillis();
                    mTmpAngle = 0;
    
                    // 如果当前已经在快速滚动
                    if (isMove) {
                        // 移除快速滚动的回调
                        removeCallbacks(mAngleRunnable);
                        isMove = false;
                        return true;
                    }
    
                    break;
                case MotionEvent.ACTION_MOVE:
                    /**
                     * 获得开始的角度
                     */
                    float start = getAngle(mLastX, mLastY);
                    /**
                     * 获得当前的角度
                     */
                    float end = getAngle(x, y);
                    Log.e("TAG", "start = " + start + " , end =" + end);
                    // 一四象限
                    if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4) {
                        mStartAngle += end - start;
                        mTmpAngle += end - start;
                        //二三象限
                    } else {
                        mStartAngle += start - end;
                        mTmpAngle += start - end;
                    }
                    // 重新布局
                    getCheck();
    
                    break;
                case MotionEvent.ACTION_UP:
                    // 获取每秒移动的角度
                    float anglePerSecond = mTmpAngle * 1000
                            / (System.currentTimeMillis() - mDownTime);
                    // 如果达到最大速度
                    if (Math.abs(anglePerSecond) > mMax_Speed && !isMove) {
                        // 惯性滚动
                        post(mAngleRunnable = new AngleRunnable(anglePerSecond));
                        return true;
                    }
    
                    // 如果当前旋转角度超过minSpeed屏蔽点击
                    if (Math.abs(mTmpAngle) > mMin_Speed) {
                        return true;
                    }
    
                    break;
            }
            return super.dispatchTouchEvent(event);
        }
    
    
    
        /**
         * 惯性滚动
         */
        private class AngleRunnable implements Runnable {
    
            private float angelPerSecond;
    
            public AngleRunnable(float velocity) {
                this.angelPerSecond = velocity;
            }
    
            public void run() {
                //小于20停止
                if ((int) Math.abs(angelPerSecond) < 20) {
                    isMove = false;
                    return;
                }
                isMove = true;
                // 滚动时候不断修改滚动角度大小
                mStartAngle += (angelPerSecond / 30);
                //逐渐减小这个值
                angelPerSecond /= 1.0666F;
                postDelayed(this, 30);
                // 重新布局
                getCheck();
            }
        }
    
    
        /**
         * 旋转圆盘
         */
        private void getCheck() {
            mStartAngle %= 360;
            setRotation(mStartAngle);
        }
    
    • 部分计算方法
    
    
     /**
         * 获取移动的角度
         */
        private float getAngle(float xTouch, float yTouch) {
            double x = xTouch - (mRadius / 2d);
            double y = yTouch - (mRadius / 2d);
            return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
        }
    
    /**
         * 根据当前位置计算象限
         */
        private int getQuadrant(float x, float y) {
            int tmpX = (int) (x - mRadius / 2);
            int tmpY = (int) (y - mRadius / 2);
            if (tmpX >= 0) {
                return tmpY >= 0 ? 4 : 1;
            } else {
                return tmpY >= 0 ? 3 : 2;
            }
    
        }
    
    
        /**
         * 通过角度判断象限
         */
        private int getQuadrantByAngle(int angle) {
            if (angle <= 90) {
                return 4;
            } else if (angle <= 180) {
                return 3;
            } else if (angle <= 270) {
                return 2;
            } else {
                return 1;
            }
        }
    

    完整代码

    JAVA代码

    /**
     * Created by huangb on 2017/3/6.
     * 圆盘 惯性滑动以及手动滑动
     */
    
    public class RingView extends ViewGroup {
        /**
         * //上一次滑动的坐标
         */
        private float mLastX;
        private float mLastY;
        /**
         * 检测按下到抬起时使用的时间
         */
        private long mDownTime;
        /**
         * 自动滚动线程
         */
        private AngleRunnable mAngleRunnable;
        /**
         * 检测按下到抬起时旋转的角度
         */
        private float mTmpAngle;
        /**
         * 每秒最大移动角度
         */
        private int mMax_Speed;
        /**
         * 如果移动角度达到该值,则屏蔽点击
         */
        private int mMin_Speed;
        /**
         * 圆的直径
         */
        private int mRadius;
        /**
         * 判断是否正在自动滚动
         */
        private boolean isMove;
        /**
         * 布局滚动角度
         */
        private int mStartAngle = 0;
        /**
         * 中间条的宽度
         */
        private int mCircleLineStrokeWidth;
        /**
         * 画圆所在的距形区域
         */
        private final RectF mRectF;
        /**
         * 画笔
         */
        private final Paint mPaint;
        /**
         * 外侧圆宽度
         */
        private int outCirWidth;
        /**
         * 内侧圆宽度
         */
        private int intCirWidth;
        /**
         * 图片内容偏移角度
         */
        private int mImageAngle;
        /**
         * 是否初始化布局
         */
        private boolean isChekc = false;
        /**
         * 外圆颜色
         */
        private int mOutColor;
        /**
         * 内圆颜色
         */
        private int mInColor;
        /**
         * 布局view
         */
        private List<Integer> mImageList = new ArrayList<>();
        /**
         * 是否可点击
         */
        private boolean isCanClick = true;
    
        /**
         * 图片与环之间的padding
         */
        private int mPadding;
    
        //是否能转动
        private boolean mCanScrool;
    
        public RingView(Context context) {
            this(context, null, 0);
    
        }
    
        public RingView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
    
        }
    
        public RingView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            //获取自定义控件设置的值
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ringview, 0, 0);
            mMax_Speed = array.getInteger(R.styleable.ringview_max_speed, 300);
            mMin_Speed = array.getInteger(R.styleable.ringview_min_speed, 3);
            outCirWidth = array.getInteger(R.styleable.ringview_out_circle_width, 10);
            intCirWidth = array.getInteger(R.styleable.ringview_in_circle_width, 20);
            mImageAngle = array.getInteger(R.styleable.ringview_image_angle, 0);
            mPadding = array.getInteger(R.styleable.ringview_image_padding, 0);
            mOutColor = array.getColor(R.styleable.ringview_out_circle_color, 0xffEFF0EB);
            mInColor = array.getColor(R.styleable.ringview_in_circle_color, 0xffEFF0EB);
            mCanScrool = array.getBoolean(R.styleable.ringview_can_scroll, true);
            //获取xml定义的资源文件
            TypedArray mList = context.getResources().obtainTypedArray(array.getResourceId(R.styleable.ringview_list, 0));
            int len = mList.length();
            if (len > 0) {
                for (int i = 0; i < len; i++)
                    mImageList.add(mList.getResourceId(i, 0));
            } else {
                mImageList.add(R.mipmap.ic_launcher);
                mImageList.add(R.mipmap.ic_launcher);
                mImageList.add(R.mipmap.ic_launcher);
            }
            mList.recycle();
            array.recycle();
            mRectF = new RectF();
            mPaint = new Paint();
            imagelogo();
            /**
             *  因为默认不走ondraw 所以设置背景透明
             */
            setBackgroundColor(0x00000000);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (!isChekc) {
                initView();
                mRadius = getWidth();
                isChekc = true;
            }
    
        }
    
        /**
         * 测量
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int childCount = this.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = this.getChildAt(i);
                this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
                child.getMeasuredWidth();
            }
    
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            int width = this.getWidth();
            int height = this.getHeight();
    
            if (width != height) {
                int min = Math.min(width, height);
                width = min;
                height = min;
            }
            // 设置画笔相关属性
            mPaint.setAntiAlias(true);
            mPaint.setColor(0xfffafafa);
            //设置画布颜色
            canvas.drawColor(Color.TRANSPARENT);
            //设置圆环宽度
            mPaint.setStrokeWidth(mCircleLineStrokeWidth);
            //设置为空心圆
            mPaint.setStyle(Paint.Style.STROKE);
            // 设置位置
            mRectF.left = mCircleLineStrokeWidth / 2; // 左上角x
            mRectF.top = mCircleLineStrokeWidth / 2; // 左上角y
            mRectF.right = width - mCircleLineStrokeWidth / 2; // 左下角x
            mRectF.bottom = height - mCircleLineStrokeWidth / 2; // 右下角y
            //画三个弧形
            mPaint.setColor(0xfff89326);
            canvas.drawArc(mRectF, -90, 120, false, mPaint);
            mPaint.setColor(0xff4daae7);
            canvas.drawArc(mRectF, 30, 120, false, mPaint);
            mPaint.setColor(0xff687df2);
            canvas.drawArc(mRectF, 150, 120, false, mPaint);
            //每根线的弧长
            int mVale = (15 / (int) (width / 2 * Math.PI / 180));
            //画三条分割线线
            mPaint.setColor(0xfffafafa);
            canvas.drawArc(mRectF, -90, mVale, false, mPaint);
            canvas.drawArc(mRectF, 30, mVale, false, mPaint);
            canvas.drawArc(mRectF, 150, mVale, false, mPaint);
            //画外圆
            mPaint.setColor(mOutColor);
            mPaint.setStrokeWidth(outCirWidth);
            canvas.drawCircle(width / 2, width / 2, (width - outCirWidth) / 2, mPaint);
            //画内圆
            mPaint.setStrokeWidth(intCirWidth);
            mPaint.setColor(mInColor);
            canvas.drawCircle(width / 2, width / 2, width / 2 - mCircleLineStrokeWidth, mPaint);
            // drawLogo(canvas);
        }
    
    
        /**
         * 用canvas 画图标
         *
         * @param canvas
         */
        private void drawLogo(Canvas canvas) {
            int width = this.getWidth();
            int height = this.getHeight();
            if (width != height) {
                int min = Math.min(width, height);
                width = min;
                height = min;
            }
            //图标半径
            int mContent = getWidth() / 2 - mCircleLineStrokeWidth / 2;
            //直接画图标
            for (int i = 1; i < mImageList.size(); i++) {
                Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),
                        mImageList.get(i));
                //获取圆点上 xy坐标 mAngle 为图片圆半径和大圆半径差值
                float x = (float) (width / 2 + mContent * Math.cos((360 / mImageList.size() * (i + 1) + mImageAngle) * Math.PI / 180)) - bitmap.getWidth() / 2;
                float y = (float) (height / 2 + mContent * Math.sin((360 / mImageList.size() * (i + 1) + mImageAngle) * Math.PI / 180)) - bitmap.getHeight() / 2;
                canvas.drawBitmap(bitmap, null, new RectF(x, y, x + bitmap.getWidth(), y + bitmap.getHeight()), new Paint());
                bitmap.recycle();
            }
        }
    
        /**
         * 排版布局
         */
        private void initView() {
            int width = this.getWidth();
            int height = this.getHeight();
            if (width != height) {
                int min = Math.min(width, height);
                width = min;
                height = min;
            }
            //图片摆放的圆弧半径
            mCircleLineStrokeWidth = getChildAt(0).getMeasuredHeight() + outCirWidth + mPadding;
            //计算图片圆的半径
            final int mContent = width / 2 - mCircleLineStrokeWidth / 2;
            for (int i = 0; i < getChildCount(); i++) {
                View child = this.getChildAt(i);
                //计算每个图片摆放的角度
                int mAnGle = 360 / mImageList.size() * (i + 1) + mImageAngle;
                //获取每个图片摆放的左上角的x和y坐标
                float left = (float) (width / 2 + mContent * Math.cos(mAnGle * Math.PI / 180)) - child.getMeasuredWidth() / 2;
                float top = (float) (height / 2 + mContent * Math.sin(mAnGle * Math.PI / 180)) - child.getMeasuredHeight() / 2;
                /**
                 * 一四象限
                 */
                if (getQuadrantByAngle(mAnGle) == 1 || getQuadrantByAngle(mAnGle) == 4) {
                    child.setRotation(mAnGle - 270);
                    /**
                     * 二三象限
                     */
                } else {
                    child.setRotation(mAnGle + 90);
                }
                child.layout((int) left, (int) top, (int) left + child.getMeasuredWidth(), (int) top + child.getMeasuredHeight());
            }
        }
    
    
        /**
         * 添加子控件
         */
        private void imagelogo() {
            for (int i = 1; i < mImageList.size() + 1; i++) {
                //新建imageview
                ImageView mImageView = new ImageView(getContext());
                mImageView.setImageResource(mImageList.get(i - 1));
                mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                mImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
                final int finalI = i;
                //添加点击事件
                mImageView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (isCanClick) {
                            ToastUtils.showShortToast(finalI + "   ---");
                            if (mOnLogoItemClick != null)
                                mOnLogoItemClick.onItemClick(view, finalI - 1);
                        }
    
                    }
                });
                //添加view
                addView(mImageView);
            }
            //添加view点击事件
            setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (isCanClick) {
                    }
                }
            });
    
        }
    
        /**
         * 触摸监听
         */
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            if (mCanScrool) {
                float x = event.getX();
                float y = event.getY();
    
    
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
    
                        mLastX = x;
                        mLastY = y;
                        mDownTime = System.currentTimeMillis();
                        mTmpAngle = 0;
    
                        // 如果当前已经在快速滚动
                        if (isMove) {
                            // 移除快速滚动的回调
                            removeCallbacks(mAngleRunnable);
                            isMove = false;
                            return true;
                        }
    
                        break;
                    case MotionEvent.ACTION_MOVE:
                        /**
                         * 获得开始的角度
                         */
                        float start = getAngle(mLastX, mLastY);
                        /**
                         * 获得当前的角度
                         */
                        float end = getAngle(x, y);
                        Log.e("TAG", "start = " + start + " , end =" + end);
                        // 一四象限
                        if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4) {
                            mStartAngle += end - start;
                            mTmpAngle += end - start;
                            //二三象限
                        } else {
                            mStartAngle += start - end;
                            mTmpAngle += start - end;
                        }
                        // 重新布局
                        getCheck();
    
                        break;
                    case MotionEvent.ACTION_UP:
                        // 获取每秒移动的角度
                        float anglePerSecond = mTmpAngle * 1000
                                / (System.currentTimeMillis() - mDownTime);
                        // 如果达到最大速度
                        if (Math.abs(anglePerSecond) > mMax_Speed && !isMove) {
                            // 惯性滚动
                            post(mAngleRunnable = new AngleRunnable(anglePerSecond));
                            return true;
                        }
    
                        // 如果当前旋转角度超过minSpeed屏蔽点击
                        if (Math.abs(mTmpAngle) > mMin_Speed) {
                            return true;
                        }
    
                        break;
                }
            }
            return super.dispatchTouchEvent(event);
        }
    
        /**
         * 获取移动的角度
         */
        private float getAngle(float xTouch, float yTouch) {
            double x = xTouch - (mRadius / 2d);
            double y = yTouch - (mRadius / 2d);
            return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
        }
    
        /**
         * 根据当前位置计算象限
         */
        private int getQuadrant(float x, float y) {
            int tmpX = (int) (x - mRadius / 2);
            int tmpY = (int) (y - mRadius / 2);
            if (tmpX >= 0) {
                return tmpY >= 0 ? 4 : 1;
            } else {
                return tmpY >= 0 ? 3 : 2;
            }
    
        }
    
    
        /**
         * 通过角度判断象限
         */
        private int getQuadrantByAngle(int angle) {
            if (angle <= 90) {
                return 4;
            } else if (angle <= 180) {
                return 3;
            } else if (angle <= 270) {
                return 2;
            } else {
                return 1;
            }
        }
    
        /**
         * 惯性滚动
         */
        private class AngleRunnable implements Runnable {
    
            private float angelPerSecond;
    
            public AngleRunnable(float velocity) {
                this.angelPerSecond = velocity;
            }
    
            public void run() {
                //小于20停止
                if ((int) Math.abs(angelPerSecond) < 20) {
                    isMove = false;
                    return;
                }
                isMove = true;
                // 滚动时候不断修改滚动角度大小
                mStartAngle += (angelPerSecond / 30);
                //逐渐减小这个值
                angelPerSecond /= 1.0666F;
                postDelayed(this, 30);
                // 重新布局
                getCheck();
            }
        }
    
    
        /**
         * 点击事件接口
         */
        public interface OnLogoItemClick {
            void onItemClick(View view, int pos);
        }
    
        private OnLogoItemClick mOnLogoItemClick;
    
        /**
         * 设置点击事件
         *
         * @param mOnLogoItemClick
         */
        public void addOnItemClick(OnLogoItemClick mOnLogoItemClick) {
            this.mOnLogoItemClick = mOnLogoItemClick;
        }
    
    
        /**
         * 旋转圆盘
         */
        private void getCheck() {
            mStartAngle %= 360;
            setRotation(mStartAngle);
        }
    
        /**
         * 设置是否可点击
         */
        public void setCanClick(boolean canClick) {
            isCanClick = canClick;
        }
    
    }
    

    自定义控件attrs.xml文件配置

    外圆宽度:out_circle_width
    内圆宽度:in_circle_width
    外圆颜色:out_circle_color
    内圆颜色:in_circle_color
    图片偏移角度:image_angle
    图片padding:image_padding
    最大速度:max_speed
    判断是否点击最小速度:min_speed
    资源数组:list
    是否能滚动:can_scroll

     <declare-styleable name="ringview">
            <attr name="out_circle_width" format="integer" />
            <attr name="in_circle_width" format="integer" />
            <attr name="out_circle_color" format="color" />
            <attr name="in_circle_color" format="color" />
            <attr name="image_angle" format="integer" />
            <attr name="image_padding" format="integer" />
            <attr name="max_speed" format="integer" />
            <attr name="min_speed" format="integer" />
            <attr name="list" format="integer" />
            <attr name="can_scroll" format="boolean" />
        </declare-styleable>
    

    XML示例代码

      <com.ddinfo.businesspay.view.RingView
                android:id="@+id/ringView"
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:layout_centerInParent="true"
                app:image_angle="15"
                app:image_padding="20"
                app:list="@array/logo_list" />
    
    

    arrays.xml示例

      <string-array name="logo_list">
            <item>@mipmap/logo_5</item>
            <item>@mipmap/logo_6</item>
            <item>@mipmap/logo_7</item>
            <item>@mipmap/logo_8</item>
            <item>@mipmap/logo_9</item>
            <item>@mipmap/logo_10</item>
            <item>@mipmap/logo_11</item>
            <item>@mipmap/logo_12</item>
            <item>@mipmap/logo_1</item>
            <item>@mipmap/logo_2</item>
            <item>@mipmap/logo_3</item>
            <item>@mipmap/logo_4</item>
        </string-array>
    

    引用

    在自定义过程中使用了一些工具类方法我这边导入了超级好用的工具类大全AndroidUtilCode

    结语

    该自定义控件如果有什么需求不满足,或者有什么问题的话各位大佬的话可以在评论区告知小生这厢有礼了,如果可以的话给个关注鼓励小生以后再次分享些东西给大家谢谢~

    相关文章

      网友评论

      本文标题:Android自定义惯性滚动圆盘

      本文链接:https://www.haomeiwen.com/subject/lqzggttx.html