利用FloatingActionButton+ValueAnimator 完成卫星菜单效果

前言:卫星菜单其实已经很常见了,网上也有很多教程甚至都有开源的控件了。嫌麻烦自己写的可以直接取拿来用。接下来文章会简单说明实现的过程。

github开源库:https://github.com/oguzbilgener/CircularFloatingActionMenu
这个效果很不错,需要的朋友可以去看看

正文之前先说说FloatingActionButton到底是什么吧,相信还是有部分朋友并不太清楚。

  • FloatingActionButton(FAB悬浮按钮) 是 Android 5.0 新特性——Material Design
    中的一个控件,是一种悬浮的按钮。
  • FloatingActionButton 是 ImageView 的子类,因此它具备ImageView的全部属性。
  • FloatingActionButton 结合 CoordinatorLayout 使用,即可实现悬浮在任意控件的任意位置。
  • 使用 FloatingActionButton 的难点主要是布局,其在JAVA代码中的用法和普通的 ImageView 基本相同。
    这里写图片描述

跟所有MD控件一样,要使用FAB,需要在gradle文件中先注册依赖:

compile 'com.android.support:design:25.0.0'
//        FAB 基本属性:
//        android:src:FAB中显示的图标
//        app:backgroundTint:正常的背景颜色
//        app:rippleColor:按下时的背景颜色
//        app:elevation:正常的阴影大小
//        app:pressedTranslationZ:按下时的阴影大小
//        app:layout_anchor:设置FAB的锚点,即以哪个控件为参照设置位置
//        app:layout_anchorGravity:FAB相对于锚点的位置
//        app:fabSize:FAB的大小,normal或mini(对应56dp和40dp)
//        注意:要想让FAB显示点击后的颜色和阴影变化效果,必须设置onClick事件

想进一步了解FloatingActionButton的可以去看看鸿洋大神的博客:
http://blog.csdn.net/lmj623565791/article/details/46678867


好,FloatingActionButton知道是什么了后我们回到卫星菜单上面来。
其实卫星菜单的效果就是当我们点击主菜单之后弹出附属的子item菜单,再次点击收回子item菜单。那么就会考虑到这么几点:
1.定位Item(设置每一个菜单项的位置)
2.展开Item(动画效果实现,包括菜单按钮旋转动画、菜单项平移、旋转动画、菜单项缩放、透明度变换动画等,这里我们只做平移效果)
3.收回Item

定位涉及到的方法一共有下面几个:参考http://blog.csdn.net/jason0539/article/details/42743531

//view获取自身坐标:getLeft(),getTop(),getRight(),getBottom()
//view获取自身宽高:getHeight(),getWidth()
//motionEvent获取坐标:getX(),getY(),getRawX(),getRawY()

//getTop:获取到的,是view自身的顶边到其父布局顶边的距离
//getLeft:获取到的,是view自身的左边到其父布局左边的距离
//getRight:获取到的,是view自身的右边到其父布局左边的距离
//getBottom:获取到的,是view自身的底边到其父布局顶边的距离

//getX():获取点击事件相对控件左边的x轴坐标,即点击事件距离控件左边的距离
//getY():获取点击事件相对控件顶边的y轴坐标,即点击事件距离控件顶边的距离
//getRawX():获取点击事件相对整个屏幕左边的x轴坐标,即点击事件距离整个屏幕左边的距离
//getRawY():获取点击事件相对整个屏幕顶边的y轴坐标,即点击事件距离整个屏幕顶边的距离

文字或许不太理解,那么我们上图
这里写图片描述

展开Item

需要做些什么呢,这里我们以子item为3举例说明:
这里写图片描述

根据上图可以清晰的看出来当子item菜单为3个的时候,子itemA应该是左移M单位(M为我们自己设置的长度);子itemC上移M单位;重点为子itemB的位置,由图可以知道当我们以初始item(以下用O代替),子itemB和坐标轴来画一个三角形的话,就可以根据三角函数sin与cos来知道子itemB的坐标。∠AOC为90°OB平分∠AOC,即∠BOC = (∠AOC/2)=45° ,由三角函数可以很简单的得到B相对于O 左移了M*sin(45°)距离,上移了M*cos(45°) 距离。 以此可以推导当子item为4的时候只需要根据个数算出彼此之间的角度大小,然后同理运用三角函数可以得到位移之后的坐标。

**

收回item

**

相对于展开,收回要简单多了。
展开后我们可以分别得到每个子item的位置坐标,和初始点的位置坐标,那么就可以利用平移动画将其移动回初始点即可。

代码部分:

xml布局代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/item1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="16dp"
        android:src="@mipmap/ic_launcher"
        app:fabSize="mini"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/item2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="16dp"
        android:src="@mipmap/ic_launcher"
        app:fabSize="mini"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/item3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="16dp"
        android:src="@mipmap/ic_launcher"
        app:fabSize="mini"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="16dp"
        android:src="@mipmap/ic_launcher"
        app:fabSize="mini"/>
</RelativeLayout>

Activity代码:

@ContentView(R.layout.activity_fab)
public class FABActivity extends BaseActivity {
    @ViewInject(R.id.fab)
    private FloatingActionButton FABButton;

    @ViewInject(R.id.item1)
    private FloatingActionButton Item1;

    @ViewInject(R.id.item2)
    private FloatingActionButton Item2;

    @ViewInject(R.id.item3)
    private FloatingActionButton Item3;
    //菜单是否展开
    private boolean menuOpen = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    //点击事件
    @Event(R.id.fab)
    private void fab(View view) {
        if (menuOpen == false) {
            showMenu();//展开
        } else {
            hideMenu();//收回
        }
    }

    //展开菜单
    private void showMenu() {
        //设置为展开菜单
        menuOpen = true;
        //获取1°的值,后面动画移动会用到
        final double r = Math.PI / 180;

        //取得主菜单坐标
        int x = (int) FABButton.getX();
        int y = (int) FABButton.getY();

        //设置第一个子菜单x轴移动动画
        ValueAnimator v1 = ValueAnimator.ofInt(x, x - 200);//起始位置主菜单x坐标,终位置向左移200
        v1.setDuration(500);
        v1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int l = (int) animation.getAnimatedValue();
                int t = (int) Item1.getY();
                int r = Item1.getWidth() + l;
                int b = Item1.getHeight() + t;
                Item1.layout(l, t, r, b);
            }
        });

        //设置第二个子菜单x轴与y轴移动动画
        ValueAnimator v2x = ValueAnimator.ofInt(x, x - (int) (200 * Math.sin(45 * r)));//起始位置主菜单x坐标,终位置向左移200*sin(45°)
        ValueAnimator v2y = ValueAnimator.ofInt(y, y - (int) (200 * Math.cos(45 * r)));//起始位置主菜单x坐标,终位置向左移200*cos(45°)
        v2x.setDuration(500).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int l = (int) animation.getAnimatedValue();
                int t = (int) Item2.getY();
                int r = Item2.getWidth() + l;
                int b = Item2.getHeight() + t;
                Item2.layout(l, t, r, b);
            }
        });
        v2y.setDuration(500).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int l = (int) Item2.getX();
                int t = (int) animation.getAnimatedValue();
                int r = Item2.getWidth() + l;
                int b = Item2.getHeight() + t;
                Item2.layout(l, t, r, b);
            }
        });

        //设置第三个子菜单y轴移动动画
        ValueAnimator v3 = ValueAnimator.ofInt(y, y - 200);//起始位置主菜单y坐标,终位置向上移200
        v3.setDuration(500).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int l = (int) Item3.getX();
                int t = (int) animation.getAnimatedValue();
                int r = Item3.getWidth() + l;
                int b = Item3.getHeight() + t;
                Item3.layout(l, t, r, b);
            }
        });

        //开始上面设置好的展开动画
        v1.start();
        v2x.start();
        v2y.start();
        v3.start();
    }


    //收回菜单
    private void hideMenu() {
        //设置为收回菜单
        menuOpen = false;
        //获取现在第一个子菜单的x轴坐标
        int x = (int) Item1.getX();
        ValueAnimator v1 = ValueAnimator.ofInt(x, (int) FABButton.getX());//始:现x坐标;  终:主菜单x坐标
        v1.setDuration(500);
        v1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int l = (int) animation.getAnimatedValue();
                int t = (int) Item1.getY();
                int r = Item1.getWidth() + l;
                int b = Item1.getHeight() + t;
                Item1.layout(l, t, r, b);
            }
        });

        //获取现在第二个子菜单的x轴,y轴坐标
        x = (int) Item2.getX();
        int y = (int) Item2.getY();
        ValueAnimator v2x = ValueAnimator.ofInt(x, (int) FABButton.getX());
        ValueAnimator v2y = ValueAnimator.ofInt(y, (int) FABButton.getY());
        v2x.setDuration(500).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int l = (int) animation.getAnimatedValue();
                int t = (int) Item2.getY();
                int r = Item2.getWidth() + l;
                int b = Item2.getHeight() + t;
                Item2.layout(l, t, r, b);
            }
        });
        v2y.setDuration(500).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int l = (int) Item2.getX();
                int t = (int) animation.getAnimatedValue();
                int r = Item2.getWidth() + l;
                int b = Item2.getHeight() + t;
                Item2.layout(l, t, r, b);
            }
        });

        //获取现在第三个子菜单的y轴坐标
        y = (int) Item3.getY();
        ValueAnimator v3 = ValueAnimator.ofInt(y, (int) FABButton.getY());
        v3.setDuration(500).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int l = (int) Item3.getX();
                int t = (int) animation.getAnimatedValue();
                int r = Item3.getWidth() + l;
                int b = Item3.getHeight() + t;
                Item3.layout(l, t, r, b);
            }
        });

        //开始设置好的收回动画
        v1.start();
        v2x.start();
        v2y.start();
        v3.start();
    }
}

运行效果:
这里写图片描述

谢谢大家观看!


为了向别人、向世界证明自己而努力拼搏,而一旦你真的取得了成绩,才会明白:人无须向别人证明什么,只要你能超越自己。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值