卫星菜单是现在一个非常受欢迎的“控件”,很多Android程序员都趋之若鹜,预览如下图。传统的卫星菜单是用Animation实现的,需要大量的代码,而且算法极多,一不小心就要通宵Debug。本帖贴出用属性动画Animator来实现卫星菜单。

一、浅析属性动画Animator

  Animator是Android3.0发布的新功能,代码简单,效果丰富。属性动画,顾名思义,只要是可以GET和SET的属性,我们都可以用属性动画进行处理。属性动画中常用的属性和方法如下:

ValueAnimator  //数值发生器,可以实现很多很灵活的动画效果
ObjectAnimator //ValueAnimator的子类,对ValueAnimator进行了封装,让我们可以更轻松的使用属性动画,我们通过ObjectAnimator来操纵一个对象,产生动画效果
AnimatorListener //对动画的开始、结束、暂停、重复等动作的事件监听(需要重写四个方法)
AnimatorListenerAdapter //对动画的开始、结束、暂停、重复中的一个动作的事件监听(根据选择的动作,只需要重写一个方法)
AnimatorSet //动画的集合,用来设置多个动画之间的关系(之前、之后、同时等)
PropertyValuesHolder //动画的集合,和AnimatorSet类似
TypeEvaluator //值计算器,在使用ValueAnimator.ofObject()方法时引入自定义的属性对象
Interpolator //插值器,设置动画的特效(速度渐变、弹跳等)

下面对这几个类做一下简单的介绍:

(一)ObjectAnimator:ObjectAnimator是最简单、最常用的属性动画,根据文档上的叙述: This subclass of ValueAnimator provides support for animating properties on target objects. ,这个ValueAnimator的子类对Object对象的属性提供动画。ObjectAnimator中常用的属性如下:

translationX / translationY             水平/垂直平移
rotaionX / rotationY 横向/纵向旋转
scaleX / scaleY 水平/垂直缩放
X / Y 直接到达X/Y坐标
alpha 透明度

我们使用一些方法( ofFloat() 、 ofInt() 、 ofObject() 、 ofPropertyValuesHolder() 等)来实现动画,调用 start() 方法来启动动画。选择哪个方法,主要是属性值的类型决定的。我们先来看看文档中对这几个方法的介绍:

target指的是动画的作用对象,一般指控件;propertyName就是上面说的“translationX”、“alpha”等属性名;values是一个不定长数组,记录着属性的始末值。唯一不同的是ofPropertyValuesHolder()方法,这个方法没有property参数,是因为这个参数在PropertyValuesHolder对象中就已经使用(下面会介绍PropertyValuesHolder的使用方法)。

(二)AnimatorListener:这是一个接口,监听属性动画的状态(开始/重复/结束/取消),Animator及其子类(包括ValueAnimator和ObjectAnimator等)都可以使用这个接口,使用 anim.addListener() 使用这个接口。具体的使用方法如下:

animator.addListener(new AnimatorListener() {
@Override // 动画开始的监听器
public void onAnimationStart(Animator animation) { ... }
@Override // 动画重复的监听器
public void onAnimationRepeat(Animator animation) { ... }
@Override // 动画结束的监听器
public void onAnimationEnd(Animator animation) { ... }
@Override // 动画取消的监听器
public void onAnimationCancel(Animator animation) { ... }
});

(三)AnimatorListenerAdapter:我们在实际开发中往往不会用到AnimatorListener中的全部四个方法,所以如果我们使用AnimatorListener,不仅浪费系统资源,代码看上去也不好看,因此,我们需要一个适配器(Adapter),可以让我们根据自己的意愿,只实现监听器中的一个或几个方法,这就要用到AnimatorListenerAdapter了。AnimatorListenerAdapter的使用方法和AnimatorListener一样,都需要使用 anim.addListener() 方法,不同的是参数。代码如下:

animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) { ... }
@Override
public void onAnimationCancel(Animator animation) { ... }
});

(四)AnimatorSet:先来看文档中的介绍: This class plays a set of Animator objects in the specified order. Animations can be set up to play together, in sequence, or after a specified delay.  这个类支持按顺序播放一系列动画,这些动画可以同时播放、按顺序播放,也可以在一段时间之后播放(主要通过 setStartDelay() 方法实现)。下面是文档中对这些方法的介绍:

值得说的是play()方法,它返回的是一个Builder对象,而Builder对象可以通过 with() 、 before() 、 after() 等方法,非常方便的控制一个动画与其他Animator动画的先后顺序。例如:

AnimatorSet s = new AnimatorSet();
s.play(anim1).with(anim2);
s.play(anim2).before(anim3);
s.play(anim4).after(anim3);

(五)PropertyValuesHolder:使用PropertyValuesHolder结合ObjectAnimator或ValueAnimator,可以达到AnimatorSet的效果。可以说,PropertyValuesHolder就是为了动画集而存在的,它不能单独的存在,因为它没有target参数(因此可以节省系统资源),所以只能依靠ObjectAnimator或ValueAnimator存在。一个实例的代码如下:

PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX", 0F, 200F);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("translationY", 0F, 200F);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("rotation", 0F, 360F);
ObjectAnimator.ofPropertyValuesHolder(top, pvh1, pvh2, pvh3).setDuration(2000).start();

(六)ValueAnimator:是ObjectAnimator的父类,但由于不能相应动画,也不能设置属性(值的是没有target和property两个参数),所以不常用。如果我们要监听ValueAnimator,则只能为ValueAnimator添加一个AnimatorUpdateListener(AnimatorUpdateListener可以监听Animator每个瞬间的变化,取出对应的值)。一个实例的代码如下:

ValueAnimator animator = ValueAnimator.ofInt(0, 100); // 没有target和property参数
animator.setDuration(5000);
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
System.out.println("-->" + value);
}
});
animator.start();

(七)TypeEvaluator:主要用于ValueAnimator的ofObject()方法。根据文档中的介绍, Evaluators allow developers to create animations on arbitrary property types ,Evaluators允许开发者自定义动画参数。因此,使用TypeEvaluator,我们可以打破ObjectAnimator的动画范围禁锢,创造我们自己的动画。大致代码如下:

ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator<Object>() {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
return null;
}
});
animator.start();

(八)Interpolator:插值器,可以设置动画的效果。Animator内置了很多插值器,如渐加速、渐减速、弹跳等等。插值器的种类可以在模拟器API DEMO应用的Views - Animation - Interpolator中查看。为Animator添加插值器只需要 animator.setInterpolator(new OvershootInterpolator()); 即可。

二、实现卫星菜单

  实现卫星菜单,我们主要使用了ObjectAnimator。先来介绍一下这个DEMO的效果(如下组图):开始的时候在屏幕左上角显示一个红色的按钮,点击之后弹出一列子菜单,每个子菜单之间的距离递增,弹出的时间递减,弹出时会有OverShoot效果,点击每个子菜单都会弹出相应的Toast;弹出菜单后再次点击红色按钮,则收回所有子菜单。

下面贴出代码。

 <RelativeLayout 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"
android:background="@color/white" > <!-- 上面的七张图片是七个子菜单 -->
<ImageView
android:id="@+id/menuitem_07"
style="@style/SateliteMenu_Item_Theme"
android:contentDescription="@string/app_name"
android:src="@drawable/item_07" /> <ImageView
android:id="@+id/menuitem_06"
style="@style/SateliteMenu_Item_Theme"
android:contentDescription="@string/app_name"
android:src="@drawable/item_06" /> <ImageView
android:id="@+id/menuitem_05"
style="@style/SateliteMenu_Item_Theme"
android:contentDescription="@string/app_name"
android:src="@drawable/item_05" /> <ImageView
android:id="@+id/menuitem_04"
style="@style/SateliteMenu_Item_Theme"
android:contentDescription="@string/app_name"
android:src="@drawable/item_04" /> <ImageView
android:id="@+id/menuitem_03"
style="@style/SateliteMenu_Item_Theme"
android:contentDescription="@string/app_name"
android:src="@drawable/item_03" /> <ImageView
android:id="@+id/menuitem_02"
style="@style/SateliteMenu_Item_Theme"
android:contentDescription="@string/app_name"
android:src="@drawable/item_02" /> <ImageView
android:id="@+id/menuitem_01"
style="@style/SateliteMenu_Item_Theme"
android:contentDescription="@string/app_name"
android:src="@drawable/item_01" /> <!-- 最上层的红色按钮,因为它显示在最上面,因此布局代码在最后 -->
<ImageView
android:id="@+id/menu_top"
android:layout_width="35.0dip"
android:layout_height="35.0dip"
android:layout_marginLeft="17.0dip"
android:layout_marginTop="17.0dip"
android:contentDescription="@string/app_name"
android:src="@drawable/top_toopen" /> </RelativeLayout>

MainActivity布局代码

 public class MainActivity extends Activity implements OnClickListener {
private ImageView top; // 红色按钮
// 七个子菜单的ID组成的数组
private int[] ids = new int[] { R.id.menuitem_01, R.id.menuitem_02, R.id.menuitem_03, R.id.menuitem_04, R.id.menuitem_05, R.id.menuitem_06, R.id.menuitem_07, };
private List<ImageView> itemList; // 七个子菜单都加到List中
private boolean isMenuOpen; // 记录子菜单是否打开了 @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
} private void initView() {
top = (ImageView) findViewById(R.id.menu_top);
top.setOnClickListener(this); itemList = new ArrayList<ImageView>();
for (int i = 0; i < ids.length; i++) {
ImageView imageView = (ImageView) findViewById(ids[i]);
imageView.setOnClickListener(this);
itemList.add(imageView);
}
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.menu_top:
if (!isMenuOpen) { // 如果子菜单处于关闭状态,则使用Animator动画打开菜单
for (int i = 0; i < itemList.size(); i++) {
// 子菜单之间的间距对着距离的增加而递增
ObjectAnimator animator = ObjectAnimator.ofFloat(itemList.get(i), "translationY", 0, (i + 1) * (30 + 2 * i));
animator.setDuration((7 - i) * 100); // 最远的子菜单弹出速度最快
animator.setInterpolator(new OvershootInterpolator()); // 设置插值器
animator.start();
}
top.setImageResource(R.drawable.top_toclose);
isMenuOpen = true;
} else { // 如果子菜单处于打开状态,则使用Animator动画关闭菜单
for (int i = 0; i < itemList.size(); i++) {
ObjectAnimator animator = ObjectAnimator.ofFloat(itemList.get(i), "translationY", (i + 1) * (30 + 2 * i), 0);
animator.setDuration((7 - i) * 100);
animator.start();
}
top.setImageResource(R.drawable.top_toopen);
isMenuOpen = false;
}
break;
default:
Toast.makeText(MainActivity.this, "Item" + (itemList.indexOf(v) + 1) + " Clicked...", Toast.LENGTH_SHORT).show();
break;
}
}
}

MainActivity代码

三、扩展

  使用属性动画可以实现的功能还有很多,这里再追加一个“评分”的功能实现。直接显示成绩往往给人一种突兀的感觉,所以我们想利用属性动画来实现一个数字变换的“成绩单”,让成绩滚动显示,同时,越到最后滚动的越慢,最后停止在真正的分数上。

  思路:我们需要用到ValueAnimator,并用AnimatorUpdateListener作为动画的监听器,监听动画的每一个动作,并为成绩的TextView设置Text。

  以下是代码。

 <RelativeLayout 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"
android:background="@color/white" > <TextView
android:id="@+id/main_mark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#ff0000"
android:textSize="65.0sp" /> </RelativeLayout>

MainActivity布局代码

 public class MainActivity extends Activity {
private TextView mark;
private static final int REAL_MARK = 96; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
} private void initView() {
mark = (TextView) findViewById(R.id.main_mark); ValueAnimator animator = ValueAnimator.ofInt(0, REAL_MARK);
animator.setDuration(3000);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
mark.setText(value + "");
}
});
animator.start();
}
}

MainActivity代码

Android之卫星菜单的实现的更多相关文章

  1. Android 实现卫星菜单

    步骤:一:自定义ViewGroup 1.自定义属性 a.attr.xml b.在布局文件中使用activity_main.xml c.在自定义控件中进行读取 2.onMeasure 3.onLayou ...

  2. Android 实现卫星菜单(精简版)

    MainActivity.java public class MainActivity extends AppCompatActivity { private ArcDemo mArc; privat ...

  3. android 实现自定义卫星菜单

    看了hyman老师的视频,听起来有点迷糊,所以就想把实现卫星菜单的实现总结一下.长话短说,下面总结一下: 一.自定义ViewGroup1).自定义属性文件 属性的定义: <attr name=& ...

  4. Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9671609 记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个 ...

  5. Android 的上下文菜单: Context Menu,registerForContextMenu(getListView())

    概述: Android 的上下文菜单类似于 PC 上的右键菜单.当为一个视图注册了上下文菜单之后,长按(2 秒左右)这个视图对象就会弹出一个浮动菜单,即上下文菜单.任何视图都可以注册上下文菜单,不过, ...

  6. android中常用菜单(menu)的基本知识

    (一)选项菜单 1.简单的创建菜单: @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMe ...

  7. Android SlidingMenu侧滑菜单使用

    把下载的侧滑菜单压缩包打开,会有一个library文件夹,在eclipse中import existing android code into workspace,导入library文件夹,并且选择作 ...

  8. 【转】Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9671609 记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个 ...

  9. Android:有关菜单的学习(供自己参考)

    Android:有关==菜单==的学习 上下文菜单 上下文菜单就是手机中对某一项进行==点击一定时间==后弹出的针对该项处理的菜单. context_menu.xml: <?xml versio ...

随机推荐

  1. Centos7 关闭防火墙

    CentOS 7.0默认使用的是firewall作为防火墙,使用iptables必须重新设置一下 1.直接关闭防火墙 systemctl stop firewalld.service #停止firew ...

  2. 项目自动化建构工具gradle 入门0——环境 & 废话

    gradle 是一个项目自动化构建工具.同类的产品还有ant ,maven等等.相比之下我更喜欢gradle,它语法简洁.兼容maven.ide集成很好. 学习使用gradle最快的方式是看文档,而且 ...

  3. 从BSP模型到Apache Hama

    一.什么是BSP模型 概述 BSP(Bulk Synchronous Parallel,整体同步并行计算模型)是一种并行计算模型,由英国计算机科学家Viliant在上世纪80年代提出.Google发布 ...

  4. float4与half4数据类型

    连续4个32位float类型数的向量 HLSL数据类型 GPU是以四维向量为基本单位来计算的.4个浮点数所组成的float4向量是GPU内置的最基本类型.使用GPU对两个float4向量进行计算,与C ...

  5. SQL用法操作合集

    SQL用法操作合集   一.表的创建 1.创建表 格式: 1 CREATE TABLE 表名 2 (列名 数据类型(宽度)[DEFAULT 表达式][COLUMN CONSTRAINT], 3 ... ...

  6. Windows10下安装OpenSSL

    Windows10下安装的方法 安装环境:Windows10专业版+VS2013 工具:ActivePerl-5.22.1.2201-MSWin32-x64-299574.msi,下载地址:http: ...

  7. python基础-异常处理

    一.错误和异常 程序中难免出现错误,而错误分成两种 1.1.语法错误(这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正) #语法错误示范一 if #语法错误示范二 def te ...

  8. CSS清除浮动float方法总结

    使用浮动造成的BUG: 使用浮动前:(子节点是将父节点撑开了) 代码如下 <div class="box"> <div class="d1"& ...

  9. git的基本介绍和使用

    前言:从事iOS开发一年多以来,一直使用svn管理源代码.对svn的特点和弊端已经深有体会.前些天双十二前后,项目工期紧张到爆,起早贪黑的加班,可谓披星戴月,这还不止,回到家中还要疯狂的敲代码.那么问 ...

  10. ORB-SLAM(六)回环检测

    上一篇提到,无论在单目.双目还是RGBD中,追踪得到的位姿都是有误差的.随着路径的不断延伸,前面帧的误差会一直传递到后面去,导致最后一帧的位姿在世界坐标系里的误差有可能非常大.除了利用优化方法在局部和 ...