Android面试一天一题(Day 30:老外的自定义View面
之前买过一本专门讲Android Launcher开发的书,有点可惜,关建的地方都没有讲深入,用太多基础的知识点来占篇幅了(并不是说基础知识不重要,只些这些基础知识我可以从很多地方得到资料,不需要一本专讲Launcher开发的书来过多的介绍),这样的讲解我认为很难让一个开发学习到Launcher的精髓。我认为开发Launcher应用有三个关键的地方:
- 自定义View
- 数据结构(显示在Launcher的应用,包括AppWidget)
- 性能优化
其实从这三点,大家也可以看出Launcher是一个很关键的应用,能同时做好这三点的开发可以说是相当不错的。所以,你也可以检验一个面试者所做过的应用或者项目,看他是否有能力独立做好这三点。
这一章,就说一下自定义View。
面试题:如何实现自定义View?
回忆一下,你去面试时常被问到的自定义View方面的问题是那些。有没有:
invalidate和postInvalidate方法的区别?
自定义View的绘制流程?
View的Touch事件分发流程?
因为在实际的工作中并不是每个人都会涉及UI的实现,所以有些人没有做过自定义View并不能否决这个人在Android开发上的能力,包括会问你这方面问题的面试官也可能并没有自定义View的经验。所以很多面试中,一般也就是问问如上面的那些机制方面的问题,看面试者是否有一个正确的认识。但如果一个人说他精通自定义View的话,不妨从细节上检验一下他。
在说我怎么检验面试者的自定义View水平之前,先来一道老外的面试题,大家不妨先自己试着敲一下代码看能不能实现。
效果界面如下:
这一道题其实把我们刚刚提到的面试常被问到的那些机制问题都涉及到了,你很容易就能查找和了解并可以很好的回答上那几个基础问题,但是你能做出这个自定义View吗?
这就是机制和现实的差距!你有必要了解机制(基础),但了解并不等于会了,会的过程需要一定的积累,只有融会贯通了才能轻而易举地完成这个老外的面试题。
我们简单剖析一下,这个可左右滑动的View中是一组子View组成的,每个子View(圆盘carousel)可以自定界面,但背景的样式是相似(每组背景只是颜色不一样),有点ListView中的Adapter味道,对滑动的过渡是有要求的,这个其实是更真实操作Touch的样例(没有人想看生硬的滑动效果)。那么我们要能实现上面的效果,需要注意以下几个点:
ViewGroup的布局(计算一行能显示的圆盘数量和大小)
圆盘View的效果(背景和半透明过渡)
左右列表和BaseAdapter
圆盘View的滑动(需要自动回弹,保证滑动后要有一个圆盘处于中间位置)
这个章节讲的是怎么准备自定义View的面试,所以我并不打算把这个问题的具体实现过程写出来(如果大家觉得有必要,可以反馈一下,人数较多的话可以就此题的实现写一篇文章),大家可以自去偿试实现,下一节再给一个我的实现链接,大家可以对比一下。
View & ViewGroup
自定义View(有时我们也可以叫自定义UI或者自定义UI组件),从实现或者分类上我觉得可以分为三类:
- 直接继承View
- 继承自ViewGroup
- 对现有组件的扩展(如继承自TextView)
第3种方式是比较容易的,因为父组件常常帮我处理了绘制、分发和Touch等事件,我们只需要加入一些特别的功能就行。比如继承自ImageView实现图片圆角显示。
第1和第2种方式,需要我们对前面提到的那些机制问题有一定的了解,也要搞清楚View和ViewGroup在哪些方面有什么区别。
ViewGroup继承自View,是一种特殊的View,可以理解成一种View的容器,它可以装其他的View或其他的ViewGroup。
ViewGroup需要控制子View如何布局,所以必须实现onLayout(在ViewGroup中是抽像方法)。
ViewGroup可以通过onInterceptTouchEvent拦截当前事件,再决定是否分发给子View处理。
ViewGroup默认是不会调用onDraw方法的,如果需要重绘容器的背景需要在构造函数调用setWillNotDraw(false)。
除此之外,要熟悉一些概念(或者类):Canvas, Paint, Matrix,Path & PathMeasure,贝塞尔曲线,SufaceView & OpenGL等等。是不是觉得概念太多了,是的,没有实际做过,你一定会这样想。让你为了面试去准备这么多东西,貌似也起不到多大的作用,面试官不一定会问(除非指明了招专门做自定义UI的开发)。而且就算面试官问了一个你准备好的主题,但他换一种说法问你,如果你只是准备过但并没有掌握的话,也不一定能答得上来。就拿PathMaesure来说,做为面试官一般不会直接问你:“这个PathMeasure有什么方法或者主要是做什么用的啊?”
而往往会用实例来看你的实现思路,例:“如何实现下图的这个箭头图标(png图片)围绕园周运动的自定义View”。如果你的做法不是使用PathMeasure,那么我也会很有兴趣想听听你的思路和实现方法。
灵活使用PathMeasure的常见的两个方法可以轻松的实现很多神奇的效果。
getSegment:截取整个Path的片段;
getPosTan:获取路径上的坐标点和对应点的切线坐标。
使用PathMeasure实现的核心代码:
path = new Path();
path.addCircle(0, 0, 200, Path.Direction.CW); //添加一个圆的path
pathMeasure = new PathMeasure(path, false);
float[] pos = new float[2];
float[] tan = new float[2];
pathMeasure.getPosTan(pathMeasure.getLength() * value, pos, tan);
float degress = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI); // 计算箭头图片的旋转角度
如果面试者不了解PathMeasure的话,也可以问一下图片圆角(或称矩形圆角)或者圆形头像的实现方式,这个也是一个很常见的功能,效果如下图:
�除了把原图直接做成圆角外,常见有三种方式实现:
使用Xfermode混合图层;
使用BitmapShader;
通过裁剪画布区域实现指定形状的图形(ClipPath)
小结
对于应用开发来说,自定义View是一项很常见的工作,很多时候都需要把UI交互设计师的动画和草图通过自定义View实现出来。如果对自定义View不太了解的话,有可能会引入很多不必要的代码(因为你总想着先找别人实现好的库),而且可能很简单的实现会被你写得很复杂(如上面举的PathMeasure的例子),无形中增加维护的难度。
遇到过很多面试者,当你问他们比较善长Android开发的哪个方面的话,他们脱口而出“UI”,但当你问他自定义View的一些经验时,得到的回答却只是知道如何使用别人的库而已。
当我写之篇文章时,也觉得很难用一篇文章把自定义View的知识要点全部讲清楚,这里也只能是从面试的角度给讲几个例子,希望对你有帮助。最后,准备自定义View方面的面试最简单的方法:
- 就是自己动手实现几个View(由简单到复杂);
- 分析一些热门App中的自定义View的效果是怎么实现的;