自定義 view - 前置知識點

ps :在系統(tǒng)的學(xué)習(xí)自定義 view 之前,搞懂本本篇的內(nèi)容會讓你學(xué)習(xí)的過程順序,簡單很多

view 的分類

view 其實就2種:

  • 單一視圖 view
    我們常用的各種具體控件都是 view ,比如 TextView
  • 視圖容器 ViewGroup
    視圖容器簡單說就是各種 layout 布局,里面用來存放 view ,給 view 定位的。想我們常用的 LinearLayout 就是視圖容器

自定義 view 的3個核心方法

  • onMeasure
    根據(jù) view 的測量模式計算確定 view 的寬高
  • onLayout
    ViewGroup 中對所有的子 view 排版,決定子 view 的位置
  • onDraw
    具體繪制 view

這3個方法就是自定義的核心了,自定義 view 不管我們怎么寫,基本都是圍繞這3個方法玩。view 沒有 onLayout 方法,因為 view 不是容器里面放不了 view ,只有 ViewGroup 才有 onLayout 方法


自定義 view 所有生命周期及回調(diào)函數(shù)

  • onFinishInflate()
    當(dāng)應(yīng)用從XML加載該組件并用它構(gòu)建界面之后調(diào)用的方法
  • onMeasure()
    檢測View組件及其子組件的大小
  • onLayout()
    當(dāng)該組件需要分配其子組件的位置、大小時
  • onSizeChange()
    當(dāng)該組件的大小被改變時
  • onDraw()
    當(dāng)組件將要繪制它的內(nèi)容時
  • onKeyDown
    當(dāng)按下某個鍵盤時
  • onKeyUp
    當(dāng)松開某個鍵盤時
  • onTouchEvent
    當(dāng)發(fā)生觸屏事件時
  • onWindowFocusChanged(boolean)
    當(dāng)該組件得到、失去焦點時
  • onAtrrachedToWindow()
    當(dāng)把該組件放入到某個窗口時
  • onDetachedFromWindow()
    當(dāng)把該組件從某個窗口上分離時觸發(fā)的方法
  • onWindowVisibilityChanged(int)
    當(dāng)包含該組件的窗口的可見性發(fā)生改變時觸發(fā)的方法

自定義 view 需要注意的生命周期及回調(diào)函數(shù)

注意這里,面試會問

  1. void onFinishInflate()
    當(dāng)系統(tǒng)解析XML中聲明的View后回調(diào)此方法,調(diào)用順序:內(nèi)層View->外層View,如果是viewgroup,適合在這里獲取子View。
    注意點:
    如果View沒有在XML中聲明而是直接在代碼中構(gòu)造的,則不會回調(diào)此方法
    此時無法獲取到View的寬高和位置

  2. void onAttachedToWindow()
    當(dāng)view 被添加到window中回調(diào),調(diào)用順序:外層View->內(nèi)層View。在XML中聲明或在代碼中構(gòu)造,并調(diào)用addview(this view)方法都會回調(diào)該方法。
    注意點:
    此時View僅僅被添加到View,而沒有開始繪制所以同樣獲取不到寬高和位置

  3. void onDetachedFromWindow()
    看名字就知道是與void onAttachedToWindow();對應(yīng)的方法,在VIew從Window中移除時回調(diào),如執(zhí)行removeView()方法。
    注意點:
    如果一個View從window中被移除了,那么其內(nèi)層View(如果有)也會被一起移除,都會回調(diào)該方法,且會先回調(diào)內(nèi)層View的onDetachedFromWindow()方法

  4. void onWindowFocusChanged(boolean hasWindowFocus)
    當(dāng)View所在的Window獲得或失去焦點時被回調(diào)此方法。除了常見的設(shè)置view的onGlobalLayoutListener,也可以通過這個方法取到VIew的寬高和位置;也適合在判斷當(dāng)失去焦點時停止一些工作,如圖片輪播,動畫執(zhí)行等,當(dāng)獲取到焦點后繼續(xù)執(zhí)行。
    hasWindowFocus:View所在Window是否獲取到焦點,當(dāng)該Window獲得焦點時,hasWindowFocus等于true,否則等于false。

  5. 該方法在當(dāng)前View或其祖先的可見性改變時被調(diào)用

更多詳細請看:


自定義 view 的種類

  • 繼承現(xiàn)成 view 控件
    比如我們寫一個自定義 view 繼承 textview ,這樣難度小,view 的3個核心方法我么你都不用關(guān)心,不過一般這樣寫都是為了給某個控件添加額外功能,難度小,但是不具有普遍適應(yīng)性。

  • 直接繼承 view
    直接繼承 view ,view 的 onMeasure 測量 ,onDraw 繪制都需要我們自己來做,很考驗功底的,里面又會涉及到大量的動畫操作,是非常難得,學(xué)好了能大大提升我們的代碼水平

  • 繼承現(xiàn)成 ViewGroup 容器
    難度小,一般我們都是做組合類型的 view 時用,多用于封裝 app 中的公共基礎(chǔ) UI 組件,雖然難度低,但是具有普遍性

  • 直接繼承 ViewGroup 容器
    難度最大,ViewGroup 容器的工作在于給子 view 確定位置,給子view 排版,需要大量的計算操作,還要精確考慮 magin,padding 的問題,很難,一般很少這樣做,都是對自己有信心的人才回去嘗試,技術(shù)不熟練的先不要來了


view 的多個構(gòu)造方法

view 的構(gòu)造方法有4個,分別面對不同的使用情況,我們在自定義 view 時要知道在哪個構(gòu)造的方法里做初始化,其實一般我們都是在這4個方法里面都寫初始化方法的

// 如果View是在Java代碼里面new的,則調(diào)用第一個構(gòu)造函數(shù)
 public CarsonView(Context context) {
        super(context);
    }

// 如果View是在.xml里聲明的,則調(diào)用第二個構(gòu)造函數(shù)
// 自定義屬性是從AttributeSet參數(shù)傳進來的
    public  CarsonView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

// 不會自動調(diào)用
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

//API21之后才使用,不會自動調(diào)用
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

注意:即使你在View中使用了Style這個屬性也不會調(diào)用三個參數(shù)的構(gòu)造函數(shù),所調(diào)用的依舊是兩個參數(shù)的構(gòu)造函數(shù)。

1.public View(Context context)
2.public View(Context context, @Nullable AttributeSet attrs)
3.public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
4.public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

構(gòu)造方法嚴(yán)格來說不算回調(diào),但除了方法一外都不需要我們手動調(diào)用,而且是自定義View僅有的必須要聲明的方法。

  • 構(gòu)造方法1
    當(dāng)不在布局文件中聲明而在代碼中創(chuàng)建View時調(diào)用的方法
  • 構(gòu)造方法2
    當(dāng)在布局文件中聲明,且沒有在styles.xml中預(yù)設(shè)主題級或item級的默認(rèn)屬性時調(diào)用。attrs就是一組布局文件中的值(包括默認(rèn)屬性和自定義屬性)
  • 構(gòu)造方法3
    當(dāng)在布局文件中聲明,在attrs.xml中有聲明一個屬性,并在styles.xml中的主題item聲明這個屬性的值(即View的一組默認(rèn)屬性)調(diào)用
  • 構(gòu)造方法4
    當(dāng)在布局文件中聲明,在styles.xml中的主題里沒有聲明但單獨聲明了View的一組默認(rèn)屬性時調(diào)用。

屬性賦值優(yōu)先級:Xml定義 (方法二)> Xml的style定義(方法二) > defStyleAttr (方法三)> defStyleRes> theme直接定義(方法四)

構(gòu)造方法四要求api21以上,所以我們一般采用構(gòu)造方法二(沒有默認(rèn)屬性)或構(gòu)造方法三(有默認(rèn)屬性)


view 的視圖層級

我們連帶著把 Actvity 的 視圖層級一起寫一下吧,下面這張就是 Actvity 視圖層級


2086682-5fda69bb0c776c22.png

我們在 xml 布局文件中聲明的布局根節(jié)點并不是 Activity 的視圖根節(jié)點,是上圖中的 contentView 的位置,contentView 上面的都是 Activity 內(nèi)部添加的,我們控制不了,但是我們需要了解,一些頁面效果我們需要操作 DecorView

這張圖是常見的 view 視圖層級


944365-afb2be431e523baf.png

ViewGroup 里面還可以再放 ViewGroup,但是 view 里面就不能放任何view 了

無論是measure過程、layout過程還是draw過程,永遠都是從View樹的根節(jié)點開始測量或計算(即從樹的頂端開始),一層一層、一個分支一個分支地進行(即樹形遞歸),最終計算整個View樹中各個View,最終確定整個View樹的相關(guān)屬性

上面圖中有一層就表示視圖有一個層級,視圖層級越多就會加重 cpu 計算負(fù)荷,這個不是線性的關(guān)系,是幾何層級的關(guān)系。ViewGroup 寬高采用 warp_content 時,會跑2次這個 ViewGroup 所屬子 view 的 onMeasur 方法,會大大增加任務(wù)量。所以我們在寫布局時,層級過多或是 warp_content 應(yīng)用過多,都會造成頁面加載計算大,頁面卡頓,這是我們需要優(yōu)化的一個點。


Android 坐標(biāo)系

  • 屏幕的左上角為坐標(biāo)原點
  • 向右為x軸增大方向
  • 向下為y軸增大方向

詳細看下圖:


944365-ee0cd39fd788e293.png

和數(shù)學(xué)坐標(biāo)系的 Y 軸方向是不同的


view 的坐標(biāo)

view 有3套描述坐標(biāo)位置的方式:

  • Left,Top,Right,Bottom
  • x,y,translationX、translationY
  • rawX ,rawY
1. Left,Top,Right,Bottom

這4個描述的是 view 的左右上下到 view 所在父控件左上角的位置

  • Top:子View上邊界到父view上邊界的距離
  • Left:子View左邊界到父view左邊界的距離
  • Bottom:子View下邊距到父View上邊界的距離
  • Right:子View右邊界到父view左邊界的距離

相關(guān)的 API :

getTop();       //獲取子View左上角距父View頂部的距離
getLeft();      //獲取子View左上角距父View左側(cè)的距離
getBottom();    //獲取子View右下角距父View頂部的距離
getRight();     //獲取子View右下角距父View左側(cè)的距離

詳細看下圖:


005Xtdi2gw1f1qzqwvkkbj308c0dwgm9.jpg

需要注意的是 right = left + view 的 width , bottom = top + view 的 height

2. rawX ,rawY

描述的是 view 左上角到屏幕左上角的距離

這個可以用 MotionEvent 中 get 和 getRaw 的區(qū)別來學(xué)習(xí)

相關(guān) API :

event.getX();       //觸摸點相對于其所在組件坐標(biāo)系的坐標(biāo)
event.getY();

event.getRawX();    //觸摸點相對于屏幕默認(rèn)坐標(biāo)系的坐標(biāo)
event.getRawY();

詳細看下圖:


005Xtdi2jw1f1r2bdlqhbj308c0dwwew.jpg
3. x,y,translationX、translationY

從android3.0開始,View增加了額外幾個參數(shù):x,y,translationX、translationY。其中x和y是View左上角的坐標(biāo),translationX和translationY是View左上角相對于父容器的偏移量,它們默認(rèn)值是0。這些參數(shù)也是相對于View父容器。具體關(guān)系見下圖:


5494434-0fbe681e48aaa4ea.png

x = left + translationX,y = top + translationY

x和left不同體現(xiàn)在:left是View的初始坐標(biāo),在繪制完畢后就不會再改變;而x是View偏移后的實時坐標(biāo),是實際坐標(biāo)。y和top的區(qū)別同理。


如何獲取 view 的寬高

獲取 view 的寬高有2套 API:

  • getWidth() / getHeight():獲得View最終的寬 / 高
  • getMeasuredWidth() / getMeasuredHeight():獲得 View測量的寬 / 高

他們的區(qū)別:


944365-6b27b9835d927e04.png

getMeasuredWidth() 方法可以在 view 的 onLayout 方法里使用,onLayout 在 onMeasure 之后跑,這時候 measuredWidth view 的寬是計算出來的,但是我們要考慮 view 申請的大小超過父控件最大值的問題。

我們可以考慮在 onSizeChange 方法內(nèi)記錄 view 的大小,這也是一種辦法。這2套獲取寬高的 API 最終的結(jié)果值都一樣,區(qū)別在于產(chǎn)生數(shù)據(jù)的時機不同。

getWidth() / getHeight() 只有在 view 計算完并顯示之后才能返回具體的值,其他時候返回的都是 0,所以 getWidth() / getHeight() 一般我們都是做延遲使用,等待 view 計算顯示完畢

獲取 view 寬高的時機不同,所依賴的方法也是不同的,具體的我就不寫了,大家看這個:

另外還有一點要清楚:getMeasuredXXX() 有時并不 = getXXX() ,下面這段話足以解釋

getMeasuredXXX() 與 getXXX() 的區(qū)別和聯(lián)系所在。說得直白一點,measuredWidth 與 width 分別對應(yīng)于視圖繪制 的 measure 與 layout 階段。很重要的一點是,我們要明白,View 的寬高是由 View 本身和 parent 容器共同決定的,要知道有這個 MeasureSpec 類的存在。

比如,View 通過自身 measure() 方法向 parent 請求 100x100 的寬高,那么這個寬高就是 measuredWidth 和 measuredHeight 值。但是,在 parent 的 onLayout() 階段,通過 childview.layout() 方法只分配給 childview 50x50 的寬高。那么,這個 50x50 寬高就是 childview 實際繪制并顯示到屏幕的寬高,也就是 width 和 height 值。

如果你對自定義 View 過程很熟練的話,理解這部分內(nèi)容就比較輕松一些。事實上,開發(fā)過程中,getWidth() 和 getHeight() 方法用的更多一些。


Android 的角度 (angle) 與弧度 (radian)

android 的角度和弧度有其需要說上一說, android 里的角度和我們平時的習(xí)慣是嫌煩的,這點很坑爹

另外這塊涉及到畫布的相關(guān)操作(旋轉(zhuǎn))、正余弦函數(shù)計算等,即會涉及到角度(angle)與弧度(radian)的相關(guān)知識。

另外記住縮寫:

  • deg --> 角度
  • rad --> 弧度

角度,弧度的詳細描述:


944365-7a81d3e1715eda0b.png

android 角度方向是順時針的:


1785445-fbfc94447e590f0e.png

Android 中顏色部分

Android 支持一下幾種顏色模式:


944365-43d2051c332e0f95.png

ARGB 表示4位顏色通道,RGB 表示3位顏色通道,RGB 相比 ARGB 少了透明的顏色通道,需要注意的是 ARGB8888 4位通道的圖片若是轉(zhuǎn)成 RGB565 3位通道的圖片格式,是會造成圖片色差的,A 透明顏色通道用的越多色差越嚴(yán)重

4位顏色通道含義:


944365-f63d3055739f08b2.png
  • java中定義顏色
//java中使用Color類定義顏色
 int color = Color.GRAY;     //灰色

  //Color類是使用ARGB值進行表示
  int color = Color.argb(127, 255, 0, 0);   //半透明紅色
  int color = 0xaaff0000;                   //帶有透明度的紅色
  • xml文件中定義顏色
<?xml version="1.0" encoding="utf-8"?>
<resources>

    //定義了紅色(沒有alpha(透明)通道)
    <color name="red">#ff0000</color>
    //定義了藍色(沒有alpha(透明)通道)
    <color name="green">#00ff00</color>
</resources>
  • java文件中引用xml中定義的顏色
//方法1
int color = getResources().getColor(R.color.mycolor);

//方法2(API 23及以上)
int color = getColor(R.color.myColor);    
  • xml文件(layout或style)中引用或者創(chuàng)建顏色
<!--在style文件中引用-->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/red</item>
    </style>

 <!--在layout文件中引用在/res/values/color.xml中定義的顏色-->
  android:background="@color/red"     

 <!--在layout文件中創(chuàng)建并使用顏色-->
  android:background="#ff0000"

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容