大家对于卫星菜单应该都不陌生了,其实这个菜单如果能合适运用到我们的APP项目中,确实是一个不错的选择,交互性非常好。在写Demo之前我也上网搜了一些关于卫星菜单的实现,感觉好多人实现卫星菜单这种动画,采用的是补间动画,并且代码还不少,通过上一讲我们知道,补间动画不具备与用户交互的特点,就比如卫星菜单展开后,产生很多子菜单有很多的点击事件,会发现产生点击事件的位置不会随着补间动画而产生位置改变而改变,反而点击事件一直保存在初始的位置。当然补间动画也能做,那就需要产生动画后发生位置的改变,并且也需要将点击事件触发的位置随着动画改变而改变。这样实现起来,可能很麻烦。今天我给大家带来的是用属性动画来实现卫星菜单,用了后你会发现,核心代码仅仅100行左右就能完成,并且可以利用属性动画的特点,它具备与用户交互的特点,它的点击事件触发位置,会随着动画的位置改变而改变,说白了属性动画实质就是修改动画的属性,而补间动画则是不断调用draw()方法不断重绘来达到一种动画的效果。
实现卫星菜单的第一步:
分析一下,这样卫星菜单怎么实现?实现该菜单对布局有什么要求?
首先,来分析一下布局,布局应该是这样的,肯定有一个按钮,并且其他的子菜单都是重叠在第一个可见按钮下面,其他的子菜单都是不可见的。当点击最上面的一个按钮时,其他子菜单则呈1/4的圆弧显示,当再次点击,将这些按钮重新回到原来的位置,隐藏起来。
所以这样布局大家应该想到了什么布局吧,我们应该使用帧布局(FrameLayout),将几个ImageView重叠在一起,并且让第一个显示的按钮放在最后,这样就会第一个显示了。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/index2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:src="@drawable/b" /> <ImageView android:id="@+id/index3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:src="@drawable/c" /> <ImageView android:id="@+id/index4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:src="@drawable/d" /> <ImageView android:id="@+id/index5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:src="@drawable/e" /> <ImageView android:id="@+id/index6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:src="@drawable/f" /> <ImageView android:id="@+id/index7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:src="@drawable/g" /> <ImageView android:id="@+id/index8" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:src="@drawable/h" /> <ImageView android:id="@+id/index1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:src="@drawable/a" /> </FrameLayout>
那么接着分析一下具体实现的逻辑,原理图如下:
由以上原理图上可得出,可根据三角函数即可得出每个子菜单分别在X方向和Y方向上的偏移量,然后通过偏移量就可以利用的属性动画中的ObjectAnimator中指定的"translationX"和"translationY"两个属性,通过控制这两个属性偏移量从而达到偏移动画的效果。但是如何得到这个他们之间的夹角a呢?因为我们知道屏幕的一个左下角正好是一个90度角,然后看平行X轴和Y轴方向两个子菜单之间看有多少个间隔,一个间隔就是一个夹角,然后可以得到一个平均夹角:(90/间隔数)。
package com.mikyou.satellitemenutest; import java.util.ArrayList; import java.util.List; import android.R.animator; import android.R.bool; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.BounceInterpolator; import android.view.Window; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends Activity implements OnClickListener{ private int[] imageIds={//开辟一个数组用于存放各个子菜单的图片,注意:index1是最外面的图片不属于子菜单,所以只有7个子菜单 R.id.index1 ,R.id.index2 ,R.id.index3 ,R.id.index4 ,R.id.index5 ,R.id.index6 ,R.id.index7 ,R.id.index8 }; private boolean flag=false;//设置一个标记变量用于实现菜单的开关。 private List<ImageView> mImageViewsList=new ArrayList<ImageView>();//定义一个集合存放每个ImageView //属性动画相关 private ObjectAnimator animatorX;//x方向的位移动画 private ObjectAnimator animatorY;//y方向的位移动画 private float line=400f;//这个则是line的长度,是三角形那个最长的边,根据三角函数即可得出X,Y的偏移量 private float angle;//平均夹角 private float sinAngle;//sinAngle=-line*sin a(a为0~90度范围内夹角) private float cosAngle;//cosAngle=line*cos a(a为0~90度范围内夹角) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initView(); } private void initView() { for (int i = 0; i < imageIds.length; i++) { ImageView imageView=(ImageView) findViewById(imageIds[i]);//找到每一个ImageView的对象 imageView.setOnClickListener(this);//给每一个ImageView设置点击事件 mImageViewsList.add(imageView); } angle=(float) (90.0/(mImageViewsList.size()-2));//把每个子菜单的之间夹角平均分,总共8张图片除去第一张还剩7张,7个子菜单之间将会产生6个间隔,所以要减去2 } @Override public void onClick(View v) { switch (v.getId()) { case R.id.index1://表示是第一个可见的按钮 if (!flag) {//展开卫星菜单, //注意:我们以flag值来代表当前菜单的状态:若为false,则表示当前是关闭状态,则需要打开菜单,所以应调用打开菜单的方法;若为true则表示当前是打开状态,则需要关闭菜单,所以应调用关闭菜单的方法 toast("打开主菜单"); SwitchOfSatlliteMenuWithAttributeAnimation(flag); flag=true;//打开菜单后,菜单当前状态也就变化了,变为打开了,所以需要更新flag的值为true }else{//关闭卫星菜单 toast("关闭主菜单"); SwitchOfSatlliteMenuWithAttributeAnimation(flag); flag=false;//关闭菜单后,菜单当前状态也就变化了,变为关闭了,所以需要更新flag的值为false } break; case R.id.index2: toast("相机"); break; case R.id.index3: toast("音乐"); break; case R.id.index4: toast("地图"); break; case R.id.index5: toast("天气"); break; case R.id.index6: toast("通讯录"); break; case R.id.index7: toast("其他"); break; case R.id.index8: toast("信息"); break; default: break; } } /** * @author zhongqihong * 卫星菜单的开关,根据switchOfSatlliteMenu的值来判断打开或者关闭菜单 * */ public void SwitchOfSatlliteMenuWithAttributeAnimation(boolean switchOfSatlliteMenu){ float startX,endX,startY,endY; for (int i = 1; i < mImageViewsList.size(); i++) { sinAngle=(float) (Math.sin((i-1)*angle*Math.PI/180)*-line);//sin15度 sin30度 sin45度 sin60度 sin75度 sin90度值的 变化,Math.PI/180代表1度 cosAngle=(float) (Math.cos((i-1)*angle*Math.PI/180)*line);//cos15度 cos30度 cos45度 cos60度 cos75度 cos90度 值的变化 AnimatorSet set =new AnimatorSet();//创建一个AnimatorSet属性动画集合 if (!switchOfSatlliteMenu) {//打开菜单是X轴上:0->cosAngle,Y轴上:0->sinAngle startX=0f; endX=cosAngle; startY=0f; endY=sinAngle; set.setDuration(1000);//设置打开卫星菜单的动画时长 set.setStartDelay(i*100);//设置每个子菜单打开的时间延迟 set.setInterpolator(new AccelerateDecelerateInterpolator());//设置一个先加速后减速的插值器 }else{//关闭菜单是X轴上:cosAngle->0,Y轴上:sinAngle->0 startX=cosAngle; endX=0f; startY=sinAngle; endY=0f; set.setDuration(600);//设置关闭卫星菜单的动画时长 set.setStartDelay(i*100);//设置每个子菜单打开的时间延迟 set.setInterpolator(new AccelerateDecelerateInterpolator());//设置一个先加速后减速的插值器 } animatorX=ObjectAnimator.ofFloat(mImageViewsList.get(i), "translationX", startX,endX);//创建X方向上位移属性动画 animatorY=ObjectAnimator.ofFloat(mImageViewsList.get(i), "translationY", startY,endY);//创建Y方向上位移属性动画 set.playTogether(animatorX,animatorY);//表示X,Y轴方向上的动画同时开始 set.start();//开启动画 } } public void toast(String str){ Toast.makeText(MainActivity.this, str, 0).show(); } }
最后的运行效果: