Android属性动画Animator实现卫星Button
1. Animator和Animation
Animator框架是android4.0之后添加的一个动画框架,和之前的Animation框架相比,Animator可以进行更多和更精细化的动画控制,而且比之前更简单和更高效。在4.0源码中随处都可以看到Animator的使用。
在3.0系统之前,Android给我们提供了逐帧动画Frame Animation和补间动画Tween Animation两种动画:
- 逐帧动画的原理很简单,就是将一个完整的动画拆分成一张张单独的图片,然后将它们连贯起来进行播放;
- 补间动画是专门为View提供的动画,可以实现View的透明度、缩放、平移和旋转四种效果。
比如要水平位移到200坐标,是这样实现的:
ImageView image = (ImageView) findViewById(R.id.imageView); //位移错标 TranslateAnimation translateAnimation = new TranslateAnimation(0, 200, 0, 0); //动画完成后保持 translateAnimation.setFillAfter(true); //动画持续时间 translateAnimation.setDuration(1000); image.startAnimation(translateAnimation);
但是补间动画还是有很多缺陷的:
- 补间动画只能对View设置动画,对非View的对象不能设置动画;
- 补间动画只是改变了View的显示效果而没有真正的改变View的属性。例如,我们想使用补间动画将一个按钮从一个位置移动到一个新的位置,那么当移动完成之后我们点击这个按钮,是不会触发其点击事件的,而当我们点击移动前的位置时,会触发其点击事件,即补间动画只是在另一个地方重新绘制了这个View,其他的东西都没有改变。
为了弥补以上缺陷,属性动画Animator闪亮登场!虽然现在很多前端特性都是通过JS来实现,但是还是会有很多场景需要使用Android原始的动画API,这个时候属性动画就可以发挥自己强大的作用了!
属性动画,顾名思义,是对对象的属性设置的动画。简单的说,只要一个对象的某个属性有set和get方法,就可以对其设置属性动画。一句话概括,属性动画就是不断的改变一个对象的某个属性。我们只需要告诉系统动画的运行时长,需要执行哪种类型的动画,以及动画的初始值和结束值,剩下的工作就可以全部交给系统去完成了。
2. ObjectAnimator的使用
ObjectAnimator是属性动画中最常用的一个类,我们可以通过它直接控制一个对象的属性,十分便捷。同样是水平位移200坐标,只需要一行代码。
ObjectAnimator.ofFloat(image,"TranslationX",0f,200f).setDuration(1000).start();
ofFloat()方法的第一个参数是动画作用的对象,这里是一个ImageView;第二个参数是属性名称,这里指定的是X轴的平移;第三个参数是一个不定长参数,指定属性的起始值和结束值;setDuration()方法指定的是动画执行的时长,这里是1秒钟;最后调用start()方法,动画就开始执行了。这样链式构造的设计模式更为清晰方便。
需要说明的是,ofFloat()需要的参数中包括一个对象和对象的属性名字,但这个属性必须有get和set函数,内部会通过Java反射机制来调用set函数修改对象属性值。
此外还可以为动画设置监听器,在动画执行状态变化时,执行需要的操作。
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).setDuration(1000); alpha.addListener(new Animator.AnimatorListener() { @Override //动画开始 public void onAnimationStart(Animator animation) {} @Override //动画结束 public void onAnimationEnd(Animator animation) {} @Override //动画取消 public void onAnimationCancel(Animator animation) {} @Override //动画重复 public void onAnimationRepeat(Animator animation) {} }); alpha.start();
大多数情况下我们只需要监听动画结束,这个时候可以使用AnimatorListenerAdapter
alpha.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); Toast.makeText(view.getContext(),"get Animator",Toast.LENGTH_SHORT).show(); } });
ObjectAnimator所操作的常见属性如下:
- translationX\translationY,水平或者纵向移动;
- rotation、rotationX\rotationY,这里的rotation是指3D的旋转。rotationX是水平方向的旋转,rotationY是垂直方向的旋转;
- scaleX\scaleY 水平、垂直方向的缩放;
- X\Y 具体会移动到的某个点;
- alpha 透明度。
通过组合以上属性,就能绘制出各种酷炫的动画效果。
3. 动画集合的使用
更多场景中,我们需要同时控制多个动画来达到更加丰富的效果,当然最直接的方法就是多生成几个ObjectAnimator对象:
ObjectAnimator.ofFloat(image,"rotation",0f,360f).setDuration(1000).start(); ObjectAnimator.ofFloat(image,"TranslationX",0f,200f).setDuration(1000).start(); ObjectAnimator.ofFloat(image,"TranslationY",0f,200f).setDuration(1000).start();
这样的效果就是三个动画同时执行:旋转360度的同时向(200,200)坐标移动。
可能你会说执行对象都是一个,执行时间也都一样,可不可以有其他写法。当然可以,PropertyValuesHolder就是一种方法:
PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("rotation",0f,360f); PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("TranslationX",0f,300f); PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("TranslationY",0f,300f); ObjectAnimator.ofPropertyValuesHolder(image,p1,p2,p3).setDuration(1000).start();
PropertyValuesHolder记录下属性变化的情况,然后再把一系列PropertyValuesHolder一次性赋予一个对象,当然也可以把PropertyValuesHolder保存下来在设置给其他对象。
不过以上两个方法都是同事执行所有动画,如果要控制动画之间的顺序呢?
AnimatorSet将是首选!
ObjectAnimator rotation = ObjectAnimator.ofFloat(image, "rotation", 0f, 360f); ObjectAnimator translationX = ObjectAnimator.ofFloat(image, "TranslationX", 0f, 200f); ObjectAnimator translationY = ObjectAnimator.ofFloat(image, "TranslationY", 0f, 200f); AnimatorSet animatorSet = new AnimatorSet(); //animatorSet.playSequentially(rotation,translationX,translationY); animatorSet.playTogether(rotation,translationX,translationY); animatorSet.setDuration(1000).start();
将需要的动画都放入一个集合中,然后管理它们的执行顺序,可以一起执行(playTogether),也可以顺序执行(playTogether),还可以自定义顺序。
animatorSet.play(translationX).with(translationY).after(rotation);
with代表一起执行,after代表随后执行。
4. 实例:卫星Button#
利用上面提到的AnimatorSet,将多个动画效果组合在一起来实现卫星Buttion的效果
布局文件
布局很简单,在FrameLayout布局中7个ImageView相互重叠
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/imageView_h" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="5dp" app:srcCompat="@drawable/h"/> <ImageView android:id="@+id/imageView_g" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="5dp" android:paddingTop="5dp" app:srcCompat="@drawable/g"/> <ImageView android:id="@+id/imageView_f" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="5dp" android:paddingTop="6dp" app:srcCompat="@drawable/f"/> <ImageView android:id="@+id/imageView_e" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="5dp" android:paddingTop="6dp" app:srcCompat="@drawable/e"/> <ImageView android:id="@+id/imageView_d" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="5dp" android:paddingTop="6dp" app:srcCompat="@drawable/d"/> <ImageView android:id="@+id/imageView_c" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="5dp" android:paddingTop="6dp" app:srcCompat="@drawable/c"/> <ImageView android:id="@+id/imageView_b" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="5dp" android:paddingTop="6dp" app:srcCompat="@drawable/b"/> <ImageView android:id="@+id/imageView_a" android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/a"/> </FrameLayout>
动画实现
扇形展开动画实际上让多个ImageView同时在X和Y轴上移动到特定的位置,这些位置以一个原点和固定的半径成扇形。也就是说这些点的位置满足:
x = r * cos(a)
y = r * sin(a)
a是扇形的弧度。假设一共有n个卫星Button,再加上开头和结尾处的空位,一个要把90度平方分为n-2份,第i个控件的弧度就是(i*PI)/(2*(n-2))。
扇形关闭的动画也是同样的道理,只不过把Button移动的起点和终点颠倒即可。
具体代码实现如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { // an array of imageView ids. int[] res = {R.id.imageView_a,R.id.imageView_b,R.id.imageView_c,R.id.imageView_d,R.id.imageView_e, R.id.imageView_f,R.id.imageView_g}; // a list of ImageViews private List<ImageView> mImageViewList = new ArrayList<>(); // a flag to control opening action or closing action private boolean opened = false; // 轨迹半径 int r = 150; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); initView(); } //控件初始化 private void initView() { for(int i=0;i<res.length;i++){ ImageView imageView = (ImageView) findViewById(res[i]); mImageViewList.add(imageView); imageView.setOnClickListener(this); } } @Override public void onClick(View v) { switch (v.getId()){ case R.id.imageView_a: if(!opened) { openAnimator(); } else { closeAnimator(); } break; default: Toast.makeText(v.getContext(),"Id: "+v.getId(),Toast.LENGTH_SHORT).show(); break; } } //控件关闭 private void closeAnimator() { for(int i=0;i<res.length-1;i++){ //计算每个控件展开的弧度 moveBack(mImageViewList.get(i+1),(float) (Math.PI/2/(res.length-2))*i); } opened = false; } // 控件打开 private void openAnimator() { for(int i=0;i<res.length-1;i++){ //计算每个控件展开的弧度 moveTo(mImageViewList.get(i+1),(float) (Math.PI/2/(res.length-2))*i); } opened = true; } /** * 扇形展开 * @param objView 目标控件 * @param angle 展开的角度 */ void moveTo(View objView, float angle){ ObjectAnimator translationX = ObjectAnimator.ofFloat(objView, "translationX", 0f, (float) Math.cos(angle) * r); ObjectAnimator translationY = ObjectAnimator.ofFloat(objView, "translationY", 0f, (float) Math.sin(angle) * r); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(translationX,translationY); animatorSet.setDuration(500).start(); } /** * 扇形关闭 * @param objView 目标控件 * @param angle 关闭的角度 */ void moveBack(View objView, float angle){ ObjectAnimator translationX = ObjectAnimator.ofFloat(objView, "translationX", (float) Math.cos(angle) * r,0f); ObjectAnimator translationY = ObjectAnimator.ofFloat(objView, "translationY", (float) Math.sin(angle) * r,0f); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(translationX,translationY); animatorSet.setDuration(500).start(); } }
类似的效果当然也可以使用Animation来实现,网上也有很多例子,但实现复杂,代码量很大。反观ObejctAnimator实现起来更为简洁,清楚明白。通过结合更多属性的变化,相信读者朋友们能制作出更多更酷炫的效果,毕竟创意是无穷!
参考文献
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
ios 实现在一个APP中调起另一个APP(demo)
有时候会遇到在我们自己的app中调起其他app的业务需求,这里写了一个简单的demo,供参考。 1、首先建立两个工程,工程一,工程二,我这里分别叫做“第一个APP”,“第二个APP”,第一个app调起第二个app。 image.png image.png 2、配置要跳转APP的URL Types 在TestSecondApp中做如下配置: image.png 3、配置第一个APP的info.plist 在TestFirstApp中,选中Info.plist,在info.plist中添加一行: LSApplicationQueriesSchemes,array类型。然后添加一个item,string类型,值为在要跳转的app(第二个app)中设置的apptwo image.png 4、在第一个app中跳转代码 第一个app界面如下: image.png 点击“跳转到第二个app”按钮,在按钮事件中写入如下代码: image.png 注意:跳转url一定要加上“://”,这样才能正确的调起。 5、调试: 首先要把第一个app和第二个app编译运行,然后点击第一个app的按钮,就可以进入第二...
- 下一篇
使用函数计算来抽取apk信息
在之前的博客中我们看到,可以使用函数计算来实现一个自定义的图像处理服务,这个服务是稳定、可靠、弹性伸缩的,并且它是无服务器架构,按函数的调用时间收费,节省了服务器闲置的开销和运维成本。 使用函数计算可以方便地构建类似的微服务,比如我们可以创建一个处理android apk文件的微服务: 小吴将下载的apk文件上传到OSS 他想分析一下这些apk应用的权限信息: 使用aapt工具抽取出每个apk文件的权限信息 将它们录入到数据库中(rds),方便日后查询 步骤 1. 创建一个OSS bucket https://oss.console.aliyun.com/index#/ 注意创建bucket时选择“华东2”区域。 2. 创建RDS数据库 https://rdsnew.console.aliyun.com/ 在RDS控制台创建一个实例(注意选择华东2区域和
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8编译安装MySQL8.0.19
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS关闭SELinux安全模块
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7