效果

思路

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

遇到的坑

1.crash问题

2021-01-09 15:43:03.653 21938-21938/com.example.circleloadingview E/AndroidRuntime: FATAL EXCEPTION: mainProcess: com.example.circleloadingview, PID: 21938java.lang.NullPointerException: Attempt to get length of null arrayat 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() {@Overridepublic 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() {@Overridepublic 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);}@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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.SingleCircleandroid:id="@+id/circle_left"android:layout_width="10dp"android:layout_height="10dp"android:layout_centerInParent="true" /><com.example.circleloadingview.SingleCircleandroid:id="@+id/circle_right"android:layout_width="10dp"android:layout_height="10dp"android:layout_centerInParent="true" /><com.example.circleloadingview.SingleCircleandroid:id="@+id/circle_center"android:layout_width="10dp"android:layout_height="10dp"android:layout_centerInParent="true" /></RelativeLayout>

Activity

public class MainActivity extends AppCompatActivity {@Overrideprotected 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.CircleLoadingViewandroid: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

红橙Darren视频笔记 圆点loadingView 动画ANR相关推荐

  1. 红橙Darren视频笔记 点赞效果 动画练习

    效果 知识点 位移动画 随机数 估值器(TypeEvaluator )使用 思路 1.在底部中心位置初始化图片 1.1用上透明度变化和scale变化的动画 2.计算二阶贝塞尔曲线 3.添加透明度变化动 ...

  2. 红橙Darren视频笔记 ViewGroup事件分发分析 基于API27

    本节目标,通过案例,先看程序运行结果,然后跟踪源码,理解为什么会有这样的输出,继而理解view group的分发机制,感觉和证明题很像呢. 考虑以下程序的运行结果: case1: public cla ...

  3. 红橙Darren视频笔记 UML图简介

    整体架构复制自红橙原视频的课堂笔记 因为他这一课没有博客,所以没有转载链接,CSDN没有转载地址是无法作为转载类型的文章发表的,暂时标记为原创 参考链接 https://blog.csdn.net/r ...

  4. 红橙Darren视频笔记 代理模式 动态代理和静态代理

    红橙Darren视频笔记 代理模式 动态代理和静态代理(Android API 25) 关于代理模式我之前有过相关的介绍: https://blog.csdn.net/u011109881/artic ...

  5. 红橙Darren视频笔记 类加载机制(API28) 自己写个热修复 查看源码网站

    第一部分 类加载机制 一个Activity是如何被Android虚拟机找到的? 在之前的文章 红橙Darren视频笔记 自定义View总集篇(https://blog.csdn.net/u011109 ...

  6. 红橙Darren视频笔记 利用阿里巴巴AndFix进行热修复

    注意 由于AndFix在2017年左右就停止更新了,在最新版本的apk上遇到很多问题,我最终也没有成功进行热修复.本节主要是学习热修复的原理 在上一篇 红橙Darren视频笔记 自己捕获异常并保存到本 ...

  7. 红橙Darren视频笔记 Behavior的工作原理源码分析

    主要coordinatorlayout的代码来自coordinatorlayout-1.0.0-sources.jar 本文从源码介绍 CoordinatorLayout 的 behavior 怎么工 ...

  8. 红橙Darren视频笔记 仿QQ侧滑效果

    这一篇没有什么新的内容 就是改写 红橙Darren视频笔记 仿酷狗侧滑效果 的侧滑的效果 1.去掉淡入淡出效果 2.加上黑色模板效果 效果: 去掉淡入淡出效果很简单 就是注释掉onScrollChan ...

  9. 红橙Darren视频笔记 view的绘制流程(上) onMeasure测量代码分析 基于API27

    一.准备工作Activity的onCreate和onResume调用过程 从ActivityThread的handleLaunchActivity开始进行代码跟踪 private void handl ...

最新文章

  1. LPC1768基本输入输出GPIO使用
  2. eclipse常用插件安装
  3. UVA10294项链和手镯(等价类计数问题)
  4. Linux上机实验1
  5. 整合shiro出现UnsatisfiedDependencyException,org.springframework.beans.factory.BeanNotOfRequiredTypeExcep
  6. c语言搜索关键字吗,c语言-以关键字搜索程序
  7. 艾瑞发布2018视频云行业报告,网易云信领跑第一阵营
  8. c++ *.h和*.cpp在编译中的作用
  9. 介绍Windows Azure移动服务:用于您连接的客户端应用程序的后端
  10. time、random以及序列化模块
  11. IDEA快捷键及xml文件中网址报错
  12. Java LinkedList公共int indexOf(Object o)方法(带示例)
  13. android改变下拉框字体颜色,有没有简单的方法来改变Android中的Spinner下拉颜色?...
  14. iTween for Unity
  15. 在电脑上安装python-在电脑上安装python的方法
  16. 操作可能会破坏运行时稳定性的解决办法
  17. java开发web应用开发,Java Web应用开发概述
  18. 超简单友盟分享(微信、QQ)+ 原生微信分享
  19. 三星N900刷机包 基于颓废NO.7更新 状态栏透明 右上角一键锁屏
  20. 【志强课堂】文案到底有何作用和威力?

热门文章

  1. 数据结构动态顺序字符串基本操作实验_技术连载:数据结构 - 栈
  2. java回调函数的生命_Java的回调函数 - choaklin 的个人空间 - OSCHINA - 中文开源技术交流社区...
  3. 转载 :配置ssh密钥认证自动登录
  4. 【转】硬盘的寻址和工作模式
  5. base64 linux_渗透测试常用Linux命令总结
  6. 【连载】如何掌握openGauss数据库核心技术?秘诀一:拿捏SQL引擎(1)
  7. MogDB/openGauss 生态工具-MTK(Migration ToolKit) 数据库迁移
  8. OpenMetric与时序数据库模型之主流TSDB分析
  9. 实践案例丨GaussDB网络重传/丢包问题定位总结
  10. 面试软件测试所需要掌握的7个技能