《Android自定义控件》TurntableView,抽奖转盘

转载请标明出处:https://blog.csdn.net/m0_38074457/article/details/86433483 ,本文出自【陈少华的博客】

一、效果图

效果图

二、实现步骤

三、代码实现

1、attrs.xml中添加自定义控件的属性

 <declare-styleable name="TurntableView">
        <!--转盘的item数量-->
        <attr name="pannum" format="integer" />
        <!--转盘的item的文本-->
        <attr name="names" format="reference" />
        <!--转盘的本地图像地址-->
        <attr name="icons" format="reference" />
        <!--转盘背景的颜色-->
        <attr name="colors" format="reference" />
        <!--控件宽占屏幕宽的比例-->
        <attr name="percentage" format="float" />
    </declare-styleable>

2、控件的构造方法

 public TurntableView(Context context) {
        this(context, null);
    }

    public TurntableView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TurntableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

initView方法中初始化控件的值

/**
     * 初始化view
     *
     * @param context
     * @param attrs
     */
    private void initView(Context context, AttributeSet attrs) {
        //手势识别对象
        mDetector = new GestureDetectorCompat(context, new TurntableGestureListener());
        //抗锯齿
        mPaint.setAntiAlias(true);

        mScreenHeight = getResources().getDisplayMetrics().heightPixels;
        mScreenWidth = getResources().getDisplayMetrics().widthPixels;

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TurntableView);
        if (array != null) {
            mPanNum = array.getInteger(R.styleable.TurntableView_pannum, 8);
            int colorsId = array.getResourceId(R.styleable.TurntableView_colors, R.array.colors);
            int namesArray = array.getResourceId(R.styleable.TurntableView_names, R.array.names);
            int iconsArray = array.getResourceId(R.styleable.TurntableView_icons, R.array.icons);
            mPercentage = array.getFloat(R.styleable.TurntableView_percentage, (float) 0.75);
            int[] colors = context.getResources().getIntArray(colorsId);
            String[] namesStrs = context.getResources().getStringArray(namesArray);
            String[] iconsStrs = context.getResources().getStringArray(iconsArray);

            for (int i = 0; i < colors.length; i++) {
                mColors.add(colors[i]);
            }

            for (int i = 0; i < namesStrs.length; i++) {
                mNamesStrs.add(namesStrs[i]);
            }
            List<Integer> iconLists = new ArrayList<>();
            for (int i = 0; i < iconsStrs.length; i++) {
                iconLists.add(context.getResources().getIdentifier(iconsStrs[i], "mipmap", context.getPackageName()));
            }
            for (int i = 0; i < iconLists.size(); i++) {
                Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), iconLists.get(i));
                mBitmaps.add(bitmap);
            }
            array.recycle();
        }
    }

3、onMeasure和onSizeChanged控制控件和转盘的大小

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //控制view的宽为屏幕宽的mPercentage
        int viewWid = (int) ((float) mScreenWidth * mPercentage);
        setMeasuredDimension(viewWid, viewWid);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWid = w;
        mHei = h;

        //让转盘的宽为view的宽,半径为view的宽的一半
        mCenterX = mWid / 2;
        mCenterY = mCenterX;
        mRadius = mWid / 2;

        mOffsetAngle = (float) 360 / (float) mPanNum;
    }

4、onDraw来绘制转盘

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画圆的背景颜色
        drawBackground(canvas);
        //画图片
        drawImage(canvas);
        //画文本
        drawText(canvas);
    }
 /**
     * 画圆的整体背景
     *
     * @param canvas
     */
    private void drawBackground(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        RectF rectF = new RectF(0, 0, mWid, mHei);

        float angle = mCurrentAngle;
        for (int i = 0; i < mPanNum; i++) {
            int yushu = i % mColors.size();
            mPaint.setColor(mColors.get(yushu));
            canvas.drawArc(rectF, angle, mOffsetAngle, true, mPaint);
            angle = angle + mOffsetAngle;
        }
    }

    /**
     * 画图像
     *
     * @param canvas
     */
    private void drawImage(Canvas canvas) {
        //绘制图片开始的角度位置
        float radian = mCurrentAngle + mOffsetAngle / (float) 2;
        //使图像的宽度的一半为半径的1/7
        float imageOffset = (float) mRadius / (float) 7;
        for (int i = 0; i < mBitmaps.size(); i++) {
            //计算图片中心位置的坐标
            //Math.toRadians  是为了提高计算精度
            float x = (float) (mCenterX + (float) mRadius * (float) 0.6 * Math.cos(Math.toRadians(radian)));
            float y = (float) (mCenterY + (float) mRadius * (float) 0.6 * Math.sin(Math.toRadians(radian)));

            RectF rectF = new RectF(x - imageOffset, y - imageOffset, x + imageOffset, y + imageOffset);
            canvas.drawBitmap(mBitmaps.get(i), null, rectF, mPaint);
            radian = radian + mOffsetAngle;
        }
    }

    /**
     * 画文本
     *
     * @param canvas
     */
    private void drawText(Canvas canvas) {
        mPaint.setColor(Color.WHITE);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(30);
        RectF rectF = new RectF(0, 0, mWid, mHei);

        //计算text文本的高度
        Paint.FontMetrics fm = mPaint.getFontMetrics();
        float textHeight = fm.bottom - fm.top;

        float startAngle = mCurrentAngle;
        for (int i = 0; i < mNamesStrs.size(); i++) {
            //使文本根据,每个item的圆弧路径绘制
            Path path = new Path();
            path.addArc(rectF, startAngle, mOffsetAngle);
            canvas.drawTextOnPath(mNamesStrs.get(i), path, 0, textHeight + 10, mPaint);
            startAngle = startAngle + mOffsetAngle;
        }
    }

5、添加手势控制

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mDetector.onTouchEvent(event);
    }
private class TurntableGestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onDown(MotionEvent e) {
            //如果正在抽奖,则不可以手势滑动
            if (isDrawingLottery) {
                return false;
            }
            if (mOnFlingAnimator != null) {
                mOnFlingAnimator.cancel();
            }
            mStartX = e.getX();
            mStartY = e.getY();
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            LoggerUtil.i(TAG, "onScroll");
            float e2X = e2.getX();
            float e2Y = e2.getY();

            float xStart = mStartX - mCenterX;
            float yStart = mCenterY - mStartY;
            double distancestart = Math.sqrt(xStart * xStart + yStart * yStart);
            //计算移动点到圆心的距离
            float xMove = e2X - mCenterX;
            float yMove = mCenterY - e2Y;
            double distanceMove = Math.sqrt(xMove * xMove + yMove * yMove);
            double distanceMoveDz = Math.sqrt(distanceX * distanceX + distanceY * distanceY);

            double cosValue = (distancestart * distancestart + distanceMove * distanceMove - distanceMoveDz * distanceMoveDz) / (2 * distancestart * distanceMove);
            //多指触控的时候会造成cosValue 大于1,这里控制一下
            if (cosValue > 1) {
                LoggerUtil.i(TAG + "大于1", cosValue);
                cosValue = 1;
            } else if (cosValue < -1) {
                LoggerUtil.i(TAG + "小于1", cosValue);
                cosValue = -1;
            }
            double acos = Math.acos(cosValue);
            double changeAngleDz = Math.toDegrees(acos);

            //大于0 顺时针,小于0 逆时针
            float value = (mStartX - mCenterX) * (e2Y - mCenterY) - (mStartY - mCenterY) * (e2X - mCenterX);
            if (value >= 0) {
                mCurrentAngle = (float) (mCurrentAngle + changeAngleDz);
                isClockwise = true;
            } else {
                mCurrentAngle = (float) (mCurrentAngle - changeAngleDz);
                isClockwise = false;
            }
            setRotate(mCurrentAngle);

            mStartX = e2X;
            mStartY = e2Y;
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) {
            float e2X = e2.getX();
            float e2Y = e2.getY();

            float dx = velocityX * (float) 0.005;
            float dy = velocityY * (float) 0.005;
            float xStart = mStartX - mCenterX;
            float yStart = mCenterY - mStartY;
            double distancestart = Math.sqrt(xStart * xStart + yStart * yStart);
            //计算移动点到圆心的距离
            float xMove = e2X - mCenterX;
            float yMove = mCenterY - e2Y;
            double distanceMove = Math.sqrt(xMove * xMove + yMove * yMove);
            double distanceMoveDz = Math.sqrt(dx * dx + dy * dy);

            double cosValue = (distancestart * distancestart + distanceMove * distanceMove - distanceMoveDz * distanceMoveDz) / (2 * distancestart * distanceMove);
            //多指触控的时候会造成cosValue 大于1,这里控制一下
            if (cosValue > 1) {
                cosValue = 1;
            } else if (cosValue < -1) {
                cosValue = -1;
            }
            double acos = Math.acos(cosValue);
            double changeAngleDz = Math.toDegrees(acos);

            if (isClockwise) {

            } else {
                //逆时针
                changeAngleDz = changeAngleDz * -1;
            }

            mOnFlingAnimator = ValueAnimator.ofFloat((float) changeAngleDz, 0);
            mOnFlingAnimator.setDuration(1000);
            mOnFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float animatedX = (float) animation.getAnimatedValue();
                    mCurrentAngle = mCurrentAngle + animatedX;
                    setRotate(mCurrentAngle);
                }
            });

            mOnFlingAnimator.start();
            return true;
        }
    }

onScroll方法中计算手指滑动时候转盘转动的角度原理:

三、项目中如何引用

步骤1.将JitPack存储库添加到构建文件中

项目的根build.gradle中添加以下代码:

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}

步骤2.build.gradle添加依赖项

dependencies {
        implementation 'com.github.hnsycsxhzcsh:TurntableView:v1.1'
}

步骤3. 布局中引用控件(控件需要放在一个父布局中,父布局中放一个图片按钮)

<RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.turntableview.TurntableView
            android:id="@+id/turntable"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />

        <ImageView
            android:id="@+id/iv_node"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:background="@mipmap/node" />

    </RelativeLayout>

步骤4. activity中添加监听

 mIvGo.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mTurntable.startRotate(new ITurntableListener() {
                @Override
                public void onStart() {
                    Toast.makeText(MainActivity.this, "开始抽奖", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onEnd(int position, String name) {
                    mTvResult.setText("抽奖结束抽中第" + (position + 1) + "位置的奖品:" + name);
                }
            });
        }
    });

备注:

可以在github上下载我的项目:https://github.com/hnsycsxhzcsh/TurntableView,如果我的博客对你有帮助的话,欢迎博客点赞支持,并在github右上角star支持!

 

 

 

 

 

 

 

 

 

 

 

 

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值