Android 自定义控件实现弹性旋转的圆形菜单

本文介绍如何实现一个弹性旋转的圆形菜单控件,涉及事件拦截、三角函数、View测量和布局。首先继承ViewGroup并在onMeasure中测量Child,接着在onLayout中确定旋转角度、中心和半径,布局Child。通过计算角度和正切值确定Child的位置。触摸事件通过onInterceptTouchEvent拦截,并在onTouchEvent中处理旋转。源代码可在GitHub找到。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自定义控件实现弹性旋转的圆形菜单

  • 写这个之前参考了一下其他类似的控件,自己实现了一下并做如下记录
  • 使用到的包含“事件拦截”、“三角函数”、“View测量”、“View布局”
  • 控件的效果图如下,旋转动画、弹性旋转、item点击

这里写图片描述

流程梳理

  • 首先要实现一个圆形的菜单控件我们选择继承ViewGroup;
  • 第一步考虑在onMeasure中对所有Child进行测量,测量完成后onLayout才可以获取到Child的测量宽高;
  • 第二步考虑Child排版问题,也就是核心代码中onLayout的过程;
  • 在这个过程中,定义了“当前旋转角度”、“旋转中心”、旋转半径;
  • 获取容器内部Child个数,计算相邻Child角度间距;
  • 在已知旋转中心、半径、当前旋转角度、相邻Child角度间距后就可以为Child排版啦;
  • 这里根据角度及其所在象限,计算出该角度相对于每个象限的角度并计算其正切值;
  • 在已知半径、正切值就可以通过三角函数tan(A)=a边/b边、勾股定理a²+b²=c²计算出a边长、b边长;
  • 根据Child所在的象限,我们可以利用a边、b边计算出该Child中心点所在的坐标;
  • 知道这样就可以对该Child进行布局了,即调用Child.onLayout(l,t,r,b),布局时需要使用Child的测量宽高;
  • 第三步考虑触摸事件的拦截,这里使用容器onInterceptTouchEvent 方法在滑动距离大于系统touchSlop时进行拦截;
  • 一旦ViewGroup决定拦截该事件,那么后续的事件都会调用容器onTouchEvent;
  • 我们在onTouchEvent 的Move时,计算前后2次事件的偏转角度来更改“当前选中角度”并要求容器重新布局 requestLayout(); 以此达到旋转的目的;
  • 我们在onTouchEvent 的Up时计算本次旋转在1秒内旋转过的角度是否达到弹性旋转
  • 如果需要弹性旋转则根据当前的速度,设定Runnable 进行弹性旋转;

代码块

控件的代码如下:


public class RotateView extends ViewGroup {
   
   
    private static final String TAG = "RotateView";

    /**
     * 当前已旋转的角度 ,当改变该角度时,并重新布局则达到旋转的效果
     */
    private float mCurAngle = 0f;
    /**
     * 记录每次旋转开始时的角度
     */
    private float mStartRotateAngle;
    /**
     * 当前ViewGroup 旋转的中心点坐标
     */
    private PointF mCenterPoint;
    /**
     * 围绕中心点旋转的半径
     */
    private double mR;
    /**
     * 缓存每个Child 布局时所在的位置
     */
    private PointF mChildPoint = new PointF();
    /**
     * 系统可检测的最小滑动距离
     */
    private int touchSlop;
    /**
     * 记录每次MotionEvent 的坐标值
     */
    private float mLastX;
    private float mLastY;
    /**
     * 记录开始滑动的时间
     */
    private long mStartRotateTime;
    /**
     * 触发弹性旋转的边界值
     */
    private static final float ROTATE_RATE = 500;
    /**
     * 弹性旋转Runnable
     */
    private RotateRunnable action;
    /**
     * 当前是否处于弹性旋转状态
     */
    private boolean isFling;

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

    public RotateView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mCenterPoint = new PointF();
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    protected void
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值