[Android自定义控件]双圆圈内外旋转菜单

概述

双圆圈菜单是之前做的一个项目的需求,写完以后boss决定不用了,提取以后把它贴出来,第一次发技术博客,希望各位前辈多多指教。

先看一下效果图。
DoubleCircleShow

需求就是
* 外圈放置菜单选项,内圈为圆形大图;
* 当内圈小白点对准外圈选项时,内圈切换对应的选项图案;
* 移动内圈时,内圈图案随转动角度旋转;
* 转动内外圈时,若没有转到对应角度,自动回弹或者弹向对应角度。


实现细节

这个DoubleCircleLayout我是仿照张鸿洋前辈的《 Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单》写的,在这里说明一下。鸿洋前辈的博客讲得已经很明白很细致了,我就不献丑了,主要贴一些我自己写的不一样的地方。
http://blog.csdn.net/lmj623565791/article/details/43131133


DoubleCircleLayout

Activity及布局文件都非常简单,这里就不写了,有兴趣的可以到github上download我的项目,地址是(https://github.com/loommo/DoubleCircleLayout)。

DoubleCircleLayout是一个自定义的ViewGroup,主要需要重写ViewGroup的onLayout(),onMeasure(),dispatchTouchEvent()方法。
* OnMeasure()测量布局容器及子视图的宽高,可以根据我们需要设定各个视图的宽高;
* OnLayout()设置各个子视图的位置;
* dispatchTouchEvent()编写转动内外圈不同的视图转动效果。


OnLayout()

为了实现需求需要的效果,首先要设定这个不完整的双环的位置,内环圆心在界面水平中心线靠左的位置,x坐标约为1/5屏幕宽度,而外圈圆心item距离约1/2屏幕宽度位置摆放。初始内外圈转动角度(mOuterStartAngle,mInnerStartAngle)为0。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int layoutRadius = mRadius;

    int focusRadius = (int) (820 / 1520f * mRadius);

    int fWidth = (int) (layoutRadius * RADIO_DEFAULT_FOCUSITEM_DIMENSION);

    // Laying out the child views
    final int childCount = getChildCount();

    int left, top;
    // menu item 的尺寸
    int cWidth = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION_WIDTH);

    int cHeight = (int) (layoutRadius * RADIO_DEFAULT_CHILD_DIMENSION);

    // 根据menu item的个数,计算角度
    float angleDelay;

    if (getChildCount() > 2) {
        angleDelay = 360 / (getChildCount() - 2);
    } else {
        angleDelay = 360;
    }

    mOneAngle = angleDelay;

    // 遍历去设置menuitem的位置
    for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);

        // 中心child与focus跳过
        if (child.getId() == R.id.id_circle_menu_item_center || child.getId() == R.id.id_circle_menu_item_focus)
            continue;

        if (child.getVisibility() == GONE) {
            continue;
        }

        mOuterStartAngle %= 360;

        mInnerStartAngle %= 360;

        // 计算,中心点到menu item中心的距离
        float tmp = layoutRadius / 2f - cHeight / 2;

        // tmp cos 即menu item中心点的横坐标
        left = mRadius * 200 / 1520
                + (int) Math.round(tmp
                * Math.cos(Math.toRadians(mOuterStartAngle)) - 1 / 2f
                * cHeight);
        // tmp sin 即menu item的纵坐标
        top = mRadius
                / 2
                + (int) Math.round(tmp
                * Math.sin(Math.toRadians(mOuterStartAngle)) - 1 / 2f
                * cHeight);

        child.layout(left, top, left + cWidth, top + cHeight);
        // 叠加尺寸
        mOuterStartAngle += angleDelay;
    }

    // focus位置
    View fView = findViewById(R.id.id_circle_menu_item_focus);
    if (fView != null) {

        float tmp = focusRadius / 2f;

        int fl = mRadius * 259 / 1520 + (int) Math.round(tmp
                * Math.cos(Math.toRadians(mInnerStartAngle)) - 1 / 2f
                * fWidth);
        int fr = mRadius / 2 + (int) Math.round(tmp
                * Math.sin(Math.toRadians(mInnerStartAngle)) - 1 / 2f
                * fWidth);
        fView.layout(fl, fr, fl + fWidth, fr + fWidth);
    }

    // 找到中心的view,如果存在设置onclick事件
    View cView = findViewById(R.id.id_circle_menu_item_center);
    if (cView != null) {
        cView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                if (mOnMenuItemClickListener != null) {
                    mOnMenuItemClickListener.itemCenterClick(v);
                }
            }
        });
        // 设置center item位置
        float cl = mRadius * RADIO_DEFAULT_CENTERITEM_LEFT - cView.getMeasuredWidth() / 2;
        int cr = mRadius / 2 - cView.getMeasuredWidth() / 2;
        cView.layout((int) cl, cr, (int) (cl + cView.getMeasuredWidth()), cr + cView.getMeasuredWidth());
    }
}

dispatchTouchEvent()

这是最重要的一个方法,为了实现外圈转动,对准小白点内圈切换图片,用户按下屏幕(ACTION_DOWN),记录点击x,y坐标,根据x,y坐标计算点击范围是在内圈或者外圈(isBigCircle());当用户手指挪动(ACTION_MOVE)时,计算当前x,y坐标(getAngle())与原先x,y坐标差别,得到转动角度,由转动角度重新布局item位置及内圈图片角度,使得效果如同item随手指转动;用户抬起手指(ACTION_UP)时,计算当前内外圈转动角度,计算当前是否匹配(对转小白点)及匹配的是哪个item(matchItem()),切换内圈图片,若不匹配,启动FlingRunnable,回弹至最近的匹配角度。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    float x = ev.getX();
    float y = ev.getY();

    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:

            // 初始点击xy值
            mLastX = x;
            mLastY = y;
            // 初始化
            mOuterTmpAngle = 0;
      
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值