属性动画实战一

安卓中动画有帧动画、补间动画以及属性动画。
帧动画:循环重复播放多张图片;
补间动画:对作用对象的某个属性进行操作,比如位移,缩放,透明度,旋转;
属性动画:同补间动画,区别在于,属性动画能够真实改变作用对象的属性位置,补间动画,虽然改变属性,但实际上并没有改变作用对象的属性位置。
今天实战内容:页面加载动画,仿五八同城加载。
实现思路:
1、三个控件
1.1、形状变化控件 ShapeView。绘制圆形,三角形,正方形,同时不断变化这三个形状
1.2、阴影控件 View。根据形状控件的上升或者下落,不断进行阴影控件的缩放
1.3、文字显示控件TextView。显示文字提示
2、形状控件圆形,三角形以及正方形的绘制
3、形状控件,上升,下落以及上升翻转动画
4、阴影控件缩放
5、内存优化,数据加载完成后,需要移除动画以及加载布局,避免内存泄露

实现代码:

  /**
    指定控件的宽高,保证控件是一个正方形
  */
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //只保证正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(Math.min(width,height),Math.min(width,height));
    }

绘制形状控件中三角形、圆形以及正方形

 @Override
    protected void onDraw(Canvas canvas) {
        switch (mCurrentShape){
            case SHAPE_CIRCLE:
                //画圆形
                int center = getWidth()/2;
                mPaint.setColor(Color.YELLOW);
                canvas.drawCircle(center,center,center,mPaint);
                break;
            case SHAPE_RECT:
                //画正方形
                mPaint.setColor(Color.BLUE);
                canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
                break;
            case SHAPE_TRIANGLE:
                //画三角形
                mPaint.setColor(Color.RED);
                if (mPath == null){
                    //画路径
                    mPath = new Path();
                    float triangleBx,triangleBy,triangleCx,triangleCy;
                    int triangleCenter = getWidth()/2;
                    Log.e("ShapeView","cos30="+Math.cos(Math.sqrt(3)/2)
                    +"\nsin30="+Math.sin(0.5));
                    triangleBx = (float) ((1-Math.sqrt(3)/2)*triangleCenter);
                    triangleBy = (float) ((1+0.5)*triangleCenter);
                    triangleCx = (float) ((1+Math.sqrt(3)/2)*triangleCenter);
                    triangleCy = (float) ((1+0.5)*triangleCenter);
                    mPath.moveTo(triangleCenter,0);
                    mPath.lineTo(triangleBx,triangleBy);
                    mPath.lineTo(triangleCx,triangleCy);
                    mPath.close();
                }
                canvas.drawPath(mPath,mPaint);
                break;
        }
    }

形状控件上升动画

 /**
     * 开始上升动画
     */
    private void startUpAnimator() {

        if (mIsStopAnimator){
            return;
        }
        Log.e("LoadingView","startUpAnimator2");
        //形状控件,上升
        //阴影控件,放大
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(ANIMATION_TIME);

        animatorSet.setInterpolator(new DecelerateInterpolator());
        animatorSet.playTogether(getAnimator(mShapeView,"translationY",DISTANCE,0),
                getAnimator(mShaDomView,"scaleX",0.4f,1f),
                getAnimator(mShaDomView,"scaleY",0.4f,1f));
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                //上升结束后开启下落动画
                startFallAnimator();

            }
            @Override
            public void onAnimationStart(Animator animation) {
               // 上升的同时进行形状翻转动画
                startRotation();
            }
        });
        animatorSet.start();
    }

上升的同时进行形状翻转动画

/**
     * 上升时,动画需要进行形状翻转
     */
    private void startRotation() {
        ObjectAnimator animator = null;
        //判断当前形状
        switch (mShapeView.getCurrentShape()){
            case SHAPE_CIRCLE:
            case SHAPE_RECT:
                //圆形,正方形,则翻转180度
                animator = getAnimator(mShapeView,"rotation",0,180);
                break;
            case SHAPE_TRIANGLE:
                //三角形则翻转120度
                animator = getAnimator(mShapeView,"rotation",0,120);
                break;
        }
        animator.setDuration(ANIMATION_TIME);
        animator.start();
    }

下落动画

/**
     * 开始下降动画
     */
    private void startFallAnimator() {

        if (mIsStopAnimator){
            Log.e("LoadingView","startFallAnimator1");
            return;
        }
        Log.e("LoadingView","startFallAnimator2");
        //形状控件,下降
        //阴影控件,缩小
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(ANIMATION_TIME);
        animatorSet.setInterpolator(new AccelerateInterpolator());
        animatorSet.playTogether(getAnimator(mShapeView,"translationY",0,DISTANCE),
                getAnimator(mShaDomView,"scaleX",1f,0.4f),
                getAnimator(mShaDomView,"scaleY",1f,0.4f));
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                //下落结束后改变形状
                mShapeView.exchange();
                //下落结束后开启上升动画
                startUpAnimator();

            }

        });
        animatorSet.start();

    }

阴影控件缩放动画

 getAnimator(mShaDomView,"scaleX",1f,0.4f);
 getAnimator(mShaDomView,"scaleY",1f,0.4f));
  /**
     * 创造一个属性动画对象
     * @param target
     * @param propertyName
     * @param values
     * @return
     */
    private ObjectAnimator getAnimator(Object target, String propertyName, float... values){
        return ObjectAnimator.ofFloat(target,propertyName,values);
    }

优化实现

 @Override
    public void setVisibility(int visibility) {
        //不管是 GONE 还是 INVISIBLE 都设置成 INVISIBLE ,如果是 GONE 的话,需要加载系统测量代码
        super.setVisibility(INVISIBLE);
        //优化处理
        mShapeView.clearAnimation();
        mShaDomView.clearAnimation();

        //移除加载布局
        ViewGroup parent = (ViewGroup) getParent();
        if (parent !=null){
            parent.removeView(this);
        }
        removeAllViews();
        mIsStopAnimator = true;
    }

完整代码实现:
一、LoadingView

/**
 * @author yifuyun
 * @date 2020/5/15
 */
public class LoadingView extends LinearLayout {
    /**
     * 阴影形状
     */
    private final View mShaDomView;
    /**
     * 形状控件
     */
    private final ShapeView mShapeView;

    //动画时间
    private final static long ANIMATION_TIME = 800;
    //形状控件上升或者下降高度
    private int DISTANCE = 0;

    //是否停止加载动画
    private boolean mIsStopAnimator;

    private AnimatorSet mAnimatorSet;

    //存储集合动画列表
    private List<Animator> mAnimatorList;

    //上升时的翻转动画
    private ObjectAnimator mRotationAnimator;

    public LoadingView(Context context) {
        this(context, null);
    }

    public LoadingView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        inflate(context, R.layout.layout_custom_load_view, this);
        mShaDomView = findViewById(R.id.shaDomView);
        mShapeView = findViewById(R.id.shapeView);

        //开始下降动画
        post(() -> {
            DISTANCE = mShapeView.getBottom() / 4;
            startFallAnimator();
        });

    }

    /**
     * 将dp 转换成 px
     *
     * @param dip
     * @return
     */
    private int dp2px(int dip) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, dip, getResources().getDisplayMetrics());
    }

    /**
     * 开始下降动画
     */
    private void startFallAnimator() {

        if (mIsStopAnimator) {
            Log.e("LoadingView", "startFallAnimator1");
            return;
        }
        Log.e("LoadingView", "startFallAnimator2");
        //形状控件,下降
        //阴影控件,缩小
        if (mAnimatorSet == null) {
            mAnimatorSet = new AnimatorSet();
            mAnimatorSet.setDuration(ANIMATION_TIME);
            mAnimatorSetListener = new AnimatorSetListener();
            mAnimatorSet.addListener(mAnimatorSetListener);
            mAnimatorList = new ArrayList<>();
            mAnimatorList.add(getAnimator(mShapeView, "translationY", 0, DISTANCE));
            mAnimatorList.add(getAnimator(mShaDomView, "scaleX", 1f, 0.4f));
            mAnimatorList.add(getAnimator(mShaDomView, "scaleY", 1f, 0.4f));

        } else {
            setFloatValues(0, 0, DISTANCE);
            setFloatValues(1, 1f, 0.4f);
            setFloatValues(2, 1f, 0.4f);
        }
        mAnimatorSet.setInterpolator(new AccelerateInterpolator());
        mAnimatorSetListener.setMoveDirection(AnimatorSetListener.DIRECTION_FALL);
        mAnimatorSet.playTogether(mAnimatorList);
        mAnimatorSet.start();

    }

    private AnimatorSetListener mAnimatorSetListener;

    /**
     * 集合动画监听
     */
    private class AnimatorSetListener extends AnimatorListenerAdapter {
        private int moveDirection;
        private final static int DIRECTION_UP = 0;
        private final static int DIRECTION_FALL = 1;


        public void setMoveDirection(int moveDirection) {
            this.moveDirection = moveDirection;
        }

        @Override
        public void onAnimationEnd(Animator animation, boolean isReverse) {
            switch (moveDirection) {
                case DIRECTION_UP:
                    startFallAnimator();
                    break;
                case DIRECTION_FALL:
                    startUpAnimator();
                    break;
            }
        }

        @Override
        public void onAnimationStart(Animator animation, boolean isReverse) {
            //如果是方向上升,需要进行翻转动画
            if (moveDirection == DIRECTION_UP) {
                //改变形状
                mShapeView.exchange();
                //启动翻转动画
                startRotation();
            }
        }
    }

    /**
     * 重置value 值
     *
     * @param index
     * @param values
     */
    private void setFloatValues(int index, float... values) {
        if (mAnimatorList.get(index) instanceof ObjectAnimator) {
            ObjectAnimator objectAnimator = (ObjectAnimator) mAnimatorList.get(index);
            objectAnimator.setFloatValues(values);
        }
    }

    /**
     * 上升时,动画需要进行形状翻转
     */
    private void startRotation() {
        //判断当前形状
        switch (mShapeView.getCurrentShape()) {
            case SHAPE_CIRCLE:
            case SHAPE_RECT:
                //圆形,正方形,则翻转180度
                startRotationAnimator(0, 180);
                break;
            case SHAPE_TRIANGLE:
                //三角形则翻转120度
                startRotationAnimator(0, 120);
                break;
        }
    }

    /**
     * 设置旋转动画的value内容
     *
     * @param values
     */
    private void startRotationAnimator(float... values) {
        if (mRotationAnimator == null) {
            mRotationAnimator = getAnimator(mShapeView, "rotation", values);
            mRotationAnimator.setDuration(ANIMATION_TIME);
        } else {
            mRotationAnimator.setFloatValues(values);
        }
        mRotationAnimator.start();
    }

    /**
     * 开始上升动画
     */
    private void startUpAnimator() {
        if (mIsStopAnimator) {
            Log.e("LoadingView", "startUpAnimator1");
            return;
        }
        Log.e("LoadingView", "startUpAnimator2");
        //形状控件,上升
        //阴影控件,放大
        setFloatValues(0, DISTANCE, 0);
        setFloatValues(1, 0.4f, 1f);
        setFloatValues(2, 0.4f, 1f);
        mAnimatorSetListener.setMoveDirection(AnimatorSetListener.DIRECTION_UP);
        mAnimatorSet.setInterpolator(new DecelerateInterpolator());
        mAnimatorSet.start();
    }

    /**
     * 创造一个属性动画对象
     *
     * @param target
     * @param propertyName
     * @param values
     * @return
     */
    private ObjectAnimator getAnimator(Object target, String propertyName, float... values) {
        return ObjectAnimator.ofFloat(target, propertyName, values);
    }

    @Override
    public void setVisibility(int visibility) {
        //不管是 GONE 还是 INVISIBLE 都设置成 INVISIBLE ,如果是 GONE 的话,需要加载系统测量代码
        super.setVisibility(INVISIBLE);
        //优化处理
        mShapeView.clearAnimation();
        mShaDomView.clearAnimation();

        //移除加载布局
        ViewGroup parent = (ViewGroup) getParent();
        if (parent != null) {
            parent.removeView(this);
        }
        removeAllViews();
        mIsStopAnimator = true;
    }

    /**
     * 移除动画监听,避免内存泄露
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        //移除动画监听
        if (mAnimatorSet != null) {
            mAnimatorSet.removeAllListeners();
            mAnimatorSet = null;
            mAnimatorSetListener = null;
        }
        if (mRotationAnimator != null) {
            mRotationAnimator = null;
        }

    }
}

二、layout_custom_load_view布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <com.yfy.lib_custom_view.view.shape_animation.ShapeView
        android:id="@+id/shapeView"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginBottom="90dp"
         />

    <View
        android:id="@+id/shaDomView"
        android:layout_width="25dp"
        android:layout_height="4dp"
        android:background="@drawable/loading_shape_bg"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:textColor="#757575"
        android:textSize="14sp"
        android:text="玩命加载中..."/>

</LinearLayout>

三、ShapeView

/**
 * @author yifuyun
 * @date 2020/5/15
 */
public class ShapeView extends View {

    private final Paint mPaint;
    private Shape mCurrentShape = Shape.SHAPE_CIRCLE;
    private Path mPath;

    public ShapeView(Context context) {
        this(context,null);
    }

    public ShapeView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ShapeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //只保证正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(Math.min(width,height),Math.min(width,height));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        switch (mCurrentShape){
            case SHAPE_CIRCLE:
                //画圆形
                int center = getWidth()/2;
                mPaint.setColor(Color.YELLOW);
                canvas.drawCircle(center,center,center,mPaint);
                break;
            case SHAPE_RECT:
                //画正方形
                mPaint.setColor(Color.BLUE);
                canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
                break;
            case SHAPE_TRIANGLE:
                //画三角形
                mPaint.setColor(Color.RED);
                if (mPath == null){
                    //画路径
                    mPath = new Path();
                    float triangleBx,triangleBy,triangleCx,triangleCy;
                    int triangleCenter = getWidth()/2;
                    Log.e("ShapeView","cos30="+Math.cos(Math.sqrt(3)/2)
                    +"\nsin30="+Math.sin(0.5));
                    triangleBx = (float) ((1-Math.sqrt(3)/2)*triangleCenter);
                    triangleBy = (float) ((1+0.5)*triangleCenter);
                    triangleCx = (float) ((1+Math.sqrt(3)/2)*triangleCenter);
                    triangleCy = (float) ((1+0.5)*triangleCenter);
                    mPath.moveTo(triangleCenter,0);
                    mPath.lineTo(triangleBx,triangleBy);
                    mPath.lineTo(triangleCx,triangleCy);
                    mPath.close();
                }
                canvas.drawPath(mPath,mPaint);
                break;
        }
    }

    /**
     * 获取当前形状
     * @return
     */
    public Shape getCurrentShape() {
        return mCurrentShape;
    }


    public enum Shape {
        SHAPE_TRIANGLE, SHAPE_RECT, SHAPE_CIRCLE
    }

    /**
     * 改变形状
     */
    public void exchange(){
        switch (mCurrentShape){
            case SHAPE_CIRCLE:
                mCurrentShape = Shape.SHAPE_RECT;
                break;
            case SHAPE_RECT:
                mCurrentShape = Shape.SHAPE_TRIANGLE;
                break;
            case SHAPE_TRIANGLE:
                mCurrentShape = Shape.SHAPE_CIRCLE;
                break;
        }
        //刷新显示,不断从新绘制
        invalidate();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339