之前買過一本專門講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的效果是怎么實現的;