• Android 动画机制与使用技巧


    • 动画效果一直是人机交互中非常重要的部分,与死板、突兀的显示效果不同,动画效果的加入,让交互变得更加友好,特别是在提示、引导类的场景中,合理地使用动画能让用户获得更加愉悦的使用体验

    一、Android View动画框架

    • Animation框架定义了透明度、旋转、缩放、位移等几种常见的动画
    • 实现原理:
      • 每次绘制View时,ViewGroup中的drawChild函数获取该view的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix())
      • 通过矩阵运算完成帧动画,如果动画没有完成,就继续调用invalidate() 函数,启动下次绘制来驱动动画,从而完成整个动画的绘制。

    二、帧动画

    • 帧动画就是一张张图片不同的切换,形成的动画效果。
    • 一般手机的开机动画,应用的等待动画等都是帧动画,因为只需要几张图片轮播,极其节省资源,如果真的设计成动画,那么是很耗费资源的事。
    • 在res目录下新建一个drawable文件夹并定义xml文件,子节点为 animation-list,在这里定义要显示的图片和每张图片的显示时长。
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android" 
              android:oneshot="false"><!-- false表示循环播放,true表示只播放一次 -->
        <item android:drawable="@drawable/g1" android:duration="200" />
        <item android:drawable="@drawable/g2" android:duration="200" />
        <item android:drawable="@drawable/g3" android:duration="200" />
        <item android:drawable="@drawable/g4" android:duration="200" />
        <item android:drawable="@drawable/g5" android:duration="200" />
        <item android:drawable="@drawable/g6" android:duration="300" />
        <item android:drawable="@drawable/g7" android:duration="400" /><!-- 慢动作 -->
        <item android:drawable="@drawable/g8" android:duration="500" />
        <item android:drawable="@drawable/g9" android:duration="200" />
        <item android:drawable="@drawable/g10" android:duration="200" />
        <item android:drawable="@drawable/g11" android:duration="200" />
     
    </animation-list>
    
    • 在屏幕上播放帧动画,需要布局文件有一个ImageView来显示动画图片
    public class MainActivity extends Activity {
     
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		ImageView iv = (ImageView) findViewById(R.id.iv);
    		//把动画文件设置为imageView的背景
    		iv.setBackgroundResource(R.drawable.frameanimation);
    		
    		AnimationDrawable ad = (AnimationDrawable) iv.getBackground();
    		//播放动画
    		ad.start();
    	}
    }
    

    三、补间动画(视图动画)

    • 组件由原始状态向终极状态转变时,为了让过渡更自然,而自动生成的动画叫做补间动画。
    • 主要是在Android 3.0之前
    • 最大的缺陷就是不具备交互性
    • 位移、旋转、缩放、透明
     
    public class MainActivity extends Activity {
     
    	private ImageView iv;
    	private TranslateAnimation ta;
    	private RotateAnimation ra;
    	private ScaleAnimation sa;
    	private AlphaAnimation aa;
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		iv = (ImageView) findViewById(R.id.iv);
    	}
    	//位移动画
    	public void translate(View v){
    		//创建位移动画的对象,设置动画的初始位置和结束位置
    		//10,100表示,从imageview的真实坐标的左上角 x+10,移动到 x+100的位置
    		//20,200表示,从imageview的真实坐标的左上角 y+20,移动到 y+200的位置
    		//ta = new TranslateAnimation(10, 100, 20, 200);
    		
    		//Animation.RELATIVE_TO_SELF相对于自己
    		//对于x,表示,相对于自己,起点是 imageview的真是坐标的左上角 x+ 0.5*iv的宽度 到 x+ 2*iv的宽度
    		//y 就是乘以 iv 的高度,也可以相对于父控件,但是用的比较少
    		ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2,
    				                    Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);
    		
    		//设置播放时间
    		ta.setDuration(2000);
    		//设置重复次数,播放一次,重复一次
    		ta.setRepeatCount(1);
    		//设置重复放的模式
    		ta.setRepeatMode(Animation.REVERSE);
    		//动画播放完毕后,组件停留在动画结束的位置上
    		ta.setFillAfter(true);
    		//播放动画
    		iv.startAnimation(ta);
    	}
    	//旋转动画
    	public void rotate(View v){
    		//20表示开始的角度,180表示结束的角度,默认的旋转圆心在iv左上角
    		//ra = new RotateAnimation(20, 180);
    		//指定圆心坐标,相对于自己, iv真实的坐标左上角 x+ 0.5*iv宽度, y+0.5*iv高度
    		ra = new RotateAnimation(20, 360, 
    				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    		
    		ra.setDuration(2000);
    		ra.setRepeatCount(1);
    		ra.setRepeatMode(Animation.REVERSE);
    		iv.startAnimation(ra);
    	}
    	//缩放动画
    	public void scale(View v){
    		//sa = new ScaleAnimation(fromX, toX, fromY, toY);
    		//改变缩放的中心点,相对于自己的中心坐标,iv的真是坐标左上角 x+0.5*iv宽度, y+0.5*iv高度
    		sa = new ScaleAnimation(0.5f, 2, 0.1f, 3, 
    				       Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    		sa.setDuration(2000);
    		sa.setRepeatCount(1);
    		sa.setRepeatMode(Animation.REVERSE);
    		iv.startAnimation(sa);
    	}
    	//透明动画
    	public void alpha(View v){
    		//0为完全透明,1为完全透明
    		aa = new AlphaAnimation(0, 1);
    		aa.setDuration(2000);
    		aa.setRepeatCount(1);
    		aa.setRepeatMode(Animation.REVERSE);
    		iv.startAnimation(aa);
    		
    	}
    	//所有动画一起飞
    	public void fly(View v){
    		//创建动画集合      false表示每个动画的时间校准有动画自己决定, true表示有动画集合决定
    		AnimationSet set = new AnimationSet(false);
    		set.addAnimation(aa);
    		set.addAnimation(ra);
    		set.addAnimation(sa);
    		set.addAnimation(ta);
    		iv.startAnimation(set);
    		
    	}
    	
    }
    

    四、属性动画

    • 补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变。
    • 属性动画是组件的位置发生了真实的改变,而且在动画的过程中组件的位置是实时改变的,可以相应组件事件。
    • 使用最多的就是 AnimatorSet和ObjectAnimator配合:
      • 使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值
      • 使多个ObjectAnimator组合到AnimatorSet形成一个动画
    • ObjectAnimator可以自动驱动:
      • 调用setFrameDelay()设置动画帧之间的间隙时间,调整帧率,减少动画绘制过程中频繁绘制,在不影响动画效果的情况下减少CPU资源消耗
    • 属性动画基本可以实现左右动画
    • 但是View的该属性一定要具有 set和get 方法

    1.ObjectAnimator

    • 内部是通过反射机制实现的,所以该属性一定要具有 set和get方法
    • 一些常用的可以直接使用的属性:
      • translationX 和translationY: 控制View从父容器的左上角的偏移
      • rotation、rotationX、rotationY : 控制View围绕 “支点”进行2D和3D旋转
      • scaleX、scaleY : 控制View围绕 “支点”进行2D缩放
      • pivotX、pivotY : 控制View的“支点”位置,默认为View的中心点
      • x、y : 这两个属性描述了View对象在父容器中的最终位置,它是最初左上角坐标和translationX 、translationY值的累计和
      • alpha : 透明度
     
    public class MainActivity extends Activity {
     
    	private ImageView iv;
     
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		iv = (ImageView) findViewById(R.id.iv);
    		iv.setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				Toast.makeText(MainActivity.this, "点不到我", 0).show();
    				
    			}
    		});
    	}
    	
    	public void translate(View v){
    		
    		//target:动画作用于哪个组件
    		
    		//protertyName指定要改变组件的哪个属性,    
    		//属性动画是真正的改变组件的属性的,每个组件对应一个java类,所以,protertyName是指组件Java类中具有get,set方法的成员变量
    		//iv.setXXX
    		
    		//values 是可变参数,就是赋予属性的新的值,可以往回走 
    		//ObjectAnimator oa = ObjectAnimator.ofFloat(target, propertyName, values);
    	
    		ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "translationX", 10, 70, 20, 100);
    		oa.setDuration(2000);
    		oa.setRepeatCount(1);
    		oa.setRepeatMode(ValueAnimator.REVERSE);
    		oa.start();
    		
    	}
    	public void scale(View v){
    		ObjectAnimator oa = ObjectAnimator.ofFloat(iv,"scaleX", 1, 1.6f, 1.2f, 2);
    		oa.setDuration(2000);
    		oa.start();
    			
    	}
    	public void alpha(View v){
    		ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "alpha", 0, 0.6f, 0.2f, 1);
    		oa.setDuration(2000);
    		oa.start();
    	}
    	public void rotate(View v){
    		ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "rotationY", 0, 180, 90, 360);
    		oa.setDuration(2000);
    		oa.setRepeatCount(1);
    		oa.setRepeatMode(ValueAnimator.REVERSE);
    		oa.start();
    	}
    	
    	public void fly(View v){
    		
    		AnimatorSet set = new AnimatorSet();
    		ObjectAnimator oa1 = ObjectAnimator.ofFloat(iv, "translationX", 10, 70, 20, 100);
    		oa1.setDuration(2000);
    		oa1.setRepeatCount(1);
    		oa1.setRepeatMode(ValueAnimator.REVERSE);
    		
    		ObjectAnimator oa2 = ObjectAnimator.ofFloat(iv, "translationY", 10, 70, 20, 100);
    		oa2.setDuration(2000);
    		oa2.setRepeatCount(1);
    		oa2.setRepeatMode(ValueAnimator.REVERSE);
    		
    		ObjectAnimator oa3 = ObjectAnimator.ofFloat(iv, "scaleX", 1, 1.6f, 1.2f, 2);
    		oa3.setDuration(2000);
    		oa3.setRepeatCount(1);
    		oa3.setRepeatMode(ValueAnimator.REVERSE);
    		
    		ObjectAnimator oa4 = ObjectAnimator.ofFloat(iv, "rotation", 0, 180, 90, 360);
    		oa4.setDuration(2000);
    		oa4.setRepeatCount(1);
    		oa4.setRepeatMode(ValueAnimator.REVERSE);
    		
    		//设置挨个飞
    		//set.playSequentially(oa1, oa2, oa3, oa4);
    		
    		//设置一起飞
    		set.playTogether(oa1, oa2, oa3, oa4);
    		
    		set.start();
    		
    	}
    	//使用xml文件配置属性动画
    	public void xml(View v){
    		//加载属性动画文件
    		 Animator animator = AnimatorInflater.loadAnimator(this, R.animator.objanimator);
    		 //设置作用于哪个组件
    		 animator.setTarget(iv);
    		 animator.start();
    	}
    }
    
    • 可以用xml配置属性动画只需要在res目录下创建一个property animator属性动画文件
    <set xmlns:android="http://schemas.android.com/apk/res/android" >
        <objectAnimator 
            android:propertyName="translationX"
    	    android:duration="200"
    	    android:repeatCount="1"
    	    android:repeatMode="reverse"
    	    android:valueFrom="-100"
    	    android:valueTo="100"
            >
            
        </objectAnimator>
     
    </set>
    

    2. 如果属性没有set 和 get方法的解决方法

    • 方案一: 通过自定义一个属性类或者包装类,类间接的给这个属性增加 get、set方法
     /**
     * Created at: 2016/8/5 14:21.
     * by author: mwp
     * 描述:使用属性动画时,给没有set和get方法的属性包装工具
     */
    public class WrapperView {
        private View mTarget;
    
        public WrapperView(View target) {
            this.mTarget = target;
        }
        
        /**包装宽度属性*/
        public int getWidth(){
            return mTarget.getLayoutParams().width;
        }
        
        public void setWidth(int width){
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
        
        //使用方法
        public void use(){
            WrapperView wrapper = new WrapperView(mButton);
            ObjectAnimator.ofInt(wrapper,"width",500).setDuration(5000).start();
        }
        
    }
    
    • 方案二: 通过ValueAnimator实现

    3. ValueAnimator(值动画)

    • ObjectAnimator 也是集成自ValueAnimator
    • ValueAnimator本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,调用者通过这个过程来执行自己特定的动画逻辑
    ValueAnimator animator = ValueAnimator.ofInt(0,100);
    animator.setTarget(view);
    animator.setDuration(1000).start();
    //最核心的方法
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 进度百分比
            float value = animation.getAnimatedFraction();
        }
    });
    

    4. 动画监听

    • 监听动画的过程执行一些操作
    ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha",0.5f);
    anim.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) {
    
        }
    });
    
    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animation) {
            super.onAnimationEnd(animation);
        }
    });
    
    • 这里边有一个重要的思想,如果一个接口需要实现的方法比较多,而通常时候又不需要实现那么多方法,导致代码乱乱的,这个时候,可以写一个抽象类来实现这个接口的所有方法,由于接口的实现类也是接口类型,所以使用的时候就可以只复写这个抽象类中感兴趣的方法就好了

    5. View的animate方法

    • 在3.0之后,Google给View增加了animate方法来直接驱动属性动画
    view.animate()
            .alpha(0)
            .scaleX(1)
            .x(300)
            .y(200)
            .setDuration(1000)
            .withStartAction(new Runnable() {
                @Override
                public void run() {
                    //动画开始
                }
            })
            .withEndAction(new Runnable() {
                @Override
                public void run() {
                    //动画结束
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            //执行一些主线程方法
                        }
                    });
                }
            })
            .start();
    

    6. Android 布局动画

    • 布局动画是指作用在ViewGroup上,给ViewGroup增加View时添加一个动画过渡效果
    • 最简单的布局动画是在ViewGroup的xml中使用如下属性打开布局动画
      • android:animateLayoutChanges="true"
      • 但是这个默认的动画效果无法替换
    • 使用LayoutAnimationController类来自定义一个过渡效果
    LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
    //设置过渡动画
    ScaleAnimation sa = new ScaleAnimation(0,1,0,1);
    sa.setDuration(2000);
    //设置布局动画的显示属性,第二个参数是每个子View显示的delay时间
    LayoutAnimationController lac = new LayoutAnimationController(sa,0.5f);
    lac.setOrder(LayoutAnimationController.ORDER_NORMAL);//顺序,随机,倒序
    //为ViewGroup设置布局动画
    ll.setLayoutAnimation(lac);
    

    7. Interpolators(插值器 )

    • 插值器是动画中一个非常重要的概念,通过插值器可以定义动画变换速率,其作用主要是控制目标变量的变化值进行对应的变化
    • AccelerateDecelerateInterpolator开始与结束的地方速率改变比较慢,在中间的时候加速
    • AccelerateInterpolator开始的地方速率改变比较慢,然后开始加速
    • AnticipateInterpolator开始的时候向后然后向前甩
    • AnticipateOvershootInterpolator开始的时候向后然后向前甩一定值后返回最后的值
    • BounceInterpolator动画结束的时候弹起
    • CycleInterpolator循环播放特定的次数,速率改变沿着正弦曲线
    • DecelerateInterpolator在开始的地方快然后慢
    • 创建的时候,可以传factor值,如DecelerateInterpolator(2f):
    • LinearInterpolator以常量速率改变
    • OvershootInterpolator向前甩一定值后再回到原来位置
    • 创建的时候,可以传tension值,OvershootInterpolator(0.8f):

    五、自定义动画

    • 就是现有的透明度,旋转,平移,缩放等行为组合起来仍然不能满足你的话,可以自定义一些更炫的动画
    public class CustomAnim extends Animation {
    
        private int mCenterWidth;
        private int mCenterHeight;
        private Camera mCamera = new Camera();
        private float mRotateY = 0.0f;
    
        @Override
        public void initialize(int width,
                               int height,
                               int parentWidth,
                               int parentHeight) {
    
            super.initialize(width, height, parentWidth, parentHeight);
            // 设置默认时长
            setDuration(2000);
            // 动画结束后保留状态
            setFillAfter(true);
            // 设置默认插值器
            setInterpolator(new BounceInterpolator());
            mCenterWidth = width / 2;
            mCenterHeight = height / 2;
        }
    
        // 暴露接口-设置旋转角度
        public void setRotateY(float rotateY) {
            mRotateY = rotateY;
        }
    
        @Override
        protected void applyTransformation(
                float interpolatedTime,
                Transformation t) {
            final Matrix matrix = t.getMatrix();
            mCamera.save();
            // 使用Camera设置旋转的角度
            mCamera.rotateY(mRotateY * interpolatedTime);
            // 将旋转变换作用到matrix上
            mCamera.getMatrix(matrix);
            mCamera.restore();
            // 通过pre方法设置矩阵作用前的偏移量来改变旋转中心
            matrix.preTranslate(mCenterWidth, mCenterHeight);
            matrix.postTranslate(-mCenterWidth, -mCenterHeight);
        }
    }
    

    六、Android 5.X SVG 矢量动画机制

    • 可伸缩矢量图形(Scalable Vector Graphics)
    • 定义用于网络的基于矢量的图形
    • 使用XML格式定义图形
    • 图像在放大或改变尺寸的情况下其图形质量不会有损失
    • 与Bitmap对比,SVG最大的优点就是方法不失真,而且不需要为不同分辨率设计多套图标

    七、点击view显示隐藏其他View带动画的一个小例子

    public class DropTest extends Activity {
    
        private LinearLayout mHiddenView;
        private float mDensity;
        private int mHiddenViewMeasuredHeight;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.drop);
            mHiddenView = (LinearLayout) findViewById(R.id.hidden_view);
            // 获取像素密度
            mDensity = getResources().getDisplayMetrics().density;
            // 获取布局的高度
            mHiddenViewMeasuredHeight = (int) (mDensity * 40 + 0.5);
        }
    
        public void llClick(View view) {
            if (mHiddenView.getVisibility() == View.GONE) {
                // 打开动画
                animateOpen(mHiddenView);
            } else {
                // 关闭动画
                animateClose(mHiddenView);
            }
        }
    
        private void animateOpen(final View view) {
            view.setVisibility(View.VISIBLE);
            ValueAnimator animator = createDropAnimator(
                    view,
                    0,
                    mHiddenViewMeasuredHeight);
            animator.start();
        }
    
        private void animateClose(final View view) {
            int origHeight = view.getHeight();
            ValueAnimator animator = createDropAnimator(view, origHeight, 0);
            animator.addListener(new AnimatorListenerAdapter() {
                public void onAnimationEnd(Animator animation) {
                    view.setVisibility(View.GONE);
                }
            });
            animator.start();
        }
    
        private ValueAnimator createDropAnimator(
                final View view, int start, int end) {
            ValueAnimator animator = ValueAnimator.ofInt(start, end);
            animator.addUpdateListener(
                    new ValueAnimator.AnimatorUpdateListener() {
    
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    int value = (Integer) valueAnimator.getAnimatedValue();
                    ViewGroup.LayoutParams layoutParams =
                            view.getLayoutParams();
                    layoutParams.height = value;
                    view.setLayoutParams(layoutParams);
                }
            });
            return animator;
        }
    }
    
  • 相关阅读:
    Two Sum II
    Subarray Sum
    Intersection of Two Arrays
    Reorder List
    Convert Sorted List to Binary Search Tree
    Remove Duplicates from Sorted List II
    Partition List
    Linked List Cycle II
    Sort List
    struts2结果跳转和参数获取
  • 原文地址:https://www.cnblogs.com/rocomp/p/5742056.html
Copyright © 2020-2023  润新知