我们都知道 Android 自带了 Roate Scale Translate Alpha 多种框架动画,我们可以通过她们实现丰富的动画效果,但是这些宽家动画却有一个致命的弱点,它们只是改变了 View 显示的大小,而没有改变 View 的响应区域。这时以 ObjectAnimator、ValueAnimator 为代表的属性动画也就应运而生了。
属性动画字如其名,是通过改变 View 的属性值来改变控件的形态,说白了就是通过反射技术来获取控件的一些属性如宽度、高度等的 get 和 set 方法,从而实现所谓的动画效果。所以,这就需要我们的 View (如自定义 View 中)具有 set 和 get 方法,如果没有则会导致程序的 Clash 。
具体步骤
由此也可以看出:属性动画直接改变了控件的属性,所以动画结束后控件也就发生了永久性的变化。
这里我打算通过使用 ObjectAnimator 实现四大动画框架:
给大家讲解下 ObjectAnimator 使用
private void iniAnimation(){ // 透明度动画 ObjectAnimator.ofFloat(mAlphaImage, "alpha", 1, 0, 1) .setDuration(4000) .start(); // 缩放 final AnimatorSet animatorSet = new AnimatorSet(); mScaleImage.setPivotX(mScaleImage.getWidth()+250); mScaleImage.setPivotY(mScaleImage.getHeight()+250); animatorSet.playTogether( ObjectAnimator.ofFloat(mScaleImage, "scaleX", 1, 0) .setDuration(2000), ObjectAnimator.ofFloat(mScaleImage, "scaleY", 1, 0) .setDuration(2000) ); animatorSet.start(); // 平移 translation final AnimatorSet translationAnimatorSet = new AnimatorSet(); translationAnimatorSet.playTogether( ObjectAnimator.ofFloat(mTranslationImage, "translationX", 20, 100) .setDuration(2000), ObjectAnimator.ofFloat(mTranslationImage, "translationY", 20,100) .setDuration(2000) ); translationAnimatorSet.start(); // 利用 ObjectAnimator 实现旋转动画 final AnimatorSet rotateAnimationSet = new AnimatorSet(); rotateAnimationSet.playTogether( ObjectAnimator.ofFloat(mRotationImage, "rotation",0, 360) .setDuration(2000) ); rotateAnimationSet.start(); }
以上代码就通过了 ObjectAnimator 实现了,四大效果,实现过程基本可以归纳为
最后的运行效果如开头动画所示
同样的,我们可以在一个 playTogether 方法中添加多个动画,这样就能实现多动画组合的效果。这里就不在赘述了,大家可以自己试试看(我 GIF 图中,右下角的动画,就是旋转 + 透明度)
ValueAnimator 是 ObjectAnimator 的父类,他两之间的区别是,ObjectAnimator 在ValueAnimator 的基础上,通过反射技术实现了动画功能,也就像我刚刚所举的例子,子要给了 ObjectAnimator 两个值(from,to),在确定动画类型(“scale,translate”),他就能自动生成动画。
与之形成区别,虽然我们同样需要给 ValueAnimator 传递起始和最终两个值,但是 ValueAnimator 并不会自动去执行什么,而是会通过 addUpdateListener 的监听方法,在时间插值器的作用下,有序的返回一连串数值,然后我们就可以通过这些数值,对控件进行设置。
最近看各大厂商,似乎都迷上了对 FloatingActionButton 进行操作,我就也来趁波热点。
这个效果既可以通过动画框架实现,也可通过属性动画实现,这里我给大家讲下实现的方法。
首先是思路
由于这里我们是采用 ValueAnimator 实现的,所以更具 ValueAnimator 的特性,在我们对其设定完时间插值器之后,它会规律的返回一系列数。所以我们只要更具这一系列数对控件的属性进行设置即可。
private FloatingActionButton fab; private ImageView imageView; private int buttonSize = 0, imageSize = 0; private float startY = 0; private float endY = 0; private ValueAnimator createValueAnimate(final View view, int start, int end){ ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { ViewGroup.LayoutParams params = view.getLayoutParams(); params.height = (int) animation.getAnimatedValue(); params.width = (int) animation.getAnimatedValue(); view.setLayoutParams(params); } }); return valueAnimator; }
可以看到我们传入三个参数,这里我做的是缩放动画,所以给的分别是控件,控件当前大小和控件目标大小。然后根据监听器返回的值进行设置即可。
调用方面
我这里实现的是上拉隐藏和下拉显示,所以我们需要判断下 Y轴 滑动方向:
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: startY = event.getY(); if ((startY - endY) < 0){ // 缩小 animationDown(fab, buttonSize); animationDown(imageView, imageSize); }else if ((startY - endY) > 0){ // 放大 animationUp(fab, buttonSize); animationUp(imageView, imageSize); } break; case MotionEvent.ACTION_DOWN: endY = event.getY(); break; } return super.onTouchEvent(event); } private void animationDown(final View view, int originalSize){ ValueAnimator animator = createValueAnimate(view, originalSize, 0); animator.addListener(new AnimatorListenerAdapter(){ @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); view.setVisibility(View.GONE); } }); animator.setInterpolator(new BounceInterpolator()); animator.setDuration(500).start(); } private void animationUp(final View view, int originalSize){ view.setVisibility(View.VISIBLE); ValueAnimator animator = createValueAnimate(view, 0, originalSize); animator.setInterpolator(new BounceInterpolator()); animator.setDuration(500).start(); }
这里我们会发现,由于是属性动画,所以改变的直接就是控件的大小,这就导致了一个问题,如果是实时的获取控件大小。那么我们在执行完多小动画,也就是 animationDown 后,就无法在获得控件原始大小了。
所以这里我们在 onResume 方法中获取控件大小:
@Override protected void onResume() { super.onResume(); fab.post(new Runnable() { @Override public void run() { buttonSize = fab.getHeight(); } }); imageView.post(new Runnable() { @Override public void run() { imageSize = imageView.getHeight(); } }); }
属性动画可以作为 ViewGroup 增加活减少控件是的动画,是的界面的变换不是那么的突兀,其实细心的同学可能有发现,android 是自带切换效果的,但是形式比较单一,所以这里我通过自定义 ObjectAnimator 的方法。
实现过程其实非常简单:
用同样的方法设置 remove 动画
LayoutTransition transition = new LayoutTransition(); ObjectAnimator appendAnimator = ObjectAnimator.ofPropertyValuesHolder( (ImageView) null, PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f), PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f), PropertyValuesHolder.ofFloat("alpha" , 0.0f, 1.0f) ); appendAnimator.setInterpolator(new BounceInterpolator()); transition.setAnimator(LayoutTransition.APPEARING, appendAnimator); transition.setDuration(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.APPEARING)); transition.setStartDelay(LayoutTransition.APPEARING, transition.getStartDelay(LayoutTransition.APPEARING)); ObjectAnimator removeAnimator = ObjectAnimator.ofPropertyValuesHolder( (ImageView) null, PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f), PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f), PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f) ); removeAnimator.setInterpolator(new BounceInterpolator()); transition.setAnimator(LayoutTransition.DISAPPEARING, removeAnimator); transition.setDuration(LayoutTransition.DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING)); transition.setStartDelay(LayoutTransition.DISAPPEARING, transition.getStartDelay(LayoutTransition.DISAPPEARING));
最后通过 setLayoutTransition 将这个 LayoutTransition 对象付给你的 ViewGroup 即可
layout = findViewById(R.id.layout); layout.setLayoutTransition(transition);
测试是分为添加控件和移除控件,功能在活动中动态的执行:
添加方法:
移除方法
@Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_add_image: ImageView imageView = new ImageView(ExtendActivity.this); imageView.setScaleType(ImageView.ScaleType.FIT_XY); imageView.setImageResource(R.drawable.heart); ViewGroup.LayoutParams params = new LinearLayout.LayoutParams(200, 200); imageView.setLayoutParams(params); layout.addView(imageView,0); break; case R.id.btn_remove_image: int count = layout.getChildCount(); if (count > 0){ layout.removeViewAt(0); } break; } }
项目 Demo 点击前往https://github.com/FishInWater-1999/android_view_user_defined_first
到此为止所有属性动画的使用基本介绍完毕
由于是个人学习的总结,如果有问题或是我个人疏漏,希望大家在评论区给我留言
祝大家编程愉快,少码 bug ,哈哈哈