Ⅰ android 自定義View之Layout過程
系列文章:
在上篇文章: Android 自定義View之Measure過程 ,我們分析了Measure過程,本次將會掀開承上啟下的Layout過程神秘面紗,
通過本篇文章,你將了解到:
在上篇文章的比喻里,我們說過:
該ViewGroup 重寫了onMeasure(xx)和onLayout(xx)方法:
同時,當layout 執行結束,清除PFLAG_FORCE_LAYOUT標記,該標記會影響Measure過程是否需要執行onMeasure。
該View 重寫了onMeasure(xx)和onLayout(xx)方法:
MyViewGroup里添加了MyView、Button兩個控制項,最終運行的效果如下:
可以看出,MyViewGroup 里子布局的是橫向擺放的。我們重點關注Layout過程。實際上,MyViewGroup里我們只重寫了onLayout(xx)方法,MyView也是重寫了onLayout(xx)方法。
接下來,分析View Layout過程。
與Measure過程類似,連接ViewGroup onLayout(xx)和View onLayout(xx)之間的橋梁是View layout(xx)。
可以看出,最終都調用了setFrame(xx)方法。
對於Measure過程在onMeasure(xx)里記錄了尺寸的值,而對於Layout過程則在layout(xx)里記錄了坐標值,具體來說是在setFrame(xx)里,該方法兩個重點地方:
View.onLayout(xx)是空實現
從layout(xx)和onLayout(xx)聲明可知,這兩個方法都是可以被重寫的,接下來看看ViewGroup是否重寫了它們。
ViewGroup.layout(xx)雖然重寫了layout(xx),但是僅僅做了簡單判斷,最後還是調用了View.layout(xx)。
這重寫後將onLayout變為抽象方法,也就是說繼承自ViewGroup的類必須重寫onLayout(xx)方法。
我們以FrameLayout為例,分析其onLayout(xx)做了什麼。
FrameLayout.onLayout(xx)為子布局Layout的時候,起始坐標都是以FrameLayout為基準,並沒有記錄上一個子布局佔了哪塊位置,因此子布局的擺放位置可能會重疊,這也是FrameLayout布局特性的由來。而我們之前的Demo在水平方向上記錄了上一個子布局的擺放位置,下一個擺放時只能在它之後,因此就形成了水平擺放的功能。
由此類推,我們常說的某個子布局在父布局裡的哪個位置,決定這個位置的即是ViewGroup.onLayout(xx)。
上邊我們分析了View.layout(xx)、View.onLayout(xx)、ViewGroup.layout(xx)、ViewGroup.onLayout(xx),這四者什麼關系呢?
View.layout(xx)
View.onLayout(xx)
ViewGroup.layout(xx)
ViewGroup.onLayout(xx)
View/ViewGroup 子類需要重寫哪些方法:
用圖表示:
通過上述的描述,我們發現Measure過程和Layout過程里定義的方法比較類似:
它倆的套路比較類似:measure(xx)、layout(xx)一般不需要我們重寫,measure(xx)里調用onMeasure(xx),layout(xx)為調用者設置坐標值。
若是ViewGroup:onMeasure(xx)里遍歷子布局,並測量每個子布局,最後將結果匯總,設置自己測量的尺寸;onLayout(xx)里遍歷子布局,並設置每個子布局的坐標。
若是View:onMeasure(xx)則測量自身,並存儲測量尺寸;onLayout(xx)不需要做什麼。
Measure過程雖然比Layout過程復雜,但仔細分析後就會發現其本質就是為了設置兩個成員變數:
而Layout過程雖然比較簡單,其本質是為了設置坐標值
將Measure設置的變數和Layout設置的變數聯系起來:
此外,Measure過程通過設置PFLAG_LAYOUT_REQUIRED 標記來告訴需要進行onLayout,而Layout過程通過清除 PFLAG_FORCE_LAYOUT來告訴Measure過程不需要執行onMeasure了。
這就是Layout的承上作用
我們知道View的繪制需要依靠Canvas繪制,而Canvas是有作用區域限制的。例如我們使用:
Cavas繪制的起點是哪呢?
對於硬體繪制加速來說:正是通過Layout過程中設置的RenderNode坐標。
而對於軟體繪制來說:
關於硬體繪制加速/軟體繪制 後續文章會分析。
這就是Layout的啟下作用
以上即是Measure、Layout、Draw三者的內在聯系。
當然Layout的"承上"還需要考慮margin、gravity等參數的影響。具體用法參見最開始的Demo。
getMeasuredWidth()/getMeasuredHeight 與 getWidth/getHeight區別
我們以獲取width為例,分別來看看其方法:
getMeasuredWidth():獲取測量的寬,屬於"臨時值"
getWidth():獲取View真實的寬
在Layout過程之前,getWidth() 默認為0
何時可以獲取真實的寬、高
下篇將分析Draw()過程,我們將分析"一切都是draw出來的"道理
本篇基於 Android 10.0
Ⅱ Android 自定義View:為什麼你設置的wrap_content不起作用
在使用自定義View時,View寬 / 高的 wrap_content 屬性不起自身應有的作用,而且是起到與 match_parent 相同作用。
其實這里有兩個問題:
請分析 & 解決問題之前,請先看自定義View原理中 (2)自定義View Measure過程 - 最易懂的自定義View原理系列
問題出現在View的寬 / 高設置,那我們直接來看自定義View繪制中第一步對View寬 / 高設置的過程:measure過程中的 onMeasure() 方法
繼續往下看 getDefaultSize()
從上面發現:
那麼有人會問:wrap_content和match_parent具有相同的效果,為什麼是填充父容器的效果呢?
我們知道,子View的MeasureSpec值是根據子View的布局參數(LayoutParams)和父容器的MeasureSpec值計算得來,具體計算邏輯封裝在getChildMeasureSpec()里。
接下來,我們看生成子View MeasureSpec的方法: getChildMeasureSpec() 的源碼分析:
getChildMeasureSpec()
從上面可以看出,當子View的布局參數使用 match_parent 或 wrap_content 時:
所以: wrap_content 起到了和 match_parent 相同的作用:等於父容器當前剩餘空間大小
當自定義View的布局參數設置成wrap_content時時,指定一個默認大小(寬 / 高)。
這樣,當你的自定義View的寬 / 高設置成wrap_content屬性時就會生效了。
網上流傳著這么一個解決方案:
答: 是,當父View為 AT_MOST 、View為 match_parent 時,該View的 match_parent 的效果就等於 wrap_content 。上述方法存在邏輯錯誤,但由於這種情況非常特殊的,所以導致最終的結果沒有錯誤。具體分析請看下面例子:
從上面的效果可以看出,View大小 = 默認值
我再將子View的屬性改為 wrap_content :
從上面的效果可以看出,View大小還是等於默認值。
相信看到這里你已經看懂了:
為了更好的表示判斷邏輯,我建議你們用本文提供的解決方案,即根據布局參數判斷默認值的設置
不定期分享關於 安卓開發 的干貨,追求 短、平、快 ,但 卻不缺深度 。
Ⅲ Android自定義View基礎篇
在我們自定義View,尤其是製作一些復雜炫酷的效果的時候,實際上是將一些簡單的東西通過數學上精密的計算組合到一起形成的效果。這其中可能會涉及到畫布的相關操作(旋轉),以及一些正餘弦函數的計算等,這些內容就會用到一些角度、弧度相關的知識。
圓一周對應的角度為360度(角度),對應的弧度為2π弧度。
故得等價關系:360(角度) = 2π(弧度) ==> 180(角度) = π(弧度)
幾種創建或使用顏色的方式
Android自定義屬性可分為以下幾步:
2.自定義View中獲取屬性
3.在布局中使用
4.屬性值的類型歸納
Ⅳ Carson帶你學Android:手把手教你寫一個完整的自定義View
自定義View一共分為兩大類,具體如下圖:
對於自定義View的類型介紹及使用場景如下圖:
在使用自定義View時有很多注意點(坑),希望大家要非常留意:
View的內部本身提供了post系列的方法,完全可以替代Handler的作用,使用起來更加方便、直接。
主要針對View中含有線程或動畫的情況: 當View退出或不可見時,記得及時停止該View包含的線程和動畫,否則會造成內存泄露問題 。
當View帶有滑動嵌套情況時,必須要處理好滑動沖突,否則會嚴重影響View的顯示效果。
接下來,我將用自定義View中最常用的 繼承View 來說明自定義View的具體應用和需要注意的點
在下面的例子中,我將講解:
下面我將逐個步驟進行說明:
步驟1:創建自定義View類(繼承View類)
特別注意:
步驟2:在布局文件中添加自定義View類的組件及顯示
至此,一個基本的自定義View已經實現了,運行效果如下圖。
接下來繼續看自定義View關於屬性自定義的問題:
先來看wrap_content & match_parent屬性的區別
如果不手動設置支持 wrap_content 屬性,那麼 wrap_content 屬性是不會生效(顯示效果同 match_parent )
padding 屬性:用於設置控制項內容相對控制項邊緣的邊距;
如果不手動設置支持padding屬性,那麼padding屬性在自定義View中是不會生效的。
繪制時考慮傳入的padding屬性值(四個方向)。
除了常見的以android:開頭的系統屬性(如下所示),很多場景下自定義View還需要系統所沒有的屬性,即自定義屬性。
實現自定義屬性的步驟如下:
下面我將對每個步驟進行具體介紹
對於自定義屬性類型 & 格式如下:
至此,一個較為規范的自定義View已經完成了。
Carson_Ho的github: 自定義View的具體應用
不定期分享關於 安卓開發 的干貨,追求 短、平、快 ,但 卻不缺深度 。
Ⅳ Android自定義布局ViewGroup
2.onLayout()方法中,通過view.layout(left,right,top,bottom)。
layout方法會設置該View視圖位於父視圖的坐標軸
其中left:子布局的左側到父布局左側的距離,right:子布局的右側到父布局左側的距離,top:子布局的上側到父布局上側的距離,bottom:子布局的下側到父布局上側的距離。
Ⅵ Android開發 自定義View
Android自定義View實現很簡單:
1、繼承View,重寫構造函數、onDraw,(onMeasure)等函數。
2、如果自定義的View需要有自定義的屬性,需要在values下建立attrs.xml。在其中定義你的屬性。
3、在使用到自定義View的xml布局文件中需要加入xmlns:前綴="http://schemas.android.com/apk/res/你的自定義View所在的包路徑".
4、在使用自定義屬性的時候,使用前綴:屬性名,如my:textColor="#FFFFFFF"。
實例:
自定義TextView類:
復制代碼
package com.zst.service.component;
import com.example.hello_wangle.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class MyTextView extends TextView {
//不能在布局文件中使用
public MyTextView(Context context) {
super(context);
}
//布局文件中用到此構造函數
Ⅶ android 自定義View 使用 DataBinding 筆記
在 build.gradle 文件中添加配置
系統會根據布局文件名稱自動生成相應的 DataBinding 類,例如
布局文件 activity_main.xml 會生成 ActivityMainBinding 類 ,類名生成規則為駝峰命名: 文件名(首字母大寫) + Binding
activity 中使用:
fragment 中使用:
這里 android:text="@{viewmodel.text}" 對text進行設置
在 Edittext 中可以使用 android:text="@={viewmodel.text}" 進行雙向綁定,關鍵是這個 = 號;
若需要在xml布局文件中使用系統類的屬性,則需要引入.例:
通過引入 <import type="android.view.View"/> 可以使用 View.VISIBLE 、 View.GONE 屬性
有時候自定義view要用DataBinding時需要通過 @BindingAdapter 設置
單向綁定設置比較簡單 @BindingAdapter("app:paramA") 方法名為 setParamA ,單向綁定嘛就是傳入參數:
雙向綁定 set 、 get 方法,最主要的是 setListeners 方法,名字可以隨意,只是 @BindingAdapter 中是 "app:paramBAttrChanged" ,在屬性後面跟上 AttrChanged , attrChange 為通知系統內容變更的回調,當在 MineView 內部 paramB 發生變更時,通過 attrChange 去通知對 paramB 進行綁定的對象更新自己緩存中的內容 如:
希望對您有幫助.
Ⅷ Android 自定義View之Draw過程(上)
Draw 過程系列文章
Android 展示之三部曲:
前邊我們已經分析了:
這倆最主要的任務是: 確定View/ViewGroup可繪制的矩形區域。
接下來將會分析,如何在這給定的區域內繪制想要的圖形。
通過本篇文章,你將了解到:
Android 提供了關於View最基礎的兩個類:
然而ViewGroup 並沒有約定其內部的子View是如何布局的,是疊加在一起呢?還是橫向擺放、縱向擺放等。同樣的View 也沒有約定其展示的內容是啥樣,是矩形、圓形、三角形、一張圖片、一段文字抑或是不規則的形狀?這些都要我們自己去實現嗎?
不盡然,值得高興的是Android已經考慮到上述需求了,為了開發方便已經預制了一些常用的ViewGroup、View。
如:
繼承自ViewGroup的子類
繼承自View的子類
雖然以上衍生的View/ViewGroup子類已經大大為我們提供了便利,但也僅僅是通用場景下的通用控制項,我們想實現一些較為復雜的效果,比如波浪形狀進度條、會發光的球體等,這些系統控制項就無能為力了,也沒必要去預制千奇百怪的控制項。想要達到此效果,我們需要自定義View/ViewGroup。
通常來說自定義View/ViewGroup有以下幾種:
3 一般不怎麼用,除非布局比較特殊。1、2、4 是我們常用的手段,對於我們常說的"自定義View" 一般指的是 4。
接下來我們來看看 4是怎麼實現的。
在xml里引用MyView
效果如下:
黑色部分為其父布局背景。
紅色矩形+黃色圓形即是MyView繪制的內容。
以上是最簡單的自定義View的實現,我們提取重點歸納如下:
由上述Demo可知,我們只需要在重寫的onDraw(xx)方法里繪制想要的圖形即可。
來看看View 默認的onDraw(xx)方法:
發現是個空實現,因此繼承自View的類必須重寫onDraw(xx)方法才能實現繪制。該方法傳入參數為:Canvas類型。
Canvas翻譯過來一般叫做畫布,在重寫的onDraw(xx)里拿到Canvas對象後,有了畫布我們還需要一支筆,這只筆即為Paint,翻譯過來一般稱作畫筆。兩者結合,就可以愉快的作畫(繪制)了。
你可能發現了,在Demo里調用
並沒有傳入Paint啊,是不是Paint不是必須的?實際上調用該方法後,底層會自動生成Paint對象。
可以看到,底層初始化了Paint,並且給其設置的顏色為在java層設置的顏色。
onDraw(xx)比較簡單,開局一個Canvas,效果全靠畫。
試想,這個Canvas怎麼來的呢,換句話說是誰調用了onDraw(xx)。發揮一下聯想功能,在Measure、Layout 過程有提到過兩者套路很像:
那麼Draw過程是否也是如此套路呢?看見了onDraw(xx),那麼draw(xx)還遠嗎?
沒錯,還真有draw(xx)方法:
可以看出,draw(xx)主要分為兩個部分:
不管是A分支還是B分支,都進行了好幾步的繪制。
通常來說,單一一個View的層次分為:
後面繪制的可能會遮擋前邊繪制的。
對於一個ViewGroup來說,層次分為:
來看看A分支標注的4個點:
(1)
onDraw(canvas)
前面分析過,對於單一的View,onDraw(xx)是空實現,需要由我們自定義繪制。
而對於ViewGroup,也並沒有具體實現,如果在自定義ViewGroup里重寫onDraw(xx),它會執行嗎?默認是不會執行的,相關分析請移步:
Android ViewGroup onDraw為什麼沒調用
(2)
dispatchDraw(canvas),來看看在View.java里的實現:
發現是個空實現,再看看ViewGroup.java里的實現:
也即是說,對於單一View,因為沒有子布局,因此沒必要再分發Draw,而對於ViewGroup來說,需要觸發其子布局發起Draw過程(此過程後續分析),可以類比事件分發過程View、ViewGroup的處理。感興趣的請移步:
Android 輸入事件一擼到底之View接盤俠(3)
(3)
OverLay,顧名思義就是"蓋在某個東西上面",此處是在繪制內容之後,繪制前景之前。怎麼用呢?
以上是給一個ViewGroup設置overLay,效果如下:
你可能發現了,這和設置overLay差不多的嘛,實際還是有差別的。在onDrawForeground(xx)里會重新調整Drawable的尺寸,該尺寸與View大小一致,之前給Drawable設置的尺寸會失效。運行效果如下:
可以看出,ViewGroup都被前景蓋住了。
再來看看B分支的重點:邊緣漸變效果
先來看看TextView 邊緣漸變效果:
加上這倆參數。
實際上系統自帶的一些控制項也使用了該效果,如NumberPicker、YearPickerView
以上是NumberPicker 的效果,可以看出是垂直方向漸變的。
對於View.java 里的onDraw(xx)、draw(xx),ViewGroup.java里並沒有重寫。
而對於dispatchDraw(xx),在View.java里是空實現。在ViewGroup.java里發起對子布局的繪制。
來看看標記的2點:
(1)
設置padding的目的是為了讓子布局留出一定的空隙出來,因此當設置了padding後,子布局的canvas需要根據padding進行裁減。判斷標記為:
FLAG_CLIP_TO_PADDING 默認設置為true
FLAG_PADDING_NOT_NULL 只要有padding不為0,該標記就會打上。
也就是說:只要設置了padding 不為0,子布局顯示區域需要裁減。
能不能不讓子布局裁減顯示區域呢?
答案是可以的。
考慮到一種場景:使用RecyclerView的時候,我們需要設置paddingTop = 20px,效果是:RecyclerView Item展示時離頂部有20px,但是滾動的時候永遠滾不到頂部,看起來不是那麼友好。這就是上述的裁減起作用了,需要將此動作禁止。通過設置:
當然也可以在xml里設置:
(2)
drawChild(xx)
從方法名上看是調用子布局進行繪制。
child.draw(x1,x2,x3)里分兩種情況:
這兩者具體作用與區別會在下篇文章分析,不管是硬體加速繪制還是軟體加速繪制,最終都會調用View.draw(xx)方法,該方法上面已經分析過。
注意,draw(x1,x2,x3)與draw(xx)並不一樣,不要搞混了。
用圖表示:
View/ViewGroup Draw過程的聯系:
一般來說,我們通常會自定義View,並且重寫其onDraw(xx)方法,有沒有繪制內容的ViewGroup需求呢?
是有的,舉個例子,大家可以去看看RecyclerView ItemDecoration 的繪制,其中運用到了ViewGroup draw(xx)、ViewGroup onDraw(xx) 、View onDraw(xx)繪制的先後順序來實現分割線,分組頭部懸停等功能的。
本篇文章基於 Android 10.0
Ⅸ Android自定義View
View的構造函數:共有4個
系統自帶的View可以在xml中配置屬性,對於寫的好的自定義View同樣可以在xml中配置屬性,為了使自定義的View的屬性可以在xml中配置,需要以下4個步驟:
一定要記住:無論是measure過程、layout過程還是draw過程,永遠都是從View樹的根節點開始測量或計算(即從樹的頂端開始),一層一層、一個分支一個分支地進行(即樹形遞歸),最終計算整個View樹中各個View,最終確定整個View樹的相關屬性。
Android的坐標系定義為:
View的位置由4個頂點決定的 4個頂點的位置描述分別由4個值決定:
View的位置是通過view.getxxx()函數進行獲取:(以Top為例)
與MotionEvent中 get()和getRaw()的區別
MarginLayoutParams是和外間距有關的。事實也確實如此,和LayoutParams相比,MarginLayoutParams只是增加了對上下左右外間距的支持。實際上大部分LayoutParams的實現類都是繼承自MarginLayoutParams,因為基本所有的父容器都是支持子View設置外間距的。
1. 創建自定義屬性
2. 繼承MarginLayout
3. 重寫ViewGroup中幾個與LayoutParams相關的方法
在為View設置LayoutParams的時候需要根據它的父容器選擇對應的LayoutParams,否則結果可能與預期不一致,這里簡單羅列一些常見的LayoutParams子類:
測量規格,封裝了父容器對 view 的布局上的限制,內部提供了寬高的信息( SpecMode 、 SpecSize ),SpecSize是指在某種SpecMode下的參考尺寸,其中SpecMode 有如下三種:
針對上表,這里再做一下具體的說明
一般getIntrinsicWidth/Height能獲得內部寬/高 圖片Drawable其內部寬高就是圖
片的寬高 顏色Drawable沒有內部寬高的概念 內部寬高不等同於它的大小,一般
Drawable沒有大小概念(作為View背景時,會被拉伸至View的大小)