下角动画旋转菜单、圆心弹出菜单ArcMenu 源码解析

支持类似Path的左下角动画旋转菜单及横向划出菜单、圆心弹出菜单

项目地址:https://github.com/daCapricorn/ArcMenu

一、关注3个效果

  1. 点击中心控制点 的时候,展开效果:
  • 中心控制点旋转45度的动画
  • 周围children 弹出动画

2.点击中心控制点的时候,收缩动画:

  • 中心控制点旋转45度
  • 周围children 自旋转并收缩

3.展开时候,点击child

  • 被点击的child放大,
  • 其他chidren 消失

二、3个java文件

  • ArcMenu.java  自定义View ,效果如图;主要有逻辑控制代码
  • ArcLayout.java  A Layout that arranges its children around its center.主要有子view布局,展开、收缩的动画实现
  • RotateAndTranslateAnimation.java  动画:控制objec产生位移,同时以object中心为轴心旋转。

三、展开和收缩代码实现

  1. 点击中心控制点的时候,展开效果:
  • 中心控制点旋转45度的动画
  • 周围children 弹出动画

2.点击中心控制点的时候,收缩动画:

  • 中心控制点旋转45度
  • 周围children 自旋转并收缩

先介绍三个重要的变量

private ArcLayout mArcLayout; //中心控制点图片

private ImageView mHintView; //当前状态: 是否已经展开;就像逻辑控制的开关一样,根据不同状态执行不同的动画效果

private boolean mExpanded = false;

ArcMenu.java中有这部分效果,实现代码

private void init(Context context) {
        LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        li.inflate(R.layout.arc_menu, this);

        mArcLayout = (ArcLayout) findViewById(R.id.item_layout);

        //中心控制点
        final ViewGroup controlLayout = (ViewGroup) findViewById(R.id.control_hint);
        controlLayout.setClickable(true);
        controlLayout.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    //中心控制点自旋转
                    mHintView.startAnimation(createHintSwitchAnimation(mArcLayout.isExpanded()));
                    //child 的收缩和展开
                    mArcLayout.switchState(true);
                }

                return false;
            }
        });

        mHintView = (ImageView) findViewById(R.id.control_hint);
    }

(1)、controlLayout 是一个ViewGroup,xml文件如下:

 <FrameLayout
        android:id="@+id/control_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@drawable/composer_button" >

        <ImageView
            android:id="@+id/control_hint"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:duplicateParentState="true"
            android:src="@drawable/composer_icn_plus" />
</FrameLayout>

图片资源composer_button和composer_icn_plus 是xml文件, composer_button.xml放在drawable下,

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/composer_button_pressed" android:state_pressed="true"/>
    <item android:drawable="@drawable/composer_button_normal"/>
</selector>

控件被按下时候,改变图片。

(2)

中心控制点旋转45度的实现:给controlLayout设置监听器OnTouchListener,点击的时候,就触发旋转动画

/**
     * 中心控制自旋转动画
     * @param expanded 状态:是否已经展开
     * @return
     */
    private static Animation createHintSwitchAnimation(final boolean expanded) {
        //绕中心旋转,收缩时候由45度转到0度,展开时由0度转到45度
        Animation animation = new RotateAnimation(expanded ? 45 : 0, expanded ? 0 : 45, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setStartOffset(0);
        animation.setDuration(100);
        animation.setInterpolator(new DecelerateInterpolator());
        animation.setFillAfter(true);

        return animation;
}

(3)

child展开、收缩动画实现

逻辑控制:

给controlLayout设置监听器OnTouchListener,调用ArcLayout中switchState()函数;这个函数就是个逻辑开关,重要的布局动画都在bindChildAnimation中;它还有个重要功能是修改mExpanded的状态值。

/**
     * switch between expansion and shrinkage
     *
     * @param showAnimation
     */
    public void switchState(final boolean showAnimation) {
        if (showAnimation) {
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                bindChildAnimation(getChildAt(i), i, 300);
            }
        }

        mExpanded = !mExpanded;

        if (!showAnimation) {
            requestLayout();
        }

        invalidate();
    }

布局实现:

private void bindChildAnimation(final View child, final int index, final long duration) {
        final boolean expanded = mExpanded;

        final int centerX = getWidth() / 2; //ViewGroup的中心X坐标
        final int centerY = getHeight() / 2;//ViewGroup的中心Y坐标
        final int radius = expanded ? 0 : mRadius;
        final int childCount = getChildCount();
        final float perDegrees = (mToDegrees - mFromDegrees) / (childCount - 1);

        Rect frame = computeChildFrame(centerX, centerY, radius, mFromDegrees + index * perDegrees, mChildSize);

        final int toXDelta = frame.left - child.getLeft(); //展开或收缩动画,child沿X轴位移距离
        final int toYDelta = frame.top - child.getTop();   //展开或收缩动画,child沿Y轴位移距离
        Log.d(TAG, "toXDelta:"+toXDelta +"\t"+"toYDelta"+toYDelta);

        Interpolator interpolator = mExpanded ? new AccelerateInterpolator() : new OvershootInterpolator(1.5f);
        final long startOffset = computeStartOffset(childCount, mExpanded, index, 0.1f, duration, interpolator);

        //mExpanded为true,收缩动画;为false,展开动画
        Animation animation = mExpanded ? createShrinkAnimation(0, toXDelta, 0, toYDelta, startOffset, duration,
                interpolator) : createExpandAnimation(0, toXDelta, 0, toYDelta, startOffset, duration, interpolator);

        final boolean isLast = getTransformedIndex(expanded, childCount, index) == childCount - 1;
        animation.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                if (isLast) {
                    postDelayed(new Runnable() {

                        @Override
                        public void run() {
                            onAllAnimationsEnd();
                        }
                    }, 0);
                }
            }
        });

        child.setAnimation(animation);
}

private static Rect computeChildFrame(final int centerX, final int centerY, final int radius, final float degrees,
            final int size) {

        //child的中心点
        final double childCenterX = centerX + radius * Math.cos(Math.toRadians(degrees));
        final double childCenterY = centerY + radius * Math.sin(Math.toRadians(degrees));

        return new Rect((int) (childCenterX - size / 2), (int) (childCenterY - size / 2),
                (int) (childCenterX + size / 2), (int) (childCenterY + size / 2));
    }

final int radius = expanded ? 0 : mRadius;

child尚未展开,expanded值为false,radius的值为mRadius。

中央控制点的坐标是ViewGroup的中心坐标(centerX,centerY),child的坐标分情况,

当child即将展开时候,radius的值为mRadius,

centerX + radius * Math.cos(Math.toRadians(degrees));

centerY + radius * Math.sin(Math.toRadians(degrees));

这个数学公式几何意义如图:已知圆上一点的角度,圆心坐标,半径,求圆上某点坐标。蓝色标出的角度值为degrees,radius就是半径。

同理,当child即将收缩时候,radius值为0,child的中心就是ViewGroup的中心坐标(centerX,centerY)

然后我们就可以根据child的中心点,和size,计算出child所占矩阵左上角(childCenterX - size / 2,childCenterY - size / 2)和右下角坐标(childCenterX + size / 2,childCenterY + size / 2)

最后计算出收缩和展开的时候,child沿X、Y轴的位移

展开动画代码:

private static Animation createExpandAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta,
            long startOffset, long duration, Interpolator interpolator) {
        Animation animation = new RotateAndTranslateAnimation(0, toXDelta, 0, toYDelta, 0, 720);
        animation.setStartOffset(startOffset);
        animation.setDuration(duration);
        animation.setInterpolator(interpolator);
        animation.setFillAfter(true);

        return animation;
    }

收缩动画代码:

private static Animation createShrinkAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta,
            long startOffset, long duration, Interpolator interpolator) {
        AnimationSet animationSet = new AnimationSet(false);
        animationSet.setFillAfter(true);
        //收缩过程中,child 逆时针自旋转360度
        final long preDuration = duration / 2;
        Animation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        rotateAnimation.setStartOffset(startOffset);
        rotateAnimation.setDuration(preDuration);
        rotateAnimation.setInterpolator(new LinearInterpolator());
        rotateAnimation.setFillAfter(true);

        animationSet.addAnimation(rotateAnimation);
        //收缩过程中位移,并逆时针旋转360度
        Animation translateAnimation = new RotateAndTranslateAnimation(0, toXDelta, 0, toYDelta, 360, 720);
        translateAnimation.setStartOffset(startOffset + preDuration);
        translateAnimation.setDuration(duration - preDuration);
        translateAnimation.setInterpolator(interpolator);
        translateAnimation.setFillAfter(true);

        animationSet.addAnimation(translateAnimation);

        return animationSet;
}

可以看到,收缩动画集合中,多了一个RotateAnimation自旋转动画。同时有RotateAndTranslateAnimation 对象。

四、点击监听器

展开时候,点击child

  • 被点击的child放大,
  • 其他chidren 消失

5.         * 增加item;此函数供外部调用
6.         * @param item
7.         * @param listener
8.         */
9.        public void addItem(View item, OnClickListener listener) {
10.            mArcLayout.addView(item);
11.            item.setOnClickListener(getItemClickListener(listener));
12.        }
13.
14.        private OnClickListener getItemClickListener(final OnClickListener listener) {
15.            return new OnClickListener() {
16.
17.                @Override
18.                public void onClick(final View viewClicked) {
19.                    //点击,child放大,其他child消失
20.                    Animation animation = bindItemAnimation(viewClicked, true, 400);
21.                    animation.setAnimationListener(new AnimationListener() {
22.
23.                        @Override
24.                        public void onAnimationStart(Animation animation) {
25.
26.                        }
27.
28.                        @Override
29.                        public void onAnimationRepeat(Animation animation) {
30.
31.                        }
32.
33.                        @Override
34.                        public void onAnimationEnd(Animation animation) {
35.                            postDelayed(new Runnable() {
36.
37.                                @Override
38.                                public void run() {
39.                                    itemDidDisappear();
40.                                }
41.                            }, 0);
42.                        }
43.                    });
44.                    //child 被点击的时候,自身放大,其他child消失的效果
45.                    final int itemCount = mArcLayout.getChildCount();
46.                    for (int i = 0; i < itemCount; i++) {
47.                        View item = mArcLayout.getChildAt(i);
48.                        if (viewClicked != item) {
49.                            bindItemAnimation(item, false, 300);
50.                        }
51.                    }
52.                    //中心控制点动画
53.                    mArcLayout.invalidate();
54.                    mHintView.startAnimation(createHintSwitchAnimation(true));
55.
56.                    if (listener != null) {
57.                        listener.onClick(viewClicked);
58.                    }
59.                }
60.            };
61.        }
62.
63.        private Animation bindItemAnimation(final View child, final boolean isClicked, final long duration) {
64.            Animation animation = createItemDisapperAnimation(duration, isClicked);
65.            child.setAnimation(animation);
66.
67.            return animation;
68.        }
69.
70.        private void itemDidDisappear() {
71.            final int itemCount = mArcLayout.getChildCount();
72.            for (int i = 0; i < itemCount; i++) {
73.                View item = mArcLayout.getChildAt(i);
74.                item.clearAnimation();
75.            }
76.             //参数为false,函数只修改mExpanded的值
77.            mArcLayout.switchState(false);
78.        }
79.
80.        private static Animation createItemDisapperAnimation(final long duration, final boolean isClicked) {
81.            AnimationSet animationSet = new AnimationSet(true);
82.            //放大缩小动画,isClicked 为true,放大两倍;为false,缩小至0
83.            animationSet.addAnimation(new ScaleAnimation(1.0f, isClicked ? 2.0f : 0.0f, 1.0f, isClicked ? 2.0f : 0.0f,
84.                    Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f));
85.            //Alpha改为0,child消失
86.            animationSet.addAnimation(new AlphaAnimation(1.0f, 0.0f));
87.
88.            animationSet.setDuration(duration);
89.            animationSet.setInterpolator(new DecelerateInterpolator());
90.            animationSet.setFillAfter(true);
91.
92.            return animationSet;
93.        }

被点击的child放大,主要由ScaleAnimation 动画实现,从1.0f 放大到2.0f.

不被点击的child ,ScaleAnimation动画,从1.0s缩小到0;放大的轴点都是child中心。

时间: 2024-10-07 15:02:58

下角动画旋转菜单、圆心弹出菜单ArcMenu 源码解析的相关文章

iOS_21团购_顶部菜单和弹出菜单联动

最后效果图: 各控件关系图1: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcHJlX2VtaW5lbnQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >\ 各控件关系图2: 点击Dock上面的buttonDockItem, 创建经导航控制器包装的DealListController, 而且加入到主控制器的右側空间 // // Deal

MFC托盘加载自己的菜单和弹出菜单的部分代码

CMenu menu,*pSubMenu; CPoint point; menu.LoadMenu(IDR_MENUM); //装载自定义的右键菜单 pSubMenu = menu.GetSubMenu(0); //获取第一个弹出菜单 GetCursorPos(&point); //获取当前光标位置 pSubMenu->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this); menu.DestroyMenu(); pSubMenu->De

layer弹出层 layer源码

下载源码:点击下载 ;!function(window, undefined){ "use strict"; var pathType = true, //是否采用自动获取绝对路径.false:将采用下述变量中的配置 pathUrl = 'lily/lib/layer/', //上述变量为false才有效,当前layerjs所在目录(不用填写host,相对站点的根目录即可). $, win, ready = { hosts: (function(){ var dk = location

Swing-JPopupMenu弹出菜单用法-入门

弹出菜单是GUI程序中非常常见的一种控件.它通常由鼠标右击事件触发,比如在windows系统桌面上右击时,会弹出一个包含“刷新”.“属性”等菜单的弹出菜单.Swing中的弹出菜单是JPopupMenu,它的基本性质与JMenu类似,可以使用add方法给它内部添加JMenu或者JMenuItem.手动显示JPopupMenu时,需使用show(parentComponent, x, y)方法,指定父控件和显示坐标.用户的操作习惯是在右击某个 控件时显示弹出菜单,那么需要使用component.se

关于MFC主菜单和右键弹出菜单

一.主菜单.弹出菜单和右键菜单的概念: 主菜单是窗口顶部的菜单,一个窗口或对话框只能有一个主菜单,但是主菜单可以被更改(SetMenu()更改): 创建方式:CMenu::CreateMenu(void); 弹出菜单在菜单项中是带有右向小三角的菜单,主菜单的每个菜单项都是一个弹出菜单(PopMenu),因此弹出菜单是凸型或左凸型: 创建方式:CMenu::CreatePopMenu(void); 右键菜单是点击右键弹出的菜单(响应OnContextMenu). 原文地址:https://www.

css+html+js实现多级下拉和弹出菜单

本文将使用css+html+js实现横向菜单.具有多级弹出菜单下拉. 首先我们来看看效果图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvajkwMzgyOTE4Mg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" > 首先应该写html部分的代码,代码比較简单,代码例如以下: <body> <div id="men

css+html+js实现下拉及多级弹出菜单

本文将使用css+html+js实现横向菜单,具有下拉的多级弹出菜单. 首先我们来看看效果图: 首先应该写html部分的代码,代码比较简单,代码如下: <body> <div id="menu"> <ul> <li><a href="#" id="current">首页</a></li> <li><a href="#">

Web标准:八、下拉及多级弹出菜单

Web标准:八.下拉及多级弹出菜单 知识点: 1.带下拉子菜单的导航菜单 2.绝对定位和浮动的区别和运用 3.CSS自适应宽度滑动门菜单 1)带下拉子菜单的导航菜单 带下拉子菜单的就是在一级导航下加一个二级菜单.这个在上一节第七节课上我已经做出来了,这里就不再写了.再重温一下注意点:如果要在一级菜单下增加二级菜单,二级菜单需要加一个float:none;来去掉浮动,否则二级菜单也会浮动到一行上去了. 2)绝对定位和浮动的区别和运用 绝对定位:它的位置将依据浏览器左上角开始计算或相对于父容器(在父

弹出菜单的创建与使用

-------------siwuxie095 工程名:TestSwingPopupMenu 包名:com.siwuxie095.popupmenu 类名:MyFrame.java 工程结构目录如下: MyFrame.java: package com.siwuxie095.popupmenu; import java.awt.BorderLayout; import java.awt.Component; import java.awt.EventQueue; import java.awt.