利用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();
}
}
运行效果:
谢谢大家观看!
为了向别人、向世界证明自己而努力拼搏,而一旦你真的取得了成绩,才会明白:人无须向别人证明什么,只要你能超越自己。
上一篇: Android-UI开发之菜单
推荐阅读