因?yàn)楣ぷ髟颍雽懸黄远xview的初級心得筛璧。
一卧波、一般而言寫自定義view有大體6個(gè)步驟(以下順序不分先后):
- 繼承View的某個(gè)子類,包括ViewGroup的子類(畢竟ViewGroup也是View的子類嘛╮(╯_╰)╭) 2. 重寫繼承的父類View的一些特定函數(shù)及常用的三個(gè):(測量measure),(放置layout)恃锉,(繪制draw)3.為自定義View類增加屬性(主要是在那三個(gè)重寫的構(gòu)造方法里)4.繪制控件(代碼形式導(dǎo)入布局)5.響應(yīng)用戶事件(單擊搀菩、輸入文字、觸摸破托、滑動等等~~)6.定義回調(diào)函數(shù)(相當(dāng)于反饋信息嘛)
二肪跋、針對繼承對象的不同自定義View分為繼承View 與ViewGroup兩種的情況,我上面2里的所說的常用三個(gè)使用上有所區(qū)別土砂。
測量measure:
View:
普通View的onMeasure邏輯大同小異州既,基本都是測量自身內(nèi)容和背景,然后根據(jù)父View傳遞過來的MeasureSpec進(jìn)行最終的大小判定萝映,例如TextView會根據(jù)文字的長度吴叶,文字的大小,文字行高序臂,文字的行寬蚌卤,顯示方式,背景圖片奥秆,以及父View傳遞過來的模式和大小最終確定自身的大小逊彭。具體的View寬高測量是調(diào)用了 setMeasuredDimension() 方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
onMeasure通過父View傳遞過來的大小和模式,以及自身的背景圖片的大小得出自身最終的大小构订,通過setMeasuredDimension()方法設(shè)置給mMeasuredWidth和mMeasuredHeight侮叮。 ViewGroup:
ViewGroup本身沒有實(shí)現(xiàn)onMeasure(但是!有setMeasuredDimension()方法)鲫咽,但是他的子類(比如:四大布局控件)都有各自的實(shí)現(xiàn)签赃,通常他們都是通過measureChildWithMargins()這種測量內(nèi)部子view的方法來遍歷內(nèi)部,測量子View分尸。當(dāng)所有的子View都測量完畢后锦聊,才根據(jù)父View傳遞過來的模式和大小來最終決定自身的大小。
** 注意事項(xiàng):如果子View被GONE的將不參與測量箩绍。**
ViewGroup一般都在測量完所有子View后才會調(diào)用setMeasuredDimension()設(shè)置自身大小孔庭。
經(jīng)過measure 完成后,我們就可以通過getMeasuredWidth/Height 獲取View 的寬高材蛛。 放置layout:
View:
普通View中的onLayout()這個(gè)函數(shù)為空函數(shù)圆到。所以不用理會,想想也是的吧卑吭,如果你繼承的是view芽淡,你還有擺放你里面的內(nèi)容嗎?如果里面有東西需要你的擺放豆赏,那么挣菲,這個(gè)view不就是父view了富稻!這個(gè)不就該是繼承的是ViewGroup。好的白胀,往下看椭赋。
ViewGroup:
對于ViewGroup而言,循環(huán)遍歷所有子View是主要的思想;蚋堋D恼!因此如果我們繼承ViewGroup 我們需要遍歷執(zhí)行所有的child.layout()向抢。
Layout方法中接受四個(gè)參數(shù)认境,是由父View提供,指定了子View在父View中的左笋额、上元暴、右篷扩、下的位置兄猩。父View在指定子View的位置時(shí)通常會根據(jù)子View在measure中測量的大小來決定。注意事項(xiàng):子View的位置通常還受到父View的orientation鉴未,gravity枢冤,padding,子View的margin等等屬性的影響哦铜秆,我相信寫過在xml寫過布局的各位大大肯定是了解的吧淹真。
ViewGroup中的onLayout()方法:
@Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
抽象就表示了繼承ViewGroup的子類布局控件,都要去重寫连茧。而這個(gè)重寫也就導(dǎo)致了核蘸,不同的布局方式。怎么重寫呢啸驯?舉個(gè)例子:我這里將第一個(gè)子控件通過layout()放置到左上角0,0 寬高是測量值客扎。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
View childView = getChildAt(0); childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight()); }
繪制draw draw()的過程就是繪制View到屏幕上的過程,draw()的執(zhí)行遵循如下步驟:
- 繪制背景
2.保存畫布的圖層來準(zhǔn)備色變 - 繪制內(nèi)容
4.繪制children
5.畫出褪色的邊緣和恢復(fù)層 - 繪制裝飾 比如scollbar
2和5 可以跳過的罚斗。
View:
view中onDraw()是個(gè)空函數(shù)徙鱼,也就是說需要每個(gè)視圖根據(jù)想要展示的內(nèi)容來自行繪制,View是不會幫我們繪制內(nèi)容部分的针姿,因此需要每個(gè)視圖根據(jù)想要展示的內(nèi)容來自行繪制袱吆。如果你去觀察TextView、ImageView等類的源碼距淫,你會發(fā)現(xiàn)它們都有重寫onDraw()這個(gè)方法绞绒,并且在里面執(zhí)行了相當(dāng)不少的繪制邏輯: 在TextView中在該方法中繪制文字、光標(biāo)和CompoundDrawable;ImageView中相對簡單榕暇,只是繪制了圖片蓬衡。繪制的方式主要是借助Canvas這個(gè)類饲趋,它會作為參數(shù)傳入到onDraw()方法中,供給每個(gè)視圖使用撤蟆。Canvas這個(gè)類的用法非常豐富奕塑,基本可以把它當(dāng)成一塊畫布,在上面繪制任意的東西家肯,那么我們就來嘗試一下吧龄砰。
View 的繪制主要通過dispatchDraw(),先根據(jù)自身的padding剪裁畫布,所有的子View都將在畫布剪裁后的區(qū)域繪制讨衣。遍歷所有子View换棚,調(diào)用子View的computeScroll對子View的滾動值進(jìn)行計(jì)算。根據(jù)滾動值和子View在父View中的坐標(biāo)進(jìn)行畫布原點(diǎn)坐標(biāo)的移動反镇,根據(jù)子在父View中的坐標(biāo)計(jì)算出子View的視圖大小固蚤,然后對畫布進(jìn)行剪裁,請看下面的示意圖歹茶。
ViewGroup:
對于ViewGroup則不需要實(shí)現(xiàn)該函數(shù)夕玩,因?yàn)樽鳛槿萜魇恰皼]有內(nèi)容“的(但必須ViewGroup要有實(shí)現(xiàn)dispatchDraw()函數(shù),告訴子view去繪制自己)惊豺。注意事項(xiàng):dispatchDraw的邏輯其實(shí)比較復(fù)雜燎孟,但ViewGroup已經(jīng)處理好了,我們不必要重載該方法對子View進(jìn)行繪制事件的派遣分發(fā)尸昧。
三揩页、其他一些可以用來重寫的方法:
onTouchEvent定義觸屏事件來響應(yīng)用戶操作。 onKeyDown 當(dāng)按下某個(gè)鍵盤時(shí)
onKeyUp 當(dāng)松開某個(gè)鍵盤時(shí)
onTrackballEvent 當(dāng)發(fā)生軌跡球事件時(shí)
onSizeChange() 當(dāng)該組件的大小被改變時(shí)
onFinishInflate() 回調(diào)方法烹俗,當(dāng)應(yīng)用從XML加載該組件并用它構(gòu)建界面之后調(diào)用的方法
onWindowFocusChanged(boolean) 當(dāng)該組件得到爆侣、失去焦點(diǎn)時(shí)
onAttachedToWindow() 當(dāng)把該組件放入到某個(gè)窗口時(shí)
onDetachedFromWindow() 當(dāng)把該組件從某個(gè)窗口上分離時(shí)觸發(fā)的方法
onWindowVisibilityChanged(int): 當(dāng)包含該組件的窗口的可見性發(fā)生改變時(shí)觸發(fā)的方法
四、View的繪制流程
繪制流程函數(shù)調(diào)用關(guān)系如下圖(取來用之):
五:requestLayout() 幢妄、invalidate()兔仰、postInvalidate()
requestLayout(): 當(dāng)view確定自身已經(jīng)不再適合現(xiàn)有的區(qū)域時(shí),該view本身調(diào)用requestLayout()方法來要求parent view(父類的視圖)重新調(diào)用他的measure和layout來重新設(shè)置自己位置磁浇。特別是當(dāng)view的layoutparameter發(fā)生改變斋陪,并且它的值還沒能應(yīng)用到view上時(shí),這時(shí)候適合調(diào)用這個(gè)方法置吓。注意无虚,并不會不執(zhí)行ondraw。
invalidate()衍锚、postInvalidate(): 調(diào)用invalidate()友题、postInvalidate()會 界面刷新,執(zhí)行 draw 過程戴质。區(qū)別就是Invalidate不能直接在線程中調(diào)用度宦,因?yàn)樗沁`背了單線程模型:Android UI操作并不是線程安全的踢匣,并且這些操作必須在UI線程中調(diào)用。 鑒于此戈抄,如果要使用invalidate的刷新离唬,那我們就得配合handler的使用,使異步非ui線程轉(zhuǎn)到ui線程中調(diào)用划鸽,如果要在非ui線程中直接使用就調(diào)用postInvalidate方法即可输莺,這樣就省去使用handler的煩惱。
六裸诽、自定義控件的三種方式
1嫂用、 繼承已有的控件當(dāng)要實(shí)現(xiàn)的控件和已有的控件在很多方面比較類似, 通過對已有控件的擴(kuò)展來滿足要求。即:繼承TextView丈冬、Button這樣已有的View(包括項(xiàng)目里已有的自定義View)嘱函。
2、 繼承一個(gè)布局文件一般用于自定義組合控件埂蕊,在構(gòu)造函數(shù)中通過inflater和addView()方法加載自定義控件的布局文件形成圖形界面(不需要onDraw方法)往弓,就好像是把a(bǔ)ctivity的xml變成用自定義view的xml來表示绳泉。
3供璧、繼承view通過onDraw方法來繪制出組件界面。即繼承View,得到和TextView、Button這樣等級的View 匀们。
七、自定義屬性的兩種方法
1准给、在布局文件中直接加入屬性泄朴,在構(gòu)造函數(shù)中去獲得。
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<rcjs.com.customview.ZYView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Text="rcjs"
/>
</RelativeLayout>
獲取屬性值:
public ZYView(Context context, AttributeSet attrs) {
super(context, attrs);
int textId = attrs.getAttributeResourceValue(null, "Text", 0);
String text = context.getResources().getText(textId).toString();
}
2露氮、在res/values/ 下建立一個(gè)attrs.xml 來聲明自定義view的屬性祖灰。
可以定義的屬性有:
<declare-styleable name="名稱">//參考某一資源ID (name可以隨便命名)
<attr name="background" format="reference"/>
//顏色值
<attr name="textColor" format="color"/>
//布爾值
<attr name="focusable" format="boolean"/>
//尺寸值
<attr name="layout_width" format="dimension"/>
//浮點(diǎn)值
<attr name="fromAlpha" format="float"/>
//整型值
<attr name="frameDuration" format="integer"/>
//字符串
<attr name="text" format="string"/>
//百分?jǐn)?shù)
<attr name="pivotX" format="fraction"/>
//枚舉值
<attr name="orientation">
<enum name="horizontal" value="0"/>
<enum name="vertical" value="1"/>
</attr>
//位或運(yùn)算
<attr name="windowSoftInputMode">
<flag name="stateUnspecified" value="0"/>
<flag name="stateUnchanged" value="1"/>
</attr>
//多類型
<attr name="background" format="reference|color"/>
</declare-styleable>
attrs.xml進(jìn)行屬性聲明
declare-styleable的name 就是自定義的名稱用于布局文件里去
attr的name是屬性名稱
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="zyView">
<attr name="Text" format="string"/>
<attr name="textColor" format="color"/>
</declare-styleable>
</resources>
添加到布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:zyView="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<rcjs.com.customview.ZYView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
zyView:Text="rcjs"
/>
</RelativeLayout>
注意事項(xiàng):
**命名空間: **
xmlns:前綴=”http://schemas.android.com/apk/res/包名(或res-auto)”,
前綴:+使用屬性畔规。
在構(gòu)造函數(shù)中獲取屬性值局扶,注意!Hā三妈!我想有一些人應(yīng)該會很郁悶 ,復(fù)制粘貼了自定義view.class后莫绣,發(fā)現(xiàn)自定義view的構(gòu)造方法里面獲得資源文件里的屬性時(shí)****畴蒲,看到R.styleable.XXX這個(gè),然后點(diǎn)擊時(shí)****找不到具體寫的地方对室。其實(shí)這個(gè)就在res -> values ->attrs里模燥。所以要記得去copy哦咖祭。
public class ZYView extends View {
public ZYView(Context context) {
super(context);
}
public ZYView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//獲取資源文件里面的屬性,由于這里只有一個(gè)屬性值蔫骂,不用遍歷數(shù)組么翰,直接通過R文件拿出color值
//把屬性放在資源文件里,方便設(shè)置和復(fù)用
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.zyView); String text = a.getString(R.styleable.zyView_Text);
int textColor = a.getColor(R.styleable.zyView_textColor, Color.WHITE); a.recycle();
}
}
八辽旋、結(jié)尾
這只是讓大家知道自定義view的制作需要什么和要哪些步驟硬鞍。像我這種完全一竅不通的,然后一下子去接觸自定義view的戴已,是會很糊涂的固该,所以,在此糖儡,小僧稍微筆記一波伐坏,助人助己。而具體的對自定義view的學(xué)習(xí)握联,請待續(xù)桦沉。。金闽。
當(dāng)然纯露,現(xiàn)在已有大佬們寫了很多博客。請參考這篇總的去學(xué)習(xí):
http://www.reibang.com/p/6aea80e1fa22