前言

  使用百度贴吧客户端的时候发发现加载的小动画挺有意思的,于是自己动手写写看。想学习自定义View以及自定义动画的小伙伴一定不要错过哦。
  读者朋友需要有最基本的canvas绘图功底,比如画笔Paint的简单使用、Path如何画直线等简单的操作,不熟悉也没关系,下文带大家撸代码的时候会简单的讲一下。
  此篇文章用到如下知识点:

  1)、自定义View的测量
  2)、自定义View属性的自定义及使用
  3)、Path绘制贝塞尔曲线
  4)、Canvas的裁剪
  5)、用ValueAnimator控制动画
  6)、Canvas文字居中

  好了,开始正文!


一、准备工作

1、效果图

2、动画拆解

  直观的看我们要实现三个方面
  1)、波浪动画(蓝色部分)
  2)、不规则的文字(白色的半个“贴”字)
  3)、控件显示部分限制成圆形
  

3、技术分析

  1)、波浪动画
  要实现波浪动画,首先要绘制出波浪的形状,其次再让他动起来。波浪线看起来有点像是正弦或者余弦函数,但是Android的Path并没有提供绘制正余弦图形的函数,但是提供了一个功能更强大的曲线——贝塞尔曲线,贝塞尔曲线分为二阶、三阶及多阶,本案例里使用的是二次贝塞尔曲线,如下图所示,二阶贝塞尔曲线需要三个点才可以确定

 
我们来看一下Android里贝塞尔曲线的源码:

/* @param x1 The x-coordinate of the control point on a quadratic curve* @param y1 The y-coordinate of the control point on a quadratic curve* @param x2 The x-coordinate of the end  on a quadratic curve* @param y2 The y-coordinate of the end point on a quadratic curve*/public void quadTo(float x1, float y1, float x2, float y2) {isSimplePath = false;native_quadTo(mNativePath, x1, y1, x2, y2);}

由注解可以看出来quadTo(float x1, float y1, float x2, float y2)的四个参数分别是控制点的x,y坐标,结束点的x,y坐标,少了一个开始点呀!不要着急,开始点是Path路径的上一次结束的点,如果你的Path没有绘制过路径,那么Path的最后一个点坐标就是(0,0)如果想自己定义起始点位置,就用Path.moveTo(float x, float y)即可。
但是每次都需要指定具体的控制点和结束点既麻烦又容易出错,那么就需要rQuadTo(float dx1, float dy1, float dx2, float dy2)出马了,rQuadTo跟quadTo的区别在于rQuadTo使用的是相对起始点的坐标,而不是具体的坐标点,举个例子,如下代码效果等价:

    //使用quadToPath path=new Path();path.moveTo(100,100);path.quadTo(150,0,200,100);//使用rQuadToPath path=new Path();path.moveTo(100,100);path.rQuadTo(50,-100,100,0);

此时画笔最后的落点都为(200,100)。
  画波浪线的技术难点解决了那么如何让波浪动起来呢,想动起来肯定需要波浪在水平方向移动,那么我们需要画一个很长很长的波浪让他移动,这样就实现了上下起伏效果,但是这样需要画无数多条贝塞尔曲线,肯定不行,这时就用到万能的数学理论——周期函数了,如果我们绘制两个周期的贝塞尔曲线,每次只让它显示一个周期,然后等第二周期显示结束的时候再从头开始,这样就造成了无限周期的假象,如下图
  初始位置为1,向右前进,当走到2位置的时候重置成3的位置,即1原始的位置,如此往复就成了绵绵不绝的波浪了
 
  做成效果如下:黄色区域就是要显示的区域,蓝色竖线是波浪线两个周期的总长度
  

2)、不规则的文字

我们可以看到圆球里的“贴”字在波浪区域显示的是白色,波浪区域之外显示的是蓝色,Android并不支持给文字部分区域着色的功能,那么我们只能靠控制显示区域让文字只显示特定形状,强大的Canvas正好有画布裁剪功能,通过裁剪画布就能控制绘制区域,画布的裁剪可以用Canvas.clipPath(Path path)实现,传入一个闭合的Path既可以随心所欲裁剪画布,裁剪示意图如下

利用波浪形闭合路径讲画布裁剪成波浪形,那么在此接下来的Canvas绘制操的内容只能在这个波浪形区域里显示,这样就解决了文字的部分区域显示问题。那么接下来我们只用在相同位置绘制相同字体、字号不同色的文字即可实现一个文字显示两种颜色了(注意:实际操作的时候,被裁剪的文字要盖在未被裁减的文字的上边,即先在画布裁剪之前绘制蓝色的“贴”字,然后再裁剪画布再在裁剪后的画布上绘制白色的“贴”)

3)、控件显示部分限制成圆形

经过2)的分析,将显示部分限制在圆形区域里不是易如反掌吗,使用一个圆形的Path裁剪画布即可。感兴趣的同学也可以尝试BitmapShader或者Xfermode来将显示区域变成圆形

好了,最主要的步骤都分析完了,上一张图更直观地展示一下绘制流程

图中可以看出波浪形的闭合Path有两个作用,一个是负责裁剪画布,一个是负责绘制蓝色,其实只用第一个功能即可,此处只是方便分解步骤。

二、代码实现

文章只贴出主要代码,完整代码文末提供链接

既然是自定义控件,那就要有通用性比如下边的效果:

loading小球需文字和颜色都可以改变,所以我们要给自己的控件添加这两个属性。首先在“res/values/”路径下新建一个attrs.xml文件,在里边定义如下属性:

 <declare-styleable name="Wave"><attr name="color" format="color"/><attr name="text" format="string"/></declare-styleable>

接下来开始自定义View
  复写三个构造函数,将单参数和双参数的构造函数的super方法都改为this,保证无论调用哪个构造方法都会跳到三个参数的构造方法中,这样就可以偷懒只用在三个参数的构造方法里初始化各种参数了

public class Wave extends View {public Wave(Context context) {this(context,null);}public Wave(Context context, AttributeSet attrs) {this(context, attrs,0);}public Wave(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//初始化参数init(context,attrs);}
}

接下来是初始化函数,在此处我们获取到自定义的颜色及文字参数,并初始化各种画笔,代码比较简单,看注释内容即可

private void init(Context context, AttributeSet attrs) {//获取自定义参数值TypedArray array =  context.obtainStyledAttributes(attrs, R.styleable.Wave);//自定义颜色和文字color = array.getColor(R.styleable.Wave_color, Color.rgb(41, 163, 254));text = array.getString(R.styleable.Wave_text);array.recycle();//图形及路径填充画笔(抗锯齿、填充、防抖动)mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.FILL);mPaint.setColor(color);mPaint.setDither(true);//文字画笔(抗锯齿、白色、粗体)textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);textPaint.setColor(Color.WHITE);textPaint.setTypeface(Typeface.DEFAULT_BOLD);//闭合波浪路径path = new Path();}

接下来是生成波浪线的方法,示意图如下:

  将Path起点移动到最左边粉色点处,然后绘制两个周期的长度的波形(一上一下是一个周期),每个周期在x轴的跨度为此控件的宽度控制点距波形的轴线的绝对高度是整个控件的3/20,当然想让波形波动幅度大的话这个比例可以随意调整,接下来就用前边讲到的rQuadTo( )来生成闭合的波浪图形,其中mWidth为控件的宽度,mHeight为控件的高度

 private Path getActionPath(float percent) {Path path = new Path();int x = -mWidth;//当前x点坐标(根据动画进度水平推移,一个动画周期推移的距离为一个周期的波长)x += percent * mWidth;//波形的起点path.moveTo(x, mHeight / 2);//控制点的相对宽度int quadWidth = mWidth / 4;//控制点的相对高度int quadHeight = mHeight / 20 * 3;//第一个周期波形path.rQuadTo(quadWidth, quadHeight, quadWidth * 2, 0);path.rQuadTo(quadWidth, -quadHeight, quadWidth * 2, 0);//第二个周期波形path.rQuadTo(quadWidth, quadHeight, quadWidth * 2, 0);path.rQuadTo(quadWidth, -quadHeight, quadWidth * 2, 0);//右侧的直线path.lineTo(x + mWidth * 2, mHeight);//下边的直线path.lineTo(x, mHeight);//自动闭合补出左边的直线path.close();return path;}

上边代码所表示的闭合路径如下图

接下来就是重头戏onDraw了

   protected void onDraw(Canvas canvas) {//底部的字textPaint.setColor(color);drawCenterText(canvas, textPaint, text);//上层的字textPaint.setColor(Color.WHITE);canvas.save(Canvas.CLIP_SAVE_FLAG);//裁剪成圆形Path o = new Path();o.addCircle(mWidth / 2, mHeight / 2, mWidth / 2, Path.Direction.CCW);canvas.clipPath(o);//生成闭合波浪路径path = getActionPath(currentPersent);//画波浪canvas.drawPath(path, mPaint);//裁剪文字canvas.clipPath(path);drawCenterText(canvas, textPaint, text);canvas.restore();}

  这里绘制思路是:在canvas上绘制蓝色的文字 ——>将画布裁剪成圆形 ——>绘制波浪 ——>裁剪画布成波浪形 ——>绘制文字,这里一定要注意绘制顺序,先绘制的在下部,后绘制的在上部。思路简单只用注意到两次clip即可。
  细心的朋友一定看到了一个函数drawCenterText(canvas, textPaint, text)没错,这个函数就是讲文字绘于控件正中心的方法。有的读者可能一直在使用Canvas.drawText( String text, float x, float y, Paint paint) 这个方法,但是参数中的(x,y)到底是哪个坐标呢,是文字左上角的点的坐标吗?不是的,接下来我们用代码验证一下这个(x,y)到底在文字的哪个部位

        canvas.drawText(text,600,200,textPaint);canvas.drawCircle(600,200,3,paint);canvas.translate(600, 200);Rect bgRect=new Rect(0,0,1000,400);canvas.drawRect(bgRect,bgPaint);Rect textBound=new Rect();
textPaint.getTextBounds(text,0,text.length(),textBound);paint.setColor(Color.RED);canvas.drawRect(textBound,paint);Paint.FontMetrics metrics=textPaint.getFontMetrics();paint.setColor(Color.RED);// ascent 橙色paint.setColor(Color.rgb(255,126,0));canvas.drawLine(0, metrics.ascent, 500,metrics.ascent, paint);// descentpaint.setColor(Color.rgb(255,0,234));canvas.drawLine(0, metrics.descent, 500, metrics.descent, paint);// toppaint.setColor(Color.DKGRAY);canvas.drawLine(0, metrics.top, 500, metrics.top, paint);// bottompaint.setColor(Color.GREEN);canvas.drawLine(0, metrics.bottom, 500, metrics.bottom, paint);

  首先是在画布的(600,200)处画上文字,为了方便观察(600,200)在文字的什么部位,我在(600,200)处画了一个半径3像素的圆圈。然后平移画布到(600,200)的地方然后依次画出了文字的边框图以及FontMetrics信息里的top、ascent、descent、bottom信息
我把运行结果截图做了处理,方便大家看

  从结果看(600,200)那个蓝色的点并不是在文字的左上角,而是左下角,这个点所在的y坐标即是大家常说的BaseLine的位置,那现在这个函数Canvas.drawText( String text, float x, float y, Paint paint)就可以理解为——将文字的基准点放在(x,y)处,那么这个基准点可以改变吗?答案是肯定的,可以通过绘制文字的画笔的setTextAlign(Align align)方法设置为Paint.Align.CENTER或者Paint.Align.RIGHT,如果不设置的话默认是Paint.Align.LEFT。读者朋友们有兴趣的话可以试试设置成CENTER之后(600,200)的蓝圈圈是不是跑到了文字的中部呢?从上图我们也可以看出,整个文字是介于FontMetrics.topFontMetrics.bottom之间。
  好了,贴上文字居中的代码,相信认真看上边那段话的朋友一定能轻松读懂

    private void drawCenterText(Canvas canvas, Paint textPaint, String text) {Rect rect = new Rect(0, 0, mWidth, mHeight);textPaint.setTextAlign(Paint.Align.CENTER);Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();float top = fontMetrics.top;float bottom = fontMetrics.bottom;int centerY = (int) (rect.centerY() - top / 2 - bottom / 2);canvas.drawText(text, rect.centerX(), centerY, textPaint);}

分析好上边的代码 我们就能绘制出一个静态的小球了,动画既然要动,肯定就像汽车一样需要一个”引擎”,在上面说到的绘制波浪路径的函数中我们忽略了getActionPath(float percent)的参数percent,这个参数即是当前动画的进度,那么我们如何来制造这个进度呢?需要怎样把这个动画“引擎”点燃呢。我们可以通过各种手段计时,生成一个计时Thread或者自己写一个Handler等等,只要能均匀的生成进度即可。
  本文中用到一个巧妙的定时器ValueAnimator 大家常说的属性动画ObjectAnimator就是它的一个子类,使用它来作为动画的引擎再方便不过了,从字面翻译”ValueAnimator”那就是“值动画者”直译虽然low但是恰恰更好理解,就是让数值动起来,从什么值动到什么值呢?
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
这句话就是定义一个值从0变化到1的一个animator,我们的percent值就是从0变化到1的中间过程值,那么怎么得到这个过程值呢?——监听器!对!

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float percent = animation.getAnimatedFraction();}});

那么数值从0变到1需要多久呢?怎么能无限重复呢?重复的时候是重头开始还是反转进行呢?别急下面三句话就是让动画无限重复,每次从头开始,一个周期1000毫秒

        animator.setDuration(1000);animator.setRepeatCount(ValueAnimator.INFINITE);animator.setRepeatMode(ValueAnimator.RESTART);

好了,引擎设置好了,发动
animator.start();
上效果

WTF!这是什么鬼,为什么鬼畜地慢几拍?
打印出来横坐标看看

07-09 18:18:47.308  E/Jcs: getActionPath: -21
07-09 18:18:47.326  E/Jcs: getActionPath: -15
07-09 18:18:47.342  E/Jcs: getActionPath: -10
07-09 18:18:47.359  E/Jcs: getActionPath: -5
07-09 18:18:47.375  E/Jcs: getActionPath: -2
07-09 18:18:47.392  E/Jcs: getActionPath: 0
07-09 18:18:47.409  E/Jcs: getActionPath: 0

最后几拍的数值差好像不太对呀!拍拍脑门突然一想,我的动画不均匀是忘记设置一个均匀的插值器了!哎!

 animator.setInterpolator(new LinearInterpolator());

补上一个线性插值器,整个世界都顺畅了

百度Loading小球Github源码

三、结语

第一次写文章,不免有些疏漏之处,望多多指教!后续我会不定期更新新的内容,争取把写文章当成自己生活的一部分。

Android仿百度贴吧客户端Loading小球相关推荐

  1. android壁纸框架,Android仿百度壁纸客户端之搭建主框架(一)

    这是个不错的教程,自己学完了之后就拿出来分享了,本来想一个帖子写完,但是发现这样对自己写博客的效率有点出入,为了让大家看的舒服点,所以分开来写,我们先开看下百度壁纸的客户端是什么样子的 我们先来写个主 ...

  2. android仿百度外卖波浪_头像随波浪漂浮效果—仿Android百度外卖

    前几天看到有iOS仿百度外卖的个人页,所以就随手撸了个Android.效果图如下: demo.gif 源码 改造轮子 轮子就不重复造了,github上已经有实现波浪的效果WaveView,WaveVi ...

  3. android 布局中绘制语音曲线,Android仿百度地图小度语音助手的贝塞尔曲线动画

    本文为大家分享了Android仿小度语音助手的贝塞尔曲线动画,供大家参考,具体内容如下 废话不多说,看下面的动图,和百度的还是有点点差别,我也不修改了,很简单,我实在是没有多余的时间,还要学习其他的东 ...

  4. android仿百度新闻,【Android】最新主流新闻app功能实现。仿网易,搜狐等新闻客户端实现展示...

    最新主流新闻app功能实现.仿网易,搜狐等新闻客户端 (原创作品,转载请说明出处)先给大家看一下效果图: 这个项目总体来说虽然不是特别难,但是确实非常常用的功能.是业余时间自己写的一个小项目. 以前我 ...

  5. android 仿百度地图,仿百度地图街景实现

    使用过百度地图的同学知道,它有个街景功能,可以看到许多地方的实景.这里就其街景内容的实现,进行下学习. 在百度地图SDK的官网上可以看到,百度对开发者提供了很多相关的内容,方便我们进行学习.关于SDK ...

  6. android 图片查看功能吗,Android仿百度图片查看功能

    我们知道,进入百度图片后,输入一个关键字后,首先看到的是很多缩略图,当我们点击某张缩略图时,我们就可以进入到大图显示页面,在大图显示页面,中包含了一个图片画廊,同时当前大图为刚刚我们点击的那张图片.现 ...

  7. 仿百度地图 android,仿百度地图街景实现

    使用过百度地图的同学知道,它有个街景功能,可以看到许多地方的实景.这里就其街景内容的实现,进行下学习. 在百度地图SDK的官网上可以看到,百度对开发者提供了很多相关的内容,方便我们进行学习.关于SDK ...

  8. 仿百度动态Android源码,Android 仿百度手机助手首页滑动效果

    今天看到百度手机助手首页上的滑动效果非常nice,主要功能归结为: 1.当手指上划时,顶部搜索栏随手指移动距离而缩小到隐藏,隐藏后内容还是可以继续移动 2.手指下滑时,当显示内容达到第一个时,顶部搜索 ...

  9. android仿百度全景地图,全景静态图API

    全景静态图API 百度地图全景静态图API与静态图API类似,可轻松实现将全景地图以图片形式嵌入到您的应用中.您只需要设置图片尺寸.经纬度坐标等参数,发送HTTP请求访问百度地图全景静态图服务,便可在 ...

最新文章

  1. php 2 往数据库添加数据
  2. 【青少年编程】【三级】加法出题机
  3. HTML的标签描述12
  4. FileZilla 连接不上虚拟机 ubuntu
  5. 计算a b python_你知道Python中a = b和a = a b的结果是不一样的吗?
  6. JAVA正则表达式介绍和使用
  7. 【动态规划】农田个数 (ssl 1633)
  8. SQL的经典语句(太全了)
  9. java设计是什么软件下载_用Java设计下载软件
  10. 为了搞懂什么是区块链,我都快抑郁了(转)
  11. css图片居中_网页元素居中的n种方法
  12. 概率论 —— 分析计算机系统和网络的可靠性和通用性
  13. 用CSS编写登陆页面(含源代码)
  14. matlab怎样分别求偏相关系数,如何用Matlab计算相关系数和偏相关系数
  15. 凯盛融英在港招股书“失效”:遭证监会严格发问,需补充披露
  16. C++基础课 5- 章
  17. 运动世界校园一直显示服务器开小差,运动世界校园跑步异常 运动世界跑步成绩异常怎么办...
  18. 动态平衡网格交易_微笑每周答——定投基金,哪种方法最好?低估值、动态再平衡、网格交易法、价值平均策略,总有一款适合你...
  19. iOS比较好用的第三方框架
  20. Maven-使用私服的好处

热门文章

  1. Wireshark Labs(3): DNS v7.0
  2. CSDN上大牛推荐书籍收集中
  3. 质量如何登入大雅之堂
  4. 计算机房的英语怎么读音,计算机房节能,computer center energy conservation,音标,读音,翻译,英文例句,英语词典...
  5. 网络编程基础【day09】:socket实现文件发送(六)
  6. ERP系统的工作原理是怎样的?
  7. 如何区分爵士舞和街舞_跳动全城舞蹈
  8. 杰理之在music模式下开混响,喊麦会卡音的处理方法【篇】
  9. 腾讯云服务器远程桌面连接
  10. python(10.30)