微博 App 的用户头像有一个圆形旋转进度条的加载效果,看上去效果非常不错,如图所示:

据说 Instagram 也采用了这种效果。最近抽空研究了一下,最后实现的效果是这样:

基本上能模拟出个大概,代码量不大,来讲讲实现思路吧。

模拟一种动画效果,首先需要仔细分析其运作过程,找到其中的物理规律,从而确定实现方案。像这种运动速度较快的动画,一般不是很容易看清。可以先通过录屏软件,录取动画运作的过程,然后借助一些辅助工具放慢放大,比如 PS,反复重复播放几遍,基本上就能看出动画的运作规律了。

回到这里的加载效果,拆分开来,可以理解为画笔上的两层绘制和时间上的两段过程。时间上,很明显可以看出分为前 360 度和后 360 度,主要在画笔上:

1,单一完整的圆弧绘制。前 360 度,从 360 度的圆弧到 0 度圆弧的递减过程;后 360 度,从 0 度圆弧到 360 度圆弧的递增过程。

2,重复片段的圆弧绘制。前 360 度,从零开始,逐渐递增,直到多段填满圆周;后 360 度,反过来,逐渐递减,直到数量为零。

其他的就是细节的处理,后面具体实现时再提及,我们先来看看如何实现这两个核心流程的绘制。

由于这两个流程使用的画笔属性相同,所以使用一个 Paint 对象即可,这段代码没什么好讲的,就是一些初始化工作:

mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mArcWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);

注意,与微博原图效果不同的是,我的效果图中使用到了渐变色圆环,这样效果更好看一些。使用 setShader() 方法可以给画笔设置渐变色,实现方式是:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    ......

    Shader shader = new LinearGradient(0f, 0f, mWidth, mHeight,
            mStartColor, mEndColor, Shader.TileMode.CLAMP);
    mPaint.setShader(shader);
}

Shader 子类有很多,这里使用的是线性渐变类 LinearGradient,由于需要使用 View 宽高,所以放在了 onSizeChanged() 函数里面。

核心计算和绘制工作都在 onDraw() 方法里面,看下这里的代码:

@Override
protected void onDraw(Canvas canvas) {
    if (maxAngle <= 360) {
        float angle = 0;
        canvas.rotate(mRatio * maxAngle / 360, mWidth / 2, mHeight / 2);
        canvas.drawArc(mRectF, maxAngle, 360 - maxAngle, false, mPaint);
        while (angle <= maxAngle) {
            float length = mArcRadian * angle / maxAngle;
            canvas.drawArc(mRectF, 0, length, false, mPaint);
            canvas.rotate(mArcSpacing, mWidth / 2, mHeight / 2);
            angle += mArcSpacing;
        }
    } else {
        float angle = 0;
        canvas.rotate(mRatio + mRatio * (maxAngle - 360) / 360, mWidth / 2, mHeight / 2);
        canvas.drawArc(mRectF, 0, maxAngle - 360, false, mPaint);
        canvas.rotate(maxAngle - 360, mWidth / 2, mHeight / 2);
        while (angle <= 720 - maxAngle) {
            float length = mArcRadian * angle / (720 - maxAngle);
            canvas.drawArc(mRectF, 0, length, false, mPaint);
            canvas.rotate(mArcSpacing, mWidth / 2, mHeight / 2);
            angle += mArcSpacing;
        }
    }

    if (maxAngle <= 720) {
        maxAngle += mArcSpacing;
        postInvalidateDelayed(30);
    }
}

前面提到,动画在时间上分前后 360 度的两段过程,所以这里定义了一个 maxAngle 变量来定义时间的变化。可以看到,前后 360 度的绘制代码看上去差不多,但还是有很大区别的。需要理解的地方有:

1,多个小段圆弧可以利用画布旋转的方式轻松实现,也就是 canvas.rotate() 方法,上面代码中的 while 循环部分。这里有个细节处理,就是每段圆弧的弧度有个递增变化。

2,整个 View 给人的感觉有一种旋转的效果,为了实现这个效果,在每次绘制前,都增加了旋转的步骤,也就是这行代码:

canvas.rotate(mRatio * maxAngle / 360, mWidth / 2, mHeight / 2);

其中 mRatio 表示整个动画结束时,View 相比初始状态时整体旋转的角度。 这里我设置的默认值是 60,值越大,动画执行时呈现出越快的旋转效果。大家可以修改源码,自己尝试一下。

3,还有一点就是,maxAngle 变量每次增量值的设置,一定要设置为相邻两段片段圆弧的间距弧度。这样做的目的是,保证每次绘制,片段圆弧都能有一个完整的递增或递减。否则,动画执行时,看上会发生视觉上的抖动效果。

基本上就是这样,代码量虽然不多,但是各种细节的处理还是耗费了很多时间调整。本来想使用一些图例展示一下每行代码的作用,但是太费时间了。如果感兴趣的,完全可以自己下载一下源码,修改试试看。

经过简单地封装,提取出一部分属性:

<declare-styleable name="CircleLoadingView">
    <attr name="circleStartColor" format="color|reference"/>
    <attr name="circleEndColor" format="color|reference"/>
    <attr name="circleArcWidth" format="integer|reference"/>
    <attr name="circleArcRadian" format="integer|reference"/>
    <attr name="circleArcSpacing" format="integer|reference"/>
</declare-styleable>

注意:中间的头像部分不是这里自定义 View 的内容,使用时可以自由填充内容。比如,看下我这里的使用方式,还是比较自由的:

<RelativeLayout
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_centerInParent="true">

    <com.yifeng.view.view.CircleLoadingView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:circleStartColor="#ffff00"
        app:circleEndColor="#ff0000"
        app:circleArcRadian="5"
        app:circleArcWidth="10"
        app:circleArcSpacing="10"/>

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_avatar_default"/>

</RelativeLayout>

源代码还是老样子,统一在 GitHub 上的自定义 View 集锦库里,地址如下。:

https://github.com/Mike-bel/android-custom-views

关于我:亦枫,博客地址:http://yifeng.studio/,新浪微博:IT亦枫

微信扫描二维码,欢迎关注我的个人公众号:安卓笔记侠

不仅分享我的原创技术文章,还有程序员的职场遐想

Android 自定义圆形旋转进度条,仿微博头像加载效果的更多相关文章

  1. Android自定义圆角矩形进度条2

    效果图: 或 方法讲解: (1)invalidate()方法 invalidate()是用来刷新View的,必须是在UI线程中进行工作.比如在修改某个view的显示时, 调用invalidate()才 ...

  2. Android开发-各种各样好看漂亮的进度条,指示器,加载提示汇总

    导读:之前项目中用到一些进度条,找了不少,打算写个demo自己总结一下,留着以后用, 有些是自己写的,有些是github上找的别人的库,如果大家觉得好看可以用,直接下载复制代码到项目里就可以用,ok ...

  3. Android自定义View(LimitScrollerView-仿天猫广告栏上下滚动效果)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53303872 本文出自:[openXu的博客] 1分析 2定义组合控件布局 3继承最外层控件 ...

  4. 灵活、可高度自定义的——Progress进度圈、弹窗、加载进度、小菊花

    DDProgressHUD的介绍 提供了四种类型的展示: 显示无限旋转的加载图(比如小菊花,可以自定义),显示文字信息.网络刷新时经常用到. 显示加载进度的动画,也可以显示文字.网络下载时用的比较多, ...

  5. Unity跳转场景进度条制作教程(异步加载)

    Unity跳转场景进度条制作 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分享 ...

  6. ajax 上传文件,显示进度条,进度条100%,进度条隐藏,出现卡顿就隐藏进度条,显示正在加载,再显示上传完成

    <form id="uploadForm" method="post" enctype="multipart/form-data"&g ...

  7. 自定义ListView实现下拉刷新,下拉加载的功能

    package com.loaderman.myrefreshlistviewdemo; import android.content.Context; import android.util.Att ...

  8. Android简易实战教程--第十七话《自定义彩色环形进度条》

    转载请注明出处:http://blog.csdn.net/qq_32059827/article/details/52203533   点击打开链接 在Android初级教程里面,介绍了shape用法 ...

  9. Android零基础入门第52节:自定义酷炫进度条

    原文:Android零基础入门第52节:自定义酷炫进度条 Android系统默认的ProgressBar往往都不能满足实际开发需要,一般都会开发者自定义ProgressBar. 在Android开发中 ...

随机推荐

  1. JS身份证验证

    window.checkIdcard = function (idcard) { var errors = new Array( "yes", "请检查输入的证件号码是否 ...

  2. vuejs2点滴

    在Vue定义一个不被添加getter setter 的属性: https://github.com/vuejs/vue/issues/1988 博客: 0.vux的x-input源码分析. http: ...

  3. Java 如何抛出异常、自定义异常、手动或主动抛出异常

    public static void main(String[] args) { try { throw new Exception("抛出异常"); } catch (Excep ...

  4. Confluence 6 管理多目录

    这个页面描述了如果在 Confluence 中定义了多个目录服务器将会发生什么样的情况.例如你可能会有一个内部目录服务器同时你还可能有连接一个 LDAP 外部服务器或者使用多种类型的其他用户目录.当你 ...

  5. PHP函数总结 (二)

    <?php header('content-type:text/html;charset=utf8');// 只要声明的函数在脚本中可见,就可以通过函数名在脚本的任何位置调用echo table ...

  6. CentOS 7 Install Gitlab CE

    https://hostpresto.com/community/tutorials/how-to-install-and-setup-gitlab-on-centos-7/ http://linux ...

  7. python-day3笔记

    1.通信是软件(计算机)与软件(计算机)之间的通信 2.网络指的是: 一:计算机与计算机之间通过物理连接介质(网络设备)连接到一起:光纤--物理连接介质,和网线一样. 二:计算机与计算机之间基于网络协 ...

  8. ORACLE11G内存管理参数

    今天,对ORACLE11G的几个内存参数看了一下,记录如下,大家可以参考: 1.首先,在ORACLE11G的INIT.ORA里,有“__”开头的参数,也就是以两个下划线开头的参数,这种参数应该是系统自 ...

  9. elasticsearch 自定义_id

    elasticsearch 自定义ID: curl -s -XPUT localhost:9200/web -d ' { "mappings": { "blog" ...

  10. spring cloud 学习(二)关于 Eureka 的学习笔记

    关于 Eureka 的学习笔记 个人博客地址 : https://zggdczfr.cn/ ,欢迎光临~ 前言 Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务.Sprin ...