• 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;
        }
    }
    
  • 相关阅读:
    GISer 应届生找工作历程(完结)
    c#跨窗体调用操作
    c#基础学习笔记-----------委托事件
    c#基础笔记-----------集合
    ArcEngine开发鹰眼实现问题
    Null Object模式
    c#基础------------静态类与非静态类
    GIS初学者
    c#基础学习汇总----------base和this,new和virtual
    用Python编写水仙花数
  • 原文地址:https://www.cnblogs.com/rocomp/p/5742056.html
Copyright © 2020-2023  润新知