美文网首页
在android中如何制作一个方向轮盘

在android中如何制作一个方向轮盘

作者: 汇源可乐 | 来源:发表于2021-09-14 17:25 被阅读0次

    先上效果图

    效果

    原理很简单,其实就是一个自定义的view

    通过观察,很容易发现,我们自己的轮盘就两个view需要绘制,一个是外面的圆盘,一个就随手指移动的滑块;
    外面的圆盘很好绘制,内部的滑块则需要采集手指的位置,根据手指的位置计算出滑块在大圆内的位置;
    最后,我们做的UI不是单纯做一个UI吧,肯定还是要用于实际应用中去,所以要加一个通用性很好的回调.

    计算滑块位置的原理:

    • 当触摸点在大圆与小圆的半径差之内:
      那么滑块的位置就是触摸点的位置
    • 当触摸点在大圆与小圆的半径差之外:
      已知大圆圆心坐标(cx,cy),大圆半径rout,小圆半径rinside,触摸点的坐标(px,py)
      求小圆的圆心(ax,ay)?
    原理

    作为经过九义的你我来说,这不就是一个简简单单的数学题嘛,很容易就求解出小圆的圆心位置了。
    利用三角形相似:
    \frac{ax-cx}{rout-rinside} = \frac{px-cx}{\sqrt{(px-cx)^2+(py-cy)^2}}
    \frac{ay-cy}{rout-rinside} = \frac{py-cy}{\sqrt{(px-cx)^2+(py-cy)^2}}

    通用性很好的接口:

    滑块在圆中的位置,可以很好的用一个二位向量来表示,也可以用两个浮点的变量来表示;
    xratio = \frac{ax-cx}{rout-rinside}
    yratio = \frac{ay-cy}{rout-rinside}
    这个接口就可以很好的表示了小圆在大圆的位置了,他们的取值范围是[-1,1]

    小技巧:

    为了小圆能始终在脱手后回到终点位置,我们设计了一个动画,当然,实际情况中有一种情况是,你移动到某个位置后,脱手后位置不能动,那你禁用这个动画即可。

    代码部分

    tips:代码部分的变量名与原理的变量名有出入

    public class ControllerView extends View implements View.OnTouchListener {
      private Paint borderPaint = new Paint();//大圆的画笔
      private Paint fingerPaint = new Paint();//小圆的画笔
      private float radius = 160;//默认大圆的半径
      private float centerX = radius;//大圆中心点的位置cx
      private float centerY = radius;//大圆中心点的位置cy
      private float fingerX = centerX, fingerY = centerY;//小圆圆心的位置(ax,ay)
      private float lastX = fingerX, lastY = fingerY;//小圆自动回归中点动画中上一点的位置
      private float innerRadius = 30;//默认小圆半径
      private float radiusBorder = (radius - innerRadius);//大圆减去小圆的半径
      private ValueAnimator positionAnimator;//自动回中的动画
      private MoveListener moveListener;//移动回调的接口
    
      public ControllerView(Context context) {
        super(context);
        init(context, null, 0);
      }
    
      public ControllerView(Context context,
          @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
      }
    
      public ControllerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
      }
    
      //初始化
      private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        if (attrs != null) {
          TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ControllerView);
          int fingerColor = typedArray.getColor(R.styleable.ControllerView_fingerColor,
              Color.parseColor("#3fffffff"));
          int borderColor = typedArray.getColor(R.styleable.ControllerView_borderColor,
              Color.GRAY);
          radius = typedArray.getDimension(R.styleable.ControllerView_radius, 220);
          innerRadius = typedArray.getDimension(R.styleable.ControllerView_fingerSize, innerRadius);
          borderPaint.setColor(borderColor);
          fingerPaint.setColor(fingerColor);
          lastX = lastY = fingerX = fingerY = centerX = centerY = radius;
          radiusBorder = radius - innerRadius;
          typedArray.recycle();
        }
        setOnTouchListener(this);
        positionAnimator = ValueAnimator.ofFloat(1);
        positionAnimator.addUpdateListener(animation -> {
          Float aFloat = (Float) animation.getAnimatedValue();
          changeFingerPosition(lastX + (centerX - lastX) * aFloat, lastY + (centerY - lastY) * aFloat);
        });
      }
    
      @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(getActualSpec(widthMeasureSpec), getActualSpec(heightMeasureSpec));
      }
    
    
      //处理wrapcontent的测量
      //默认wrapcontent,没有做matchParent,指定大小的适配
      //view实际的大小是通过大圆半径确定的
      public int getActualSpec(int spec) {
        int mode = MeasureSpec.getMode(spec);
        int len = MeasureSpec.getSize(spec);
        switch (mode) {
          case MeasureSpec.AT_MOST:
            len = (int) (radius * 2);
            break;
        }
        return MeasureSpec.makeMeasureSpec(len, mode);
      }
    
      //绘制
      @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, borderPaint);
        canvas.drawCircle(fingerX, fingerY, innerRadius, fingerPaint);
      }
    
      @Override public boolean onTouch(View v, MotionEvent event) {
        float evx = event.getX(), evy = event.getY();
        float deltaX = evx - centerX, deltaY = evy - centerY;
        switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
            //圆外按压不生效
            if (deltaX * deltaX + deltaY * deltaY > radius * radius) {
              break;
            }
          case MotionEvent.ACTION_MOVE:
            //如果触摸点在圆外
            if (Math.abs(deltaX) > radiusBorder || Math.abs(deltaY) > radiusBorder) {
              float distance = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
              changeFingerPosition(centerX + (deltaX * radiusBorder / distance),
                  centerY + (deltaY * radiusBorder / distance));
            } else { //如果触摸点在圆内
              changeFingerPosition(evx, evy);
            }
            positionAnimator.cancel();
            break;
          case MotionEvent.ACTION_UP:
            positionAnimator.setDuration(1000);
            positionAnimator.start();
            break;
        }
        return true;
      }
    
      /**
       * 改变位置的回调出来
       */
      private void changeFingerPosition(float fingerX, float fingerY) {
        this.fingerX = fingerX;
        this.fingerY = fingerY;
        if (moveListener != null) {
          float r = radius - innerRadius;
          if (r == 0) {
            invalidate();
            return;
          }
          moveListener.move((fingerX - centerX) / r, (fingerY - centerY) / r);
        }
        invalidate();
      }
    
      @Override protected void finalize() throws Throwable {
        super.finalize();
        positionAnimator.removeAllListeners();
      }
    
      public void setMoveListener(
          MoveListener moveListener) {
        this.moveListener = moveListener;
      }
    
      /**
        *回调事件的接口
        *
       **/
      public interface MoveListener {
        void move(float dx, float dy);
      }
    }
    

    style.xml

    <declare-styleable name="ControllerView">
      <attr name="fingerColor" format="color" />
      <attr name="borderColor" format="color" />
      <attr name="fingerSize" format="dimension" />
      <attr name="radius" format="dimension" />
    </declare-styleable>
    

    写在最后:

    欢迎留言,讨论~

    相关文章

      网友评论

          本文标题:在android中如何制作一个方向轮盘

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