老外Android工程師談Android: 自定義View

相關閱讀:

吊炸天!74款APP完整源碼!

有了這些免費無限次的API 接口,再也不愁沒有服務器開發不了APP了,也可以自己開發小程序了

2016年未,騰訊,百度,華爲,搜狗和滴滴Android面試題彙總

JavaAndroid架構

歡迎關注我們,一起討論技術,掃描和長按下方的二維碼可快速關注我們。搜索微信公衆號:JANiubility。

公衆號:JANiubility



英文:medium.com


部分譯文是按自己的理解翻譯的,如有錯漏,還請指正


簡介


每天我們都會使用很多的應用程序,儘管他們有不同的約定,但大多數應用的設計是非常相似的。這就是爲什麼許多客戶要求使用一些其他應用程序沒有的設計,使得應用程序顯得獨特和不同。


如果功能佈局要求非常定製化,已經不能由Android內置的View創建 —這時候就需要使用自定義View了。而這意味着在大多數情況下,我們將需要相當長的時間來完成它。但這並不意味着我們不應該這樣做,因爲實現它是非常令人興奮和有趣的。


我最近面臨了類似的情況:我的任務是使用ViewPager實現Android應用引導頁。不同於iOS,Android並沒有提供這樣的View,所以我不得不編寫一個自定義View來實現它。

我花了一些時間來實現它。幸運的是,時下很多開源項目都有類似可複用的View,這節省了我和其他開發者的時間。我決定基於這種View創建一個公共庫。如果你有類似的功能需求並且缺乏時間實現它,可以在github repo發現它。


Sample of using PageIndicatorView


繪製!


因爲編寫自定義View比起普通的View更耗時,你應該只在爲了實現特定的功能但沒有更簡單的方法情況下使用自定義View,或者你希望通過自定義View解決以下問題:


  1. 性能。如果你佈局裏面有很多View,你想通自定義View優化它,使其更輕量。

  2. 視圖層次結構複雜。

  3. 一個完全自定義的View,需要手動繪製才能實現。



如果你還沒有嘗試過編寫自定義View,這篇文章將教會你繪製扁平的自定義View的一些技巧。我將會告訴你整體的視圖結構,如何實現具體的功能,不要重犯常見的錯誤,以及實現動畫效果!


我們需要知道的第一件事 –View的生命週期。不知出於某種原因,谷歌並沒有提供View生命週期的圖表,由於開發者普遍對其有誤解,導致了一些意想不到的錯誤和問題,所以我們要認清這過程。


構造函數


每個View的生命都是從構造函數開始。而且這是一個繪製初始化,進行各種計算,設定默認值或做任何我們需要的事情很好的地方。


但是,爲了使我們的View更易於使用和配置,Android提供了很有用的AttributeSet接口。它很容易實現,而且絕對值得花時間去了解和實現它,因爲它會幫助你(和你的團隊)通過靜態參數來設置View,對於以後新特性加入或者新屏幕拓展性支持也更好。


首先,創建一個新的文件attrs.xml。所有不同的自定義View屬性都可以放在該文件中。正如你看到的這個例子,我們有一個PageIndicatorView和它的唯一屬性piv_count。


Custom Attributes sample


緊接着在View的構造函數中,你需要獲取這個屬性並使用它,如下圖所示。


public PageIndicatorView(Context context, AttributeSet attrs) {

    super(context, attrs);

    TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.PageIndicatorView);

    int count = typedArray.getInt(R.styleable.PageIndicatorView_piv_count,0);

    typedArray.recycle();

}


注意:


  • 在創建自定義屬性使用一個簡單的前綴,以避免與其它View類似的屬性名稱衝突。一般我們使用View名稱縮寫,就像例子中的piv_。

  • 如果你使用的是Android Studio,一旦你使用完屬性,lint會建議你調用recycle()方法 。The reason is just to get rid of inefficiently bound data that’s not gonna be used again。[譯者注:翻譯有點拗口,其實就是回收TypedArray,以便後面重用]


onAttachedToWindow


父View調用addView(View)後,這個View將被依附到一個窗口。在這個階段,我們的View會知道它被包圍的其他view。如果你的View和其他View在相同的layout.xml,這是通過id找到他們的好地方(你可以通過屬性進行設置),同時可以保存爲全局(如果需要)的引用。


onMeasure


這意味着我們的自定義View到了處理自己的大小的時候。這是非常重要的方法,因爲在大多數情況下,你的View需要有特定的大小以適應你的佈局。


當你重寫此方法,需要記得的是,最終要設置setMeasuredDimension(int width, int height) 。


onMeasure


當處理自定義View的大小時候,使用者可能通過layout.xml或者動態設置了具體的大小。要正確地計算它,我們需要做幾件事情。


1.計算你的View內容所需的大小(寬度和高度)。

2.獲取你的View MeasureSpec大小和模式(寬度和高度)。


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    }


3.檢查MeasureSpec 設置和調整View(寬度和高度)的尺寸模式。


int width;

  if (widthMode == MeasureSpec.EXACTLY) {

     width = widthSize;

   } else if (widthMode == MeasureSpec.AT_MOST) {

     width = Math.min(desiredWidth, widthSize);

  } else {

    width = desiredWidth;

  }


注意:


看看MeasureSpec的值:


  • MeasureSpec.EXACTLY 意味着硬編碼大小值,所以你應該設置指定的寬度或高度。

  • MeasureSpec.AT_MOST 用於表明你的View匹配父View的大小,

    所以它應該和他想要的大小一樣大。

    [譯者注:此時View尺寸只要不超過父View允許的最大尺寸即可]

  • MeasureSpec.UNSPECIFIED 實際上是視圖包裝尺寸。因此,你可以使用上面計算所需的大小。


在通過setMeasuredDimension設置最終值之前,以防萬一,可以檢查這些值不爲負數。這可以避免在佈局預覽時一些問題。


onLayout


此方法分配大小和位置給它的每一個子View。正因爲如此,我們正在研究一個扁平的自定義視圖(繼承簡單的View)不具有任何子View,那麼就沒有理由重寫此方法。[譯者注:實現可以參考Custom Layouts on Android]


onDraw


這就是發生魔法的地方。在這裏,使用Canvas和Paint對象你將可以畫任何你需要的東西。

一個Canvas實例從onDraw參數得來,它一般用於繪製不同形狀,而Paint對象定義形狀顏色。簡單地說,Canvas用於繪製對象,而Paint用於造型。它們無處不在,無論繪製的是一個直線,圓或長方形。


onDraw() — methods example


使自定義View,要始終牢記onDraw會花費大量的時間。當佈局有一些變化,滾動、快速滑動都會導致重新繪製。所以這就是爲什麼Android Studio也建議:避免在onDraw中進行對象分配的操作,對象應該只創建一次並在將來重用。


onDraw() — Paint object recreation


onDraw() — Paint object reuse


注意:


  • 在執行繪製時始終牢記重用對象,而不創建新的。不要依賴於IDE高亮一個潛在的問題,而是自己有意識地去做這件事,因爲在onDraw調用一個內部會創建對象的方法時,IDE無法識別它。

  • 同時請不要硬編碼View大小。其他開發者在使用時可以定義不同的大小,所以View大小應該取決於它有什麼尺寸。


View 更新


從View的生命週期圖可以得知,可以重繪View自身有兩種方法。invalidate()和requestLayout()方法會幫助你在運行時動態改變View狀態。但爲什麼需要兩個方法?


  • invalidate()用來簡單重繪View。例如更新其文本,色彩或觸摸交互性。View將只調用onDraw()方法再次更新其狀態。

  • requestLayout()方法,你可以看到其將會從`onMeasure()開始更新View。這意味着你的View更新後,它改變它的大小,你需要再次測量它,並依賴於新的大小來重新繪製。


動畫


在自定義View中,動畫是一幀一幀的過程。這意味着,如果你想使一個圓半徑從小變大,你將需要逐步增加半徑並調用invalidate()來重繪它。


在自定義View動畫中,ValueAnimator是你的好朋友。下面這個類將幫助你從任何值開始執行動畫到最後,甚至支持Interpolator(如果需要)。


ValueAnimator animator = ValueAnimator.ofInt(0, 100);

animator.setDuration(1000);

animator.setInterpolator(new DecelerateInterpolator());

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

  public void onAnimationUpdate(ValueAnimator animation) {

    int newRadius = (int) animation.getAnimatedValue();

  }

});


注意:


當每一次新的動畫值出來時,不要忘記調用invalidate()。


[GIF圖超過2MB,微信無法顯示]

Sample of animation via ValueAnimator


希望這篇文章可以幫助你實現你的第一個自定義View,如果你想更多地瞭解它,可以看看這個視頻。

https://www.youtube.com/watch?v=4NNmMO8Aykw&feature=youtu.be


關於Java和Android大牛頻道

Java和Android大牛頻道是一個數萬人關注的探討Java和Android開發的公衆號,分享和原創最有價值的乾貨文章,讓你成爲這方面的大牛!

我們探討android和Java開發最前沿的技術:android性能優化 ,插件化,跨平臺,動態化,加固和反破解等,也討論設計模式/軟件架構等。由羣來自BAT的工程師組成的團隊

關注即送紅包,回覆:“百度” 、“阿里”、“騰訊” 有驚喜!!!關注後可用入微信羣。羣裏都是來自百度阿里騰訊的大牛。

歡迎關注我們,一起討論技術,掃描和長按下方的二維碼可快速關注我們。搜索微信公衆號:JANiubility。

公衆號:JANiubility


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章