关注我就能达到大师级水平,这话我终于敢说了

这句话我真的憋了好久。Android 工程师只要关注我,我就能让你达到大师级水平,不是面试时的吹牛逼水平,不是自我欺骗的了解皮毛的水平,是真正的开发实力。以前我有这个自信,没这个证据。但现在,证据我也有了。

关注我的人都知道,我这三个多月来一直在网上分享关于 Android 自定义绘制的技术,并且在一周多前举办了一场线上的「仿写酷应用」活动,让我的粉丝仿写即刻、薄荷健康、小米运动、Flipboard 这四个软件中的几个经典效果,来自我验证一下这三个月来的学习成果。

简单地说,这事儿很成功(脸红)。

经过一周的投稿时间,共收到 97 份来自关注者的投稿(真的很多,出乎意料),而且做得都很棒。在四位原开发者的艰难筛选下,每个效果选出了一位优胜者。下面就是这四位优胜者的作品以及来自各位原开发者对他们的点评。

仿写作品

仿写一:即刻

二话不说,先看结果。

原效果:

仿写效果:

仿得很细致有没有。而且,这个仿写效果看似简单,其实是假象。它右边的数字跳动,是一个字符一个字符跳动替换的,而不是整个字符串一起被替换掉,这个需要对文字绘制的 API 有足够的熟悉才能做到。

这位仿写者叫刘金伟。在通知刘金伟成为「被选中的人」后,我对他进行了一个简短的微信采访。关于这次仿写的过程,刘金伟是这样对我说的:

到这次仿写活动为止,HenCoder专栏已经出了8篇View绘制相关的技术文章了,每篇文章认真阅读之后我也根据例子进行实践操作,学到的一些东西也在项目开发中得以应用,正如文章中所说,在自定义View相关方面更加自如了。

看到这次HenCoder的仿写活动,就有点跃跃欲试,发现这几个效果都挺不错的,和朋友聊了聊之后就选了这个经常会用到的点赞功能进行实践。这个点赞效果经过我分析发现虽然内部动画挺多,但是所用到的技术点都是在HenCoder中讲到过的,所以在简单的分析之后就开始写,大约花了大半天时间就基本完成了,整个过程也没太多技术难点,做出来后还给朋友炫耀了一番。

最后再次由衷的感谢HenCoder专栏,为了提升大家技术水平而做的事,非常敬佩。

不谢不谢,哈哈。

原作者点评

对他的作品进行点评的是即刻的 Android Leader 罗琼。他对这份仿写的评价似乎还不错:

仿写效果的点评,主要针对以下几点

  1. 对原创的还原程度
  • 示例的完备程度(可测试性)
  • 项目规范
  • 代码实现

对原创的还原程度

这位同学仔细观察了即刻点赞效果

  • 使用的资源应该是反编译即刻 apk 之后拿到的
  • 实现了点赞和取消点赞两种效果
  • 实现了左边图片部分的动画
  • 实现了右边数字部分的动画

未实现的部分

  • 点赞对 touch 操作的处理(只处理了 click 操作)
  • 点赞效果在图片上的先放大后缩小
  • 散开的点的放大效果
  • 整体动画的细节优化

示例的完备程度

这个是做得比较好的

  • 可以直接设置具体数字,这样方便做边界条件(进位退位)的测试。
  • 上传了可以直接安装的 app-release.apk ,方便先安装看效果。
  • README 有对动画实现的说明。

项目规范

  • 没有 .idea 这种不该上传的文件夹
  • .gitignore 也是专门修改过的

可以优化的部分

  • 对 android support 包的依赖,不要依赖 alpha 这种版本,尽量依赖稳定版
  • 代码文件都需要进行格式化,包括 xml
  • mdpi 的资源不再需要了
  • 自定义 View 的 attr 的命名,需要有自己的命名空间(前缀)
  • 自定义 View 的变量命名不规范,一会儿有 m ,一会儿有下划线

代码实现

作者把图片和文字写在一个类里面了,这样的话不太灵活,即刻 app 现在在某些主题下的点赞效果不是大拇指,比如逝者这个主题就是蜡烛,所以建议图片和文字还是分成两个类来搞。

对点击事件的防重处理好像有点问题,另外连续点击是否直接对之前动画进行 cancel 也需要斟酌一下。

整体的实现代码上冗余逻辑比较多,并且每一个函数内部每一行语句并没有完全去考虑,如对 specMode 的 switch case ,没有穷尽所有的可能值。

图片这块的实现,整体的思路是通过事先画好各种元素,然后通过属性动画的方式,但自定义的属性动画的方法是通过反射调用的,整个类里面并没有显示调用,对于> 此类不是很复杂的动画,建议还是用系统自带的属性比较好。

文字这块的实现,涉及到偏移、颜色、以及分离出变化和不变的部分(作者对文字的解剖分析得很好),但代码的复用程度不高。

总体而言,项目完成度比较高,恭喜获得本次仿写即刻点赞效果的冠军,不过真正商用的话还是有不少需要考虑的,尤其是代码规范上需要加强,再好的实现和算法,如果代码不够规范,恐怕看得人也会比较累,希望再接再厉。

评价得好详细啊,我在读这份点评的过程中多次遇到「哇塞好细,如果是我的话这里肯定要被扣分了」的情况。

仿写二:薄荷健康

依然是先上效果:

原效果:

仿写效果:

以假乱真了有没有?

这份仿写的作者叫严积楷。他对仿写过程的回顾是这样的:

我觉得仿写,首先就是要了解清楚仿写对象的细节,所以还是自己亲自体验一下比较好,所以就下载了薄荷健康APP,发现尺子UI已经改变了(这样就提醒了我要做一个适合多变需求的控件),但是一些特性也是和效果图差不多的:触摸滑动后是有惯性滚动的光标保持在中间,不随尺子滚动光标选中的刻度只会是0.1整点让触摸滑动、惯性滚动之后,尺子会回滚到最近的整点刻度,如最后滑动到66.5和66.6中间靠右,则回滚到66.6。

了解了特性之后,就是想着怎样实现了:首先就是画出刻度和数字,这里是采用一下子就画出所有刻度的方法,然后通过滑动方法scrollBy()来移动画面(不知道这样系统会不会对尺子那些不在屏幕显示的部分进行绘画计算的,我当时是加上滑动之后,发现很可以滑的流畅,所以就这样了,现在想想还可以处理一下的)。触摸控制方面,因为之前分析过GestureDetector,了解了它的实现,所以这里就试着直接重写onTouchEvent()和OverScroller配合实现惯性滑动。对于刻度的计算,这里通过滑动位置getScrollX()来计算当前刻度,而又通过把滑动结束后的刻度四舍五入之后,计算对应的ScrollX来让尺子最近的整0.1点刻度。对于光标的绘制,由于这里是采用了scrollBy()的方法移动画面,所以也没有找到一个好的方法让光标不随尺子移动,所以就外层加入一个ViewGroup重写dispatchDraw()方法在这里绘制(这个方法执行在onDraw()之后,在这里绘制才不会被尺子的刻度覆盖掉)。

主要的实现思路就是这样了,还有一些细节,如代码封装这些比较常见的就不详细说了,大家可以看代码,已经写好了注释,欢迎指教小弟。

「欢迎指教小弟。」高仿到这种程度竟然还求指教,这谦虚的态度莫非是讨打?有人想当面指教他一下吗?

原作者点评

对这份作品进行点评,我请到的是薄荷健康的 Android 工程师 loody。

还原度

99分,多一分怕你骄傲。

实现思路

  1. 背景绘制
    顶部基准线+纯色背景。
  2. 刻度绘制,包括长、短刻度及长刻度下的文字绘制
    这块是整个卷尺最复杂的部分,整个卷尺有最大最小边界,所以整个刻度的绘制会基于最大、最小值以及定义的最小刻度单位来从左往右依次绘制,同时要保证当前选中的位置位于屏幕中央。
  3. 基准线绘制
    位于屏幕中间、可以有多种样式、以上绘制必须按照顺序依次绘制。
  4. 手势滑动处理、惯性滑动
    通过监测 MotionEvent 里的 ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL 事件来监测滑动距离来进行重绘,如果满足Fling,则使用OverScroller来计算速度来进行惯性滑动。
  5. 智能精准定位
    滑动完之后需要进行精准定位,必须回到基准线最近的刻度,用作者的话就是滑动到66.5和66.6中间靠右,则回滚到66.6。

优点

整个实现的非常好,所有的要点都实现了,特别是精准定位体验做得很棒,在拓展性方面,也定义了很多自定义属性,同时也上传到 Maven 仓库,方便其他人调用。

不足之处

  1. 背景和基准线的绘制和刻度的绘制进行了分离,其实没必要,因为他们的绘制并没有冲突;
  2. 在刻度绘制上,屏幕之外的刻度其实可以不用绘制;
  3. 卷尺的小刻度模式可以再丰富一些,一般两个大刻度之间有10个小刻度,当然也有2个、5个情况;
  4. 对外开放的初始化方法应该更丰富一些,因为很多时候我们的边界值并不是固定的。

薄荷的卷尺效果要实现起来是有非常多的细节的。loody 要从几十份仿写中选出这一份,还要写出这么细致的点评,别的我不想说,我只想对他说一句:苦了你了。另外也更想回头对上面的仿写者严积楷说一句:你太苦了!

仿写三:小米运动

原效果:

仿写效果:

仿得也是超棒。这件仿写作品的作者叫陈浩,他的仿写过程回顾充满了认真和辛酸:

之前没有太多做UI的经验,所以一开始我是比较乐观的,但最后花了整整5个晚上才做完。

我编程属于比较严谨(啰嗦)的那种,所以准备工作时我把gif下下来用电脑逐帧看了,还借了朋友的小米手环来看实际的动画(最新版有点出入)。然后把动画流程写下来,没有做什么模块规划,就开始编程了。

最开始动手时我只有动画参数可配置和要做动画状态机的这两个想法,后面整个代码结构都是在编写的过程中多次重构产生的。毕竟代码是程序员的第二张脸(我说的),给别人看的时候还是想写的更好一些。但这也存在一些过度设计的情况,导致完成的时间变长了不少。比如,我看到那个转动的烟花圆环线条不是一成不变的,于是天马行空设计出来一套的动画运动公式:这个线条是椭圆的一段圆弧,其圆心,宽和高的偏移都是随机参数,变化量d由变化速度v决定,变化速度v由外力1.衰减常量力,2.速度平方成正比的阻力,3.定时触发的随机力决定(戏真多)。结果导致动画参数调整就花太多时间,也改了好几版才稳定下来。。

写得好的地方的话,因为工作原因会比较考虑封装,可维护性,可拓展性这些,这些方面我觉得还是做到位了的。

说实话,在发布仿写征稿文的时候我就知道,肯定每个效果都有人能完成,但陈浩的这份作品还是让我有点意外:你仿得也太真了!但看了他的这份回顾,我觉得我知道为什么他写得这么好了。

原作者评价

我很高兴请到了这个效果的原作者,来自小米运动团队的卜冬旭来点评这份作品:

总体评价

相当不错。。。(本来想这四个字完结点评了的,因为模仿的效果相似度很高了,而且仅凭一个gif图,在很短的时间做出这样的效果,真的挺厉害的。同时up主提供的代码里的注释也很清晰,规范,可以很容易理解实现的原理。但是秉着认(wu)真(ren)负(zi)责(di)的态度, 还是来吹毛求疵一下。)

细节对比

这块的动效是小米运动app首页的效果。示例GIF图中包含了2部分效果,一个是小米手环连接过程中效果,就是一大堆杂乱的白色渐变的圆圈在转,圆圈的起始位置有一个往后发散的粒子效果。另一部分就是小米手环连接成功后的效果, 也就是几个白色透明度不等的渐变的圆环叠加在一起,最外圈层次渐变的光晕效果围绕圆环转动。
下面具体分析对比下这几块动画效果.

圆圈的叠加效果

我这边用了8个圆圈,每个圆圈的centerX, centerY有微小的差别,半径有微小的差别。通过SweepGradient来设置它的渐变,从纯白到20%左右透明度的白色,每个圆圈canvas.rotate的角度也有微小的差别。当然以上所有参数都可以自己任意调节,来达到参差不齐的效果, 只要整体效果不离谱就可以了。我对于其中一些参数用的都是random, 这块没有个标准。杂而不乱, 达到这个效果就行. 😀

粒子效果

比较麻烦的就是这块粒子效果。一方面要实现功能,一方面性能也要满足,不能卡顿。模仿的效果就这一块有点小问题, 一个是半径有点大了,然后透明度可以稍微降的快一点, 再聚拢一点效果会更好。(坏笑.jpg) 这块调起来是相当的麻烦。 这块粒子产生在第一个效果中圆圈的最亮的地方,也就是透明度最低的地方。具体位置可以从图中看一下。

粒子产生的位置是竖着的线的位置, 也就是随机在这条线上生成初始粒子。 发射的角度大概是左边2个线之间。第一个不是水平线,这两条线的角度可以慢慢的调整,从而找到合适的效果。 需要注意一点的就是, 粒子发射的角度需要比较精细的控制, 具体点说就是因为产生粒子的位置不是一个点,而是一条很短的线,所以尽量不要让粒子发射的延长线在很短的位置相交, 如图

关于粒子发生器的原理这里就不赘述了,我这里是通过距离超过一定数就重新生成一个粒子来控制的,当然也可以通过半径大小或者透明度来控制。Up主是通过透明度来控制的,也是没关系的。靠近发生器位置的粒子半径稍微大一点,然后半径是递减的。透明度也是递减的。至于性能问题,也不属于这个文章的范围内了。可以看下up主代码。

圆环效果

圆环主体是五个渐变的圆环叠加在一起,其实不能算作是圆环,应该是一个圆环和4个椭圆环。 绘制的时候,第一个渐变的圆环正常绘制,后面的椭圆环通过drawArc来绘制。保证它们五个环的left,right,`bottom一样, top参数为依次递增一定的大小。同时需要注意一点的就是颜色最深的圆环,也就是透明度最低的环,不是椭圆环放在最后绘制,这样可以盖住其他稍高透明度的环,这样不会看出叠加部分因为用的SweapGradient导致的叠加的效果。而是会觉得主体是一个整个系统,光晕是独立系统。不然能比较清楚的看出五个环的叠加效果。

多余的效果

其实那个整体效果,带有一点上下振动的这块是因为程序的下拉上推导致的,不属于这块的动画效果,没想到也给做出来了。(捂脸.jpg)

这块效果看着挺炫的,其实使用HenCoder里绘图的基础知识就可以实现,麻烦的地方就在于细节的调整, 可能调个参数就要重新跑一下程序, 还是相当痛苦的。想了解完整的动画效果,可以从仿写的代码里看到, 也可以来这里看。

最后竟然被冬旭兄弟打了我一个猝不及防的广告……

仿写四:Flipboard

原效果:

仿写效果:

这个效果可谓是人气超高了。我在之前的几篇文章或视频中都展示过这个效果,但只是说「用我教的技术就能实现」,却没有展示过具体的实现代码。很多人找我要实现,我也都没给过,为的就是这一天,让我的读者自己实现出来,我只帮你展示。

这个仿写的作者叫贾元斌,它的仿写过程回顾如下:

仿写“翻页效果(加强版)”这个动效的想法,源于凯哥讲解几何变换章节提到的一个动画栗子,很炫酷,极大地激发了偶的好奇心。

做动画的过程,其实就是一个典型的“发现问题,分析问题,解决问题”的过程。所以大概的流程就是,先拆解分析,动画是怎么“动”的,然后再去解决问题,想如何使用Android提供的Api实现“这么动”。 把gif图放在手机上一帧一帧地截屏,然后逐张观察,可以发现这个动画是由三个部分组成的: - 开始动画,一个Y轴三维旋转-45度动效 - 中间动画,比较复杂,图片右半边三维旋转,同时三维旋转的“转轴”平面内旋转了-270度,左半边图不变 - 结束动画,一个绕Y轴三维旋转30度动效 开始和结束的动画比较简单,camera旋转就能实现,重点分析中间的动画: 右半边三维旋转的时候,canvas先旋转,再裁切,再使用camera执行三维动效,然后保存camera效果,最后再旋转回来。问题分析清楚,具体到代码,拿canvas和camera的几何变换+范围裁切,就能实现了。

需要注意的坑:camera执行几何变换时,我们会把canvas的中心点移动到原点,所以这个时候canvas再执行其他变换(比如范围裁切),要用移动后的坐标系计算。 最后一点,不要忘记做糊脸校正,不然图片尺寸变大时,就没法看了…

贾元斌在最后提到的「糊脸修正」是我自己造的词,我觉得好形象啊,佩服自己。

另外,一直找我要实现的,请到文末自取贾兄的源码。

原作者评价

这个点评者我熟悉,我前同事,来自 Flipboard 的段建华(技术小黑屋),以前我在 Flipboard 的时候我俩挨着坐的。

总体点评

运行效果良好,思路比较清晰。总的来说不错。

思路与实现

  • 在readme文件中给出了具体的实现方案并进行逐步拆分,同时给出了需要的理论分析和一些注意事项
  • 提供了在布局文件中设置图片资源的实现,同时也支持在代码中设置。可扩展性较强
  • 做到动画的执行实现与View分离,便于在不修改代码的情况下,更改动画的配置,比如duration和delay时间等

代码质量

  • 配合以必要的注释,便于理解。
  • MainActivity中handler.postDelayed已经位于UI线程,无需在进行runOnUiThread调用
  • 对于degreeY等方法使用Keep来注解修饰,既解决了编译器的报警提示也避免了一些因为Proguard优化删除无用方法带来的潜在的问题,这一点体现了作者很周全考虑。

其他

  • 祥云图标的Flipboard Logo很赞
  • 替代的google_map提供了三种dpi对应的资源,+1

改进与完善

  • MapView 类的javadoc写成“整个动画拆分成了三部分”有些不妥,一般为类的功能介绍
  • 每一个commit的提交信息都需要概括修改的内容,不宜出现简单的update来了事
  • View命名成MapView,可能存在继续优化改善的空间
  • Repo的命名建议更加和参赛作品有关,这样便于筛选人员更好的快速辨识并分发给对应的评委。

看了建华的评价,我最大的感受就是,他果然还是这么认真,一些很细节的地方他也都提到了。另外可能是出于谨慎考虑,建华对于这份仿写实现和 Flipboard 的内部代码实现没有进行比对。而我作为一个已经离职的员工,我就……当然也不会泄露前公司的源码啦。但我要特别说明一下的是,贾元斌的仿写代码中的实现虽然和 Flipboard 的内部实现不完全一样,但执行效率和代码可读性都不比 Flipboard 的实现差。

至于 Flipboard 的内部实现到底是怎样的?我就不告诉你……

优胜者中的优胜者

上期内容我说过,由于赞助方爸爸 insight.io 的支持,这次活动的四位优胜者中,还会有一位特别优胜者,他获得的奖品和另外三位不一样,他将获得一步 Google Clips 相机:

这位优胜者将通过微博投票得出,投票链接稍后放出。

资源链接

几位仿写者的仿写代码链接在这里。这里贴的是 insight.io 从 github 同步的代码库,用 insight.io 来读源码超级爽(具体怎么爽,你点开就知道了),真的感谢 insight.io 的在礼物支持之外还给予我的技术支持。

即刻仿写https://insight.io/github.com/arvinljw/ThumbUpSample/tree/master/

关于仿写者刘金伟:

github: https://github.com/arvinljw

简书: http://www.jianshu.com/u/8fcc3372beb7

薄荷健康仿写https://insight.io/github.com/totond/BooheeRuler/tree/master/

关于仿写者严积楷:

github: https://github.com/totond

CSDN: http://blog.csdn.net/totond

邮箱: yanzhikai_yjk@qq.com

小米运动仿写https://insight.io/github.com/SickWorm/MISportsConnectWidget/tree/master/

关于仿写者陈浩:

github: https://github.com/SickWorm

Flipboard 仿写https://insight.io/github.com/sunnyxibei/HenCoderPractice/tree/master/

关于仿写者贾元斌:

github: https://github.com/sunnyxibei

微博: https://weibo.com/812306989

微信: sun521xibei

个人博客: http://timeriver.com.cn/

关于几位点评者

想了解几位点评者,想和他们一起工作?赶快看这里。

即刻

罗琼给了我一份超长的文案,我嫌长想给他删点,可又觉得这份文案写得已经超级棒,哪一句都删不得,所以干脆把这份文案最重要的关键词提取了出来,被这些关键词吸引了的可以点下面的链接进去看详情:

即刻的关键词:

流浪猫、400 平米、自带 Gif 表情包文化、健身房、台球桌、无人机、电玩设备、足球队、男女比例平衡、年轻、一年两次免费国内外纯玩 Outing、La Marzocco咖啡机 + Volcan 豆(这个看不懂)、咖啡师、Herman Miller人体工学座椅、公费参加 Google IO & WWDC

好吧我只提取了「福利」部分的关键词,因为我最关心这个……其实即刻团队做的事情更有意思,不过篇幅有限就不给它展示的机会啦(任性脸),有兴趣的可以点链接去看详情:

https://www.v2ex.com/t/388064

哦对了,工作地点上海。

小米运动

冬旭兄比较委婉地把招聘广告插进了点评的最后(就是上面那句「想了解完整的动画效果,可以从仿写的代码里看到, 也可以来这里看」的「这里」)。不过我还是要在这里再贴一次他们的招聘链接,让需要的人更清楚地看到:

http://www.huami.com/jobs

「华米科技」,又是一个米。你猜小米旗下到底有多少种米?

Flipboard

我的老同事建华给我发的「广告」是这样的:

此次仿写的参考目标之一为 Flipboard 的折页效果,Flipboard 中国团队目前进行了有史以来最特别、动作最大的一次产品升级改版,推出全新产品——红板报,它将带来更对味的个性化内容推荐、更广阔的全球化新闻视野、更生动的杂志化浏览体验。通过红板报,要将全球最优质的新闻内容用最好看的方式呈现,精简产品功能回归到阅读最核心使命:新闻可以很好看。

嗯,Flipboard 中国改为名红板报之后,作为开发者之一的建华很自豪,也很希望推荐给大家用。至于招聘链接……建华表示「什么招聘链接?」

Flipboard 北京办公室暂时没有 Android 职位空缺,所以建华其实是在给他自己打广告(哈哈哈我写了个好 App),大家快去打他呀。

薄荷健康

关于招聘,loody 和建华一样对我表示目前没有需求,只是告诉我「能提到薄荷健康就最好了」。

只付出不求回报的好同志。好吧,那我就来负责他们的文案吧:

薄荷健康,真呀真健康,装了薄荷健康,又瘦又健康。耶。

感谢

最后非常感谢四位点评者的帮助,这次活动的响应度比我的预期大很多,各位点评者光是审阅投稿就花了一天的时间,审完还要来帮我写点评语,写得短的还要被我打回去重写……真的辛苦了,你们。谢谢!

另外,还要感谢 insight.io 的两位联合创始人李崇哲、赵扶摇,在物质和技术上给我提供支持,让我这第一次自己主持的线上活动少了许多手忙脚乱。当然,也得感谢介绍我与 insight.io 相识的代码家(怎么哪儿都有你)。

下期预告

到这期位置,HenCoder 的自定义 View 系列的绘制部分就结束了,从下期开始,就要开始布局部分了。按照惯例,官方泄露一点截图吧:

布局部分由于技术的概念比绘制要大得多,所以掌握起来也会难一些,这个各位做好思想准备吧。不过你依然可以相信一点:就像之前的绘制部分一样,虽然有很多人已经尝试过通过看博客、看文档、读源码的方式来学习 Android 的布局(有很多甚至尝试过多次),最终都以「太难了实在学不会」告终,但只要你跟着我走,几期内容之后,你会发现,同样的技术,只要到 HenCoder 来学,它怎么就变得那么的简单。

就像本文标题和开头我说的,关注我,你就能达到大师级水平,这话我憋了好久,现在终于敢说了。因为,我做到了。