关于Android动画的一点愚见

这一段时间在看Activity的工作流程,奈何内容太多,涉及到的东西有点多,暂时放下这一篇,慢慢来。先总结一下自己学过的Android自定义动画与属性动画。

Android的View动画分为两类变换动画(Tweened Animation)以及帧动画(Frame by-frame Animation)。

变换动画又分为四大类:平移(translate),旋转(rotate),缩放(scale),透明度(alpha)。

View动画的使用

View动画的编程有两种方法:一种是通过xml中编写,另一种在Java代码中编程控制。
在系统支持xml的view动画中只支持ttranslate,rotate,scale,alpha四种标签,下面是一个实例:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:zAdjustment="normal">

    <translate
        android:duration="100"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toXDelta="100"
        android:toYDelta="0"/>

    <rotate
        android:duration="400"
        android:fromDegrees="0"
        android:toDegrees="45"/>

</set>

代码则是这么调用:

        Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim1);
        Img.startAnimation(animation);

在这里面,值得一提的是:属性android:interpolator插值器,这是指动画的是按照怎么变化规律变化的,,比如线性变化,加速变化等等,就像初中物理那样,一个物体在做匀速运动还是变速运动。

除了使用xml配置之外,还可以使用代码配置并且控制:

                AlphaAnimation alphaAnimation = new AlphaAnimation(1, (float) 0.1);
                alphaAnimation.setDuration(3000);
                Img.startAnimation(alphaAnimation);

思路都是一样的,不过只是在xml调用的时候,声明的Animation需要调用AnimationUtils.loadAnimation(Context context,int id);代码直接声明相应类型的动画类型即可。

还有一点,那就是Activity切入切出时的动画设置,必须调用overridePendingTransition(int enterAnim,int exitAnim)。

自定义View动画

自定义View动画,我需要继承一个Animation的类,再通过矩阵变换来转换其中的动画效果。有一位大神已经通过矩阵的方式将Animation解读了一遍,让我受益良多。

这里是地址:Android动画的矩阵运用

在自定义View动画的时候,Camera类经常被用到,它相当于是一个摄像机一样从不同的角度来观察对象,其结果可以简化矩阵变换的过程。同时也要重写两个方法:
1.public void initialize(int width,int height,int parentWidth,int parentHeight)是用来初始化一些数值或者类。
2.protected void applyTransformation(float interpolateTime,Transformation t)这是用来编写矩阵的变换的。

让我们编写一个3D旋转的自定义动画吧。

public class Roate3dAnimation extends Animation{

    private final float mFromDegrees;
    private final float mToDegress;
    private final float mCenterX;
    private final float mCenterY;
    private final float mDepthZ;
    private final boolean mReverse;
    private Camera mCamera;

    //构造器获取初始角度,变化都角度,获取中心的点的x,y,z坐标
    public Roate3dAnimation(float fromDegress,float toDegress,float centerX,
            float centerY,float depthZ,boolean reverse){
        mFromDegrees = fromDegress;
        mToDegress = toDegress;
        mCenterX = centerX;
        mCenterY = centerY;
        mDepthZ = depthZ;
        mReverse = reverse;
    }

    //初始化Camera
    @Override
    public void initialize(int width,int height,int parentWidth,int parentHeight){
        super.initialize(width, height, parentWidth, parentHeight);
        mCamera = new Camera();
    }

    @Override
    protected void applyTransformation(float interpolatedTime,Transformation t){
        final float fromDegress = mFromDegrees;
        //角度变化随着时间变化而变化:当前角度=起始角度+((结束角度-起始角度)*角度/每秒)
        float degress = fromDegress + ((mToDegress - fromDegress) * interpolatedTime);

        final float centerX = mCenterX;
        final float centerY = mCenterY;
        final Camera camera = mCamera;
        //获取此时的矩阵
        final Matrix matrix = t.getMatrix();
        //camera保存此时camera状态
        camera.save();
        //mReverse是指是反向旋转还是正向旋转
        if(mReverse){
            camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
        }else {
            camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
        }
        //camera对象额旋转
        camera.rotateY(degress);
        //获取旋转后的矩阵
        camera.getMatrix(matrix);
        camera.restore();

        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);


    }


}

上面就是通过camera简化了自己计算矩阵的过程。调用对象还是和之前一样:

        Roate3dAnimation dAnimation = new Roate3dAnimation(0, 180, 50, 50, 1, true);
        dAnimation.setDuration(3000);
        img.startAnimation(dAnimation);

到这里就说完了自定义View动画了,现在已经和少用这种动画,在API11之后Android加入了强大的动画器:属性动画,来更加简单的的完成更加绚烂多彩的动画了。

属性动画

属性动画,顾名思义,这不是只针对View的动画,而是可以对任意一个对象的进行动画操作,比如说,View动画就很难操作Button中的宽度,而属性动画却能弥补这些缺点。

属性动画中我们常用这么几个类:ValueAnimator,ObjectAnimator,AnimatorSet等。直接让我们看一个例子吧:

        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(img, "rotationX", 0.0f,306.0f),
                ObjectAnimator.ofFloat(img, "rotationY", 0.0f,180.0f),
                ObjectAnimator.ofFloat(img, "rotation", 0.0f,-90.0f),
                ObjectAnimator.ofFloat(img, "translationX", 0.0f,90.0f)
                );
        set.setDuration(5000).start();

从上面这这一段代码,可以清晰的认知到,AnimatorSet是作为一个动画的集合,ObjectAnimator则是通过反射获取对象的属性来变化的。在这里我们值得注意的是第二个参数就是指我们要变化的属性,但是如果对象中没有该属性的get函数和set函数,动画将不会生效。
就会出现如下警告:

Method setX() with type int not found on target class class android.widget.Button

我们同样可以配置xml来处理属性动画:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially" >
    <objectAnimator
        android:propertyName="RotationX"
        android:duration="300"
        android:valueTo="90"
        android:valueType="intType"/>

    <objectAnimator
        android:propertyName="RotationY"
        android:duration="300"
        android:valueTo="90"
        android:valueType="intType"/>

</set>

但是用xml来配置属性动画有个缺点就是,很多身后我们无法知道属性的起始值会导致我们的动画出现一些意外的情况,如果用xml来配置,除非提前知道这些值,我们提倡使用代码控制。
还有一个坑,那就是即使引用了本身View里面存在的属性获取,设置的函数还是有可能获取不到。我暂时在源码里面看不出原因。
所以基于以上种种问题,属性动画还是使用代码控制比较好。

那么我们遇到了,对象没有提供set,get的方法的时候,难道就素手无策了吗?不,下面提供三种方案,来解决:

  1. 我们可以获取更高级的权限来修改对象中源码,给相应的对象加上set,get的方法。这是最简单直接的方法,但是比如说Button,这是SDK中实现的,我们往往不可能因此而重新编译一个Android来。
  2. 用一个类来包装原始对象,间接的提供get和set方法。比如说:
    private static class ViewWrapper{
        private View mTarget;
        public ViewWrapper(View target){
            mTarget = target;
        }

        public int getWidth(){
            return mTarget.getLayoutParams().width;
        }

        public void setWidth(int width){
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }
  1. 我们可以使用ValueAnimator来监听动画的过程,实现自己的属性的修改,ValueAnimator是ObjectAnimator的父类,它并没有指定对象确提供了修改对象属性的抽象类,我们往往可以调用valueAnimator.addUpdateListener(new AnimatorUpdateListner(){})的方法,下面是例子:
    private void performAnimate(final View target,final int start,final int end){
        //valueAnimator动画进度条范围
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            //整形插值器的声明
            private IntEvaluator mEvaluator = new IntEvaluator();
            @Override
            public void onAnimationUpdate(ValueAnimator anim) {
                // TODO Auto-generated method stub
                //获取此时动画的进度条
                int currentvalue = (Integer)anim.getAnimatedValue();
                Log.e("currentvalue", ""+currentvalue);
                //获取此时进度条占总进度条的百分比
                float fraction = anim.getAnimatedFraction();
                //相应属性的变化
                target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
                //刷新对象UI
                target.requestLayout();
            }
        });
        valueAnimator.setDuration(5000).start();
    }

到这里就结束了,属性动画的全部内容,为了检验自己是否明白,自己模仿了知乎的目录动画,做了一个简单的属性动画效果。下面是动画的核心源码:

public class MyFloatMenu extends RelativeLayout{

    private ImageView menu;
    private ImageView bell;
    private ImageView wallet;
    private ImageView shopping;
    private ImageView wait_use;
    private View bellView;
    private View waitView;
    private View walletView;
    private View shopView;
    private boolean isOpen = false;

    public MyFloatMenu(Context context){
        super(context);
        init(context);
    }

    public MyFloatMenu(Context context,AttributeSet attr){
        super(context,attr);
        init(context);
    }

    public MyFloatMenu(Context context,AttributeSet attr,int defStyle){
        super(context, attr, defStyle);
        init(context);
    }

    private void init(final Context context){
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.menu, this);
        menu = (ImageView)view.findViewById(R.id.plus_menu);
        bellView = inflater.inflate(R.layout.bell, null);
        waitView = inflater.inflate(R.layout.wait, null);
        walletView = inflater.inflate(R.layout.wallet, null);
        shopView = inflater.inflate(R.layout.shopping, null);
        this.addView(bellView);
        this.addView(waitView);
        this.addView(walletView);
        this.addView(shopView);
        bellView.setVisibility(View.GONE);
        waitView.setVisibility(View.GONE);
        walletView.setVisibility(View.GONE);
        shopView.setVisibility(View.GONE);
        menu.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                setVisible();
                if(isOpen == false){
                    perform();
                    isOpen = true;
                }else if (isOpen == true) {
                    backperform();
                    isOpen = false;
                }

            }
        });

        bellView.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent intent = new Intent(context, testActivity.class);
                context.startActivity(intent);
            }
        });
    }


    private void setVisible(){
        bellView.setVisibility(View.VISIBLE);
        waitView.setVisibility(View.VISIBLE);
        walletView.setVisibility(View.VISIBLE);
        shopView.setVisibility(View.VISIBLE );
    }

    public void perform(){
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(menu, "rotation", 45.0f),
                ObjectAnimator.ofFloat(bellView, "translationX", 300f),
                ObjectAnimator.ofFloat(waitView, "rotationZ", 30.0f),
                ObjectAnimator.ofFloat(waitView, "translationY", 150.0f),
                ObjectAnimator.ofFloat(waitView, "translationX", 259.8f),
                ObjectAnimator.ofFloat(walletView, "rotationZ", 60.0f),
                ObjectAnimator.ofFloat(walletView, "translationX", 150.0f),
                ObjectAnimator.ofFloat(walletView, "translationY", 259.8f),
                ObjectAnimator.ofFloat(shopView, "translationY", 300f));
        set.setDuration(500).start();
    }

    public void backperform(){
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(menu, "rotation", 90.0f),
                ObjectAnimator.ofFloat(bellView, "translationX", -300f),
                ObjectAnimator.ofFloat(waitView, "rotationZ", -30.0f),
                ObjectAnimator.ofFloat(waitView, "translationY", -150.0f),
                ObjectAnimator.ofFloat(waitView, "translationX", -259.8f),
                ObjectAnimator.ofFloat(walletView, "rotationZ", -60.0f),
                ObjectAnimator.ofFloat(walletView, "translationX", -150.0f),
                ObjectAnimator.ofFloat(walletView, "translationY", -259.8f),
                ObjectAnimator.ofFloat(shopView, "translationY", -300f));
        set.setDuration(500).start();
    }

    @Override
    protected void onLayout(boolean change, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        super.onLayout(change, l, t, r, b);
    }

}

感谢任玉刚大神的android开发探索艺术以及郭霖大神的博客。

这里附上Github工程的源码:Github

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
锐龙处理器是一款性能出色的处理器,适合用于运行PyCharm这样的开发环境。由于锐龙处理器具有强大的多核性能和高频率,它能够提供出色的计算能力和响应速度,以满足开发人员的需求。同时,锐龙处理器还支持多线程处理,可以更好地利用系统资源,提高程序的执行效率。 在运行PyCharm时,锐龙处理器能够提供稳定的性能和流畅的使用体验。无论是编写代码、调试程序还是运行复杂的算法,锐龙处理器都能够保持良好的性能表现。同时,锐龙处理器还具有较低的能耗和良好的散热性能,可以在长时间的使用中保持稳定的性能。 总之,锐龙处理器是一款适合跑PyCharm的处理器,它能够提供强大的计算能力和响应速度,帮助开发人员高效地进行编程工作。无论是开发小型项目还是运行大型应用,锐龙处理器都能够提供优秀的性能表现。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [一个小白对当前锐龙4000笔记本的愚见](https://blog.csdn.net/weixin_39823299/article/details/113723107)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [我的NVIDIA开发者之旅——深度学习入门之Anaconda搭建PyTorch GPU、CPU全教程](https://blog.csdn.net/qq_53904578/article/details/124157557)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值