红橙Darren视频笔记 圆点loadingView 动画ANR

效果
在这里插入图片描述

思路

思路比较简单 先自定义view 该view只是一个圆,可以设置绘制的颜色。再自定义一个ViewGroup,在里面放三个之前自定义好的view。初始化的部分就完成了。下面接着看动画部分,动画可以分为两部分,一部分是向外移动 一部分是向内移动,这里使用属性动画+AnimatorSet很容易实现,接着就是监听动画执行完毕,执行向内移动的动画,向内移动的动画结束执行向外移动的动画,循环执行。注意需要提供一个方法,在加载完毕的时候停止动画释放资源。

遇到的坑

1.crash问题

2021-01-09 15:43:03.653 21938-21938/com.example.circleloadingview E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.circleloadingview, PID: 21938
    java.lang.NullPointerException: Attempt to get length of null array
        at android.animation.ValueAnimator.initAnimation(ValueAnimator.java:555)
        at android.animation.ObjectAnimator.initAnimation(ObjectAnimator.java:894)
        at android.animation.ValueAnimator.startAnimation(ValueAnimator.java:1231)
        at android.animation.ValueAnimator.start(ValueAnimator.java:1041)
        at android.animation.ValueAnimator.start(ValueAnimator.java:1065)
        at android.animation.ObjectAnimator.start(ObjectAnimator.java:852)
        at android.animation.ValueAnimator.startWithoutPulsing(ValueAnimator.java:1058)
        at android.animation.AnimatorSet.handleAnimationEvents(AnimatorSet.java:1142)
        at android.animation.AnimatorSet.startAnimation(AnimatorSet.java:1227)
        at android.animation.AnimatorSet.start(AnimatorSet.java:729)
        at android.animation.AnimatorSet.start(AnimatorSet.java:684)
        at com.example.circleloadingview.CircleLoadingView.startInAnimate(CircleLoadingView.java:106)
        at com.example.circleloadingview.CircleLoadingView.lambda$nASjswge2JPrJsLIt_Asff7ch5s(Unknown Source:0)

原因出在创建ObjectAnimator的方式上
不应该这样创建

            ObjectAnimator translationRightIn = new ObjectAnimator();
            translationRightIn.ofFloat(mCircleRight, "translationX", mAnimateDistance, 0);

而应该这样创建

ObjectAnimator translationRightIn = ObjectAnimator.ofFloat(mCircleRight, "translationX", mAnimateDistance, 0);

其实Android studio一开始就有警告了
Static member ‘android.animation.ObjectAnimator.ofFloat(java.lang.Object, java.lang.String, float…)’ accessed via instance reference
只不过被我忽视了,自己坑自己…

2.复用AnimatorSet时遇到问题

    private void innerAnimation() {
        // 左边跑
        ObjectAnimator leftTranslationAnimator = ObjectAnimator.ofFloat(mLeftView,"translationX",-mTranslationDistance,0);
        // 右边跑
        ObjectAnimator rightTranslationAnimator = ObjectAnimator.ofFloat(mRightView,"translationX",mTranslationDistance,0);
        AnimatorSet set = new AnimatorSet();
        set.setInterpolator(new AccelerateInterpolator());
        set.setDuration(ANIMATION_TIME);
        set.playTogether(leftTranslationAnimator,rightTranslationAnimator);
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // 往里外面跑
                // 切换颜色顺序  左边的给中间 中间的给右边  右边的给左边
                int leftColor = mLeftView.getColor();
                int rightColor = mRightView.getColor();
                int middleColor = mMiddleView.getColor();
                mMiddleView.exchangeColor(leftColor);
                mRightView.exchangeColor(middleColor);
                mLeftView.exchangeColor(rightColor);
                expendAnimation();
            }
        });
        set.start();
    }

这是视频里面的代码 我看到执行动画每次都会创建一个AnimatorSet,于是想将他抽成类变量,结果动画出现卡顿,之后竟然ANR了,后面发现虽然我把AnimatorSet抽成类变量,但是每次动画都会执行set.setInterpolator set.setDuration set.playTogether set.addListener很大可能是这里出错了,后面我加了判断,只有初始化的时候才执行就OK了

3.其他优化

利用AnimatorSet的reverse方法 可以只使用一个动画 另外一个动画反过来执行就可以,不过这样用起来逻辑可能比较混乱,有时候代码复用之后,逻辑会变得没有原来清晰,这时候,是否复用代码就见仁见智了.
这里我虽然发现可以使用AnimatorSet的reverse方法减少很多冗余代码,但是为了让逻辑清晰,我最终放弃了这种方案.

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void startInAnimate() {
        Log.d(TAG, "startInAnimate: ");
        if (animatorSet4In == null) {
            animatorSet4In = new AnimatorSet();
            ObjectAnimator translationLeftIn = ObjectAnimator.ofFloat(mCircleLeft, "translationX", -mAnimateDistance, 0);
            ObjectAnimator translationRightIn = ObjectAnimator.ofFloat(mCircleRight, "translationX", mAnimateDistance, 0);
            animatorSet4In.setInterpolator(new AccelerateInterpolator(2f));
            animatorSet4In.playTogether(translationLeftIn, translationRightIn);
            animatorSet4In.setDuration(mAnimateDuration);
            animatorSet4In.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation, boolean isReverse) {
                    i++;
                    if (i % 2 != 0) {
                        exchangeColor();
                        animatorSet4In.reverse();
                        Log.d(TAG, "startInAnimate reverse: "+i);
                    } else {
                        Log.d(TAG, "startInAnimate start: "+i);
                        animatorSet4In.start();
                    }

                }
            });
        }
        Log.d(TAG, "startInAnimate first: "+i);
        animatorSet4In.start();
    }

最终代码

自定义View

class SingleCircle extends View {
    private Paint mPaint;
    private int mRadius;
    private int mColor;

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

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

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

    @Override
    protected void onDraw(Canvas canvas) {
        //onMeasure onLayout onDraw的执行顺序 此时可以知道测量宽高
        mRadius = Math.min(getMeasuredHeight(), getMeasuredWidth()) / 2;
        canvas.drawCircle(getMeasuredWidth() >> 1, getMeasuredHeight() >> 1, Util.dip2px(mRadius, getContext()), mPaint);
    }

    public void changeColor(int color) {
        mPaint.setColor(color);
        mColor = color;
        invalidate();//交换颜色之后需要重新绘制 否则颜色没有实际变化
    }

    public int getColor() {
        return mColor;
    }

}

自定义ViewGroup

class CircleLoadingView extends RelativeLayout {
    private static final String TAG = "CircleLoadingView";
    private SingleCircle mCircleLeft, mCircleCenter, mCircleRight;
    private float mAnimateDistance;
    private int mAnimateDuration = 500;
    AnimatorSet mAnimatorSet4Out;
    AnimatorSet mAnimatorSet4In;


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

    public CircleLoadingView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mAnimateDistance = Util.dip2px(80, context);
        inflate(context, R.layout.layout_circle_loading_view, this);
        mCircleLeft = findViewById(R.id.circle_left);
        mCircleCenter = findViewById(R.id.circle_center);
        mCircleRight = findViewById(R.id.circle_right);
        mCircleLeft.changeColor(Color.RED);
        mCircleCenter.changeColor(Color.GREEN);
        mCircleRight.changeColor(Color.BLUE);
        post(this::startOutAnimate);
    }


    //往外跑
    private void startOutAnimate() {
        if (mAnimatorSet4Out == null) {
            ObjectAnimator translationLeftOut = ObjectAnimator.ofFloat(mCircleLeft, "translationX", 0, -mAnimateDistance);
            ObjectAnimator translationRightOut = ObjectAnimator.ofFloat(mCircleRight, "translationX", 0, mAnimateDistance);
            mAnimatorSet4Out = new AnimatorSet();
            mAnimatorSet4Out.setInterpolator(new DecelerateInterpolator(2f));//2f表示比原来的动画更明显 减速动画
            mAnimatorSet4Out.playTogether(translationLeftOut, translationRightOut);
            mAnimatorSet4Out.setDuration(mAnimateDuration);
            mAnimatorSet4Out.start();
            mAnimatorSet4Out.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation, boolean isReverse) {
                    startInAnimate();//往里跑
                }
            });
        }
        mAnimatorSet4Out.start();
    }

    //往里跑
    private void startInAnimate() {
        if (mAnimatorSet4In == null) {
            ObjectAnimator translationLeftIn = ObjectAnimator.ofFloat(mCircleLeft, "translationX", -mAnimateDistance, 0);
            ObjectAnimator translationRightIn = ObjectAnimator.ofFloat(mCircleRight, "translationX", mAnimateDistance, 0);
            mAnimatorSet4In = new AnimatorSet();
            mAnimatorSet4In.setInterpolator(new AccelerateInterpolator(2f));//2f表示比原来的动画更明显 加速动画
            mAnimatorSet4In.playTogether(translationLeftIn, translationRightIn);
            mAnimatorSet4In.setDuration(mAnimateDuration);
            mAnimatorSet4In.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation, boolean isReverse) {
                    exchangeColor();//交换颜色
                    startOutAnimate();//往外跑
                }
            });
        }
        mAnimatorSet4In.start();
    }

    //点集中到中间 交换颜色
    private void exchangeColor() {
        int leftColor = mCircleLeft.getColor();
        int centerColor = mCircleCenter.getColor();
        int rightColor = mCircleRight.getColor();
        mCircleCenter.changeColor(leftColor);
        mCircleRight.changeColor(centerColor);
        mCircleLeft.changeColor(rightColor);
    }

    //释放资源
    public void loadingComplete() {
        mAnimatorSet4Out.cancel();
        mAnimatorSet4In.cancel();
        removeAllViews();
    }
}

自定义ViewGroup的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <com.example.circleloadingview.SingleCircle
        android:id="@+id/circle_left"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_centerInParent="true" />

    <com.example.circleloadingview.SingleCircle
        android:id="@+id/circle_right"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_centerInParent="true" />

    <com.example.circleloadingview.SingleCircle
        android:id="@+id/circle_center"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_centerInParent="true" />

</RelativeLayout>

Activity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CircleLoadingView circleLoadingView = findViewById(R.id.loadingView);
        circleLoadingView.postDelayed(circleLoadingView::loadingComplete, 1000 * 10);//10s后假装加载完毕
    }
}

Activity布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.circleloadingview.CircleLoadingView
        android:id="@+id/loadingView"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

后记

没有做这个自定义View的时候,我觉得这是个很简单的功能,预计最多两小时完成.视频里面也说个小时就可以收工.然而由于各种问题+优化+笔记,最后我还是花了一个下午才完成.即使是很小的东西,我也从中学到了不少东西,比如ObjectAnimator创建方式不对会导致crash,AnimatorSet的复用如果不正确会导致动画ANR,代码的复用与逻辑的清晰有时不能兼得.
所以再小的东西,也要认真对待呀.
收工!
代码:
https://github.com/caihuijian/learn_darren_android

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值