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

目录
  • 先上效果图
  • 原理很简单,其实就是一个自定义的view
  • 计算滑块位置的原理:
  • 通用性很好的接口:
  • 小技巧:
  • 代码部分
  • 写在最后:

先上效果图

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

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

计算滑块位置的原理:

  • 当触摸点在大圆与小圆的半径差之内:
    那么滑块的位置就是触摸点的位置
  • 当触摸点在大圆与小圆的半径差之外:

已知大圆圆心坐标(cx,cy),大圆半径rout,小圆半径rinside,触摸点的坐标(px,py)
求小圆的圆心(ax,ay)?

作为经过九义的你我来说,这不就是一个简简单单的数学题嘛,很容易就求解出小圆的圆心位置了。
利用三角形相似:

通用性很好的接口:

滑块在圆中的位置,可以很好的用一个二位向量来表示,也可以用两个浮点的变量来表示;

这个接口就可以很好的表示了小圆在大圆的位置了,他们的取值范围是[-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>

写在最后:

这个是一个智能小车的安卓控制端的一部分demo,到此这篇关于如何在android中制作一个方向轮盘的文章就介绍到这了,更多相关android制作方向轮盘内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

    目录 先上效果图 原理很简单,其实就是一个自定义的view 计算滑块位置的原理: 通用性很好的接口: 小技巧: 代码部分 写在最后: 先上效果图 原理很简单,其实就是一个自定义的view 通过观察,很容易发现,我们自己的轮盘就两个view需要绘制,一个是外面的圆盘,一个就随手指移动的滑块: 外面的圆盘很好绘制,内部的滑块则需要采集手指的位置,根据手指的位置计算出滑块在大圆内的位置: 最后,我们做的UI不是单纯做一个UI吧,肯定还是要用于实际应用中去,所以要加一个通用性很好的回调. 计算滑块位置的

  • Android 中读取Excel文件实例详解

    Android 中读取Excel文件实例详解 最近有个需求需要在app内置数据,新来的产品扔给了我两个Excel表格就不管了(两个表格格式还不统一...),于是通过度娘等方法找到了Android中读取Excel表格文件的一种方法,记录一下. 闲话一下Excel中工作簿和工作表的区别: 工作簿中包含有工作表.工作簿可以由一张或多张工作表组成,一个工作簿就是一个EXCEL表格文件. 好了,开始读取表格文件吧. 前提 首先,我们假设需要读取的表格文件名字为test.xls, 位于assets根目录下.

  • Android中XUtils3框架使用方法详解(一)

    xUtils简介 xUtils 包含了很多实用的android工具. xUtils 支持大文件上传,更全面的http请求协议支持(10种谓词),拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响... xUitls 最低兼容android 2.2 (api level 8) 今天给大家带来XUtils3的基本介绍,本文章的案例都是基于XUtils3的API语法进行的演示.相信大家对这个框架也都了解过, 下面简单介绍下XUtils3的一些基本知识. XUtils3一共有4大功能:注解模块,网络

  • Android 中Context的使用方法详解

    Android 中Context的使用方法详解 概要: Context字面意思是上下文,位于framework package的android.content.Context中,其实该类为LONG型,类似Win32中的Handle句柄.很多方法需要通过 Context才能识别调用者的实例:比如说Toast的第一个参数就是Context,一般在Activity中我们直接用this代替,代表调用者的实例为Activity,而到了一个button的onClick(View view)等方法时,我们用t

  • Android 中RecyclerView顶部刷新实现详解

    Android 中RecyclerView顶部刷新实现详解 1. RecyclerView顶部刷新的原理 RecyclerView顶部刷新的实现通常都是在RecyclerView外部再包裹一层布局.在这个外层布局中,还包含一个自定义的View,作为顶部刷新时的指示View.也就是说,外层布局中包含两个child,一个顶部刷新View,一个RecyclerView,顶部刷新View默认是隐藏不可见的.在外层布局中对滑动事件进行处理,当RecyclerView滑动到顶部并继续下滑的时候,根据滑动的距

  • Android中mvp模式使用实例详解

    MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负 责显示.作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller. 在MVC里,View是可以直接访问

  • 如何在IDEA中快速解决Jar冲突详解

    目录 一.为什么会产生Jar包冲突? 1.1 直接与传递依赖 1.2 Maven 的传递依赖 1.3 Maven 如何解决版本冲突? 1.4 覆盖传递依赖版本 1.5 使用直接依赖覆盖传递依赖版本 二.通过IDEA快捷解决依赖冲突 2.1 查找冲突 2.2 发现冲突 2.3 解决冲突 一.为什么会产生Jar包冲突? 作为 Java 开发人员,我们可能会使用 Maven 维护许多应用程序以进行依赖项管理.这些应用程序需要不时升级以保持最新状态并添加新功能或安全更新. 由于某些依赖项之间的冲突,这个

  • Android中的LeakCanary的原理详解

    场景:最新的leakCanary2.8.1: debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' 原理:首先就是我们在引入最新的依赖包,什么都不用干了,因为他的初始化在清单文件中注册了contentProvider(),把初始化放到了这里面的onCreate()去初始化了,在初始化的过程中,他会用application监听观察对象activity.fragment等对象的生命周期的变化,当执行销毁的生命周期

  • Android如何实现一个DocumentProvider示例详解

    目录 前言 步骤 首先在Manifest 中注册这个Provider 创建这个Provider 重写queryRoot 重写queryDocument 重写getChildDocument 前言 假如你做了一个云盘类的app,或者可以保存用户导入的配置.用户在未来肯定需要获取这些文件,一个办法是写一个Activity,向一个文件管理软件一样把他们列出来.但是这个有一个问题是用户必须进入app 才能访问. 现在有一个解决方案是实现一个DocumentProvider 步骤 DocumentProv

  • Android 中RxPermissions 的使用方法详解

    Android 中RxPermissions 的使用方法详解 以请求拍照.读取位置权限为例 module的build.gradle: compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar' compile 'io.reactivex.rxjava2:rxjava:2.0.5' AndroidManifest.xml: <uses-permission android:name="android.permission.AC

随机推荐