學(xué)習(xí)資料:
- Android開(kāi)發(fā)藝術(shù)探索
- 愛(ài)哥自定義控件其實(shí)很簡(jiǎn)單3/4
一般自定義一個(gè)View
有4種思路:
- 直接繼承
View
- 直接繼承
ViewGroup
- 繼承現(xiàn)有的
View
揭糕,例如ImageViwe
,SurfaceView
- 繼承現(xiàn)有的
ViewGroup
试伙,例如LinearLayout
推薦優(yōu)先考慮3
或者4
析校。原因也比較明顯鳞尔,無(wú)論哪一種方式滥比,需要注意的事項(xiàng)都不少牙咏。采用3
或者4
的話臼隔,現(xiàn)有控件的一些特性可以直接拿來(lái)用,而且也更加容易做適配
1.自定義View注意事項(xiàng) <p>
之前有一個(gè)誤區(qū)妄壶,總覺(jué)得自定義View
就得是直接繼承View
摔握,然后自己實(shí)現(xiàn)各種效果
1.1 讓View支持 wrap_content <p>
在之前的學(xué)習(xí)中,重寫(xiě)onMeasure()
方法的目的就是為了支持wrap_content
丁寄。原因在學(xué)習(xí)View
和ViewGroup
中氨淌,也進(jìn)行了嘗試說(shuō)明,View
是的大小是要受自身和父容器ViewGroup
影響伊磺。想要讓wrap_content
擁有包裹內(nèi)容
的特性盛正,就需要重寫(xiě)View
和ViewGroup
的onMeausre()
方法,針對(duì)要加載內(nèi)容或者childView
的寬高來(lái)設(shè)置自定義View
的寬高屑埋。這也是為啥推薦方式3
或者4
的原因之一
1.2 根據(jù)需要豪筝,考慮Padding和Margin <p>
- 直接繼承的
View
,Margin
是不用考慮的,之前的學(xué)習(xí)提到過(guò)壤蚜,這個(gè)屬性被封裝進(jìn)了LayoutParamas
由父容器ViewGroup
來(lái)處理即寡。
Padding
屬性需要根據(jù)需求在onMeasure()
和onDraw()
方法中處理 - 直接繼承的
ViewGroup
,在onMeadsure()
方法中袜刷,ViewGroup
自身的Margin
屬性是直接支持的聪富。需要考慮的是封裝進(jìn)了LayoutParamas
中的childView
的Margin
屬性信息。利用childView
拿到的LayoutParams
中的Margin
屬性信息著蟹,在測(cè)量時(shí)就對(duì)childView
的寬高進(jìn)行處理墩蔓。在onLayout()
確定childView
四個(gè)頂點(diǎn)位置時(shí),將Margin
屬性信息也做處理萧豆。Padding
屬性的處理類(lèi)似Margin
奸披,也需要在onMeausre
和onLayout()
方法中,都進(jìn)行處理
1.3 盡量不要在View中創(chuàng)建Handler <p>
View
自身提供了post
系列方法涮雷,里面封裝的有Handler
阵面。除非需要明確使用Handlder
來(lái)發(fā)送消息
注意:
post(new Runnable(){....})
中的一系列操作依然是在主線程,最好不要進(jìn)行耗時(shí)的操作洪鸭。而執(zhí)行網(wǎng)絡(luò)請(qǐng)求會(huì)直接造成應(yīng)用崩潰样刷,給出android.os.NetworkOnMainThreadException
異常
1.4 View中有線程后者動(dòng)畫(huà),考慮關(guān)閉時(shí)機(jī) <p>
在Android 自定義View學(xué)習(xí)(九)——Bezier貝塞爾曲線學(xué)習(xí)中览爵,處理過(guò)一次這樣的情況
主要使用到一個(gè)onDetachedFromWindow()
的方法置鼻。在View
所在的Activity
退出或者當(dāng)前View
自身被remove
點(diǎn)時(shí),View
的onDetachedFromWindow()
方法便會(huì)被回調(diào)蜓竹。在這個(gè)方法內(nèi)箕母,將正在運(yùn)行的動(dòng)畫(huà)或者線程關(guān)閉掉,能夠減少內(nèi)存泄露的出現(xiàn)
1.5 View有嵌套時(shí)俱济,考慮點(diǎn)擊事件和滑動(dòng)沖突
繼承之一些可以滑動(dòng)的ViewGroup
后嘶是,常需要考慮滑動(dòng)沖突。View
的事件體系蛛碌,下篇開(kāi)始學(xué)習(xí)俊啼。
以上都是看Android開(kāi)發(fā)藝術(shù)探索
后做的總結(jié)
2. 直接繼承View
雖然之前學(xué)習(xí)過(guò)程中,用過(guò)了十幾次了左医,但每次都是按照固定套路來(lái),并沒(méi)有進(jìn)行一些思考同木。
- 構(gòu)造方法又都有啥啥區(qū)別浮梢?
-
View
有沒(méi)有生命周期? 有 -
View
和Activity
的生命周期有啥聯(lián)系沒(méi)有彤路? 有
這里之前搞錯(cuò)了秕硝。說(shuō)
View
和Activity
的生命周期沒(méi)有聯(lián)系。View
依賴于Activity
洲尊,不可能沒(méi)有聯(lián)系远豺。感謝奈偏,Android之路
留言指出錯(cuò)誤。但躯护,具體啥聯(lián)系惊来,暫時(shí)不打算學(xué)習(xí),想留在后面具體學(xué)習(xí)Activity
時(shí)棺滞,再來(lái)補(bǔ)充
直接繼承View
后裁蚁,代碼:
public class LifecycleView extends View {
private final String TAG = "LifecycleView";
public LifecycleView(Context context) {
super(context);
Log.e(TAG,"&&&第1個(gè)構(gòu)造方法");
}
public LifecycleView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.e(TAG,"&&&第2個(gè)構(gòu)造方法");
for(int i=0;i<attrs.getAttributeCount();i++){
Log.e(TAG, "&&&第2個(gè)構(gòu)造方法"+attrs.getAttributeName(i)+"--->"+attrs.getAttributeValue(i));
}
}
public LifecycleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.e(TAG,"&&&第3個(gè)構(gòu)造方法");
}
public LifecycleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Log.e(TAG,"&&&第4個(gè)構(gòu)造方法");
}
}
打印出的Log
信息:
根據(jù)Log
信息,很明顯得出继准,當(dāng)在xml
布局文件中聲明了LifecycleView
枉证,系統(tǒng)就會(huì)調(diào)用第2個(gè)構(gòu)造方法,并且通過(guò)參數(shù)attrs
可以拿到布局中移必,LifecycleView
中的標(biāo)簽屬性室谚。布局中控件的屬性就封裝在AttributeSet
中
第1個(gè)構(gòu)造方法是直接進(jìn)行new LifecycleView(mContext)
時(shí),會(huì)被調(diào)用崔泵,但很少這樣用
結(jié)論1:當(dāng)在布局中使用自定義View時(shí)秒赤,調(diào)用第2個(gè)構(gòu)造方法
可第3和第4構(gòu)造方干嘛用的?
2.1 使用自定義屬性 <p>
第3個(gè)構(gòu)造方法管削,需要一個(gè)declare-styleable
自定義屬性
關(guān)于自定義屬性可以看看鴻洋大神的Android 深入理解Android中的自定義屬性
先在style.xnl
布局文件中聲明一個(gè)
<declare-styleable name="CustomProperty">
<attr name="number" format="integer" />
<attr name="text" format="string" />
</declare-styleable>
定義好自定義屬性后倒脓,LifecycleView
中使用:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<com.szlk.customview.custom.LifecycleView
custom:text="英勇青銅5"
custom:number="521"
android:layout_width="100dp"
android:layout_height="100dp" />
</RelativeLayout>
修改LifecycleView
代碼:
public class LifecycleView extends View {
private final String TAG = "LifecycleView";
public LifecycleView(Context context) {
super(context);
Log.e(TAG,"&&&第1個(gè)構(gòu)造方法");
}
public LifecycleView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.e(TAG,"&&&第2個(gè)構(gòu)造方法");
for(int i=0;i<attrs.getAttributeCount();i++){
Log.e(TAG, "&&&&&&第2個(gè)構(gòu)造方法"+attrs.getAttributeName(i)+"--->"+attrs.getAttributeValue(i));
}
}
public LifecycleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.e(TAG,"&&&第3個(gè)構(gòu)造方法");
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomProperty);
String text = ta.getString(R.styleable.CustomProperty_text);
int textAttr = ta.getInteger(R.styleable.CustomProperty_number, -1);
Log.e(TAG,"&&&第3個(gè)構(gòu)造方法"+text+"-----"+textAttr);
ta.recycle();
}
public LifecycleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Log.e(TAG,"&&&第4個(gè)構(gòu)造方法");
}
}
本以為會(huì)調(diào)用第3個(gè)構(gòu)造方法,Log
信息卻顯示含思,調(diào)用的依然是第2個(gè)構(gòu)造方法崎弃,但此時(shí)已經(jīng)拿到了自定義的屬性
結(jié)論2:在布局文件中使用自定義屬性后,仍然調(diào)用的第2個(gè)構(gòu)造方法
在別的博客看到含潘,想要調(diào)用到第3和4個(gè)構(gòu)造方法饲做,只有在第2個(gè)構(gòu)造方法使用this
進(jìn)行調(diào)用
再次對(duì)代碼進(jìn)行修改:
//構(gòu)造方法1
this(context,null);
//構(gòu)造方法2
this(context, attrs,0);
//構(gòu)造方法3
this(context, attrs, defStyleAttr,0);
此時(shí)就調(diào)用了第4和第3個(gè)構(gòu)造方法,但最終還是會(huì)調(diào)用第2個(gè)構(gòu)造方法
至于為啥這樣設(shè)計(jì)構(gòu)造方法遏弱,雖然看了幾篇博客盆均,我暫時(shí)依然沒(méi)有想明白。難道是一開(kāi)始系統(tǒng)默認(rèn)會(huì)給一個(gè)屬性漱逸,利用第3和第4個(gè)構(gòu)造方法可以更改系統(tǒng)默認(rèn)屬性泪姨?
2.2 關(guān)于四個(gè)參數(shù) <p>
第一個(gè)和第二個(gè)參數(shù)都比較容易理解。
-
第3個(gè)參數(shù)
int defStyleAttr
饰抒,當(dāng)前Theme中的包含的一個(gè)指向style的引用默認(rèn)值為0肮砾,是一個(gè)屬性資源。沒(méi)有使用第2個(gè)參數(shù)給自定義View設(shè)置declare-styleable資源集合時(shí),默認(rèn)從這個(gè)集合里面查找布局文件中配置屬性值袋坑。傳入0表示不向該defStyleAttr中查找默認(rèn)值仗处,只要在主題中對(duì)這個(gè)屬性賦值,該View就會(huì)自動(dòng)應(yīng)用這個(gè)屬性的值。
-
第4個(gè)參數(shù)
int defStyleRes
婆誓,也就是在布局文件中使用的style="@style/..."
的id
只有在第三個(gè)參數(shù)defStyleAttr為0吃环,或者主題中沒(méi)有找到這個(gè)defStyleAttr屬性的賦值時(shí),才可以啟用洋幻。
1.defStyleRes: 指向一個(gè)style引用.
2.defStyleRes的優(yōu)先級(jí)低于defStyleAttr.
這里郁轻,看得有些懵,先記住結(jié)論
在布局xml中直接定義 > 在布局xml中通過(guò)style定義 > 自定義View所在的Activity的Theme中指定style引用 > 構(gòu)造函數(shù)中defStyleRes指定的默認(rèn)值
以上摘抄Android自定義View構(gòu)造函數(shù)詳解
這里還找了另外兩篇:
Android View 四個(gè)構(gòu)造函數(shù)詳解
Android中自定義樣式與View的構(gòu)造函數(shù)中的第三個(gè)參數(shù)defStyle的意義
這里實(shí)在是沒(méi)有搞清楚優(yōu)先級(jí)鞋屈,先挖坑了 : )
2.3 View的類(lèi)似生命周期的方法 <p>
雖然View
也有好多類(lèi)似Activity
那種會(huì)在不同時(shí)期進(jìn)行調(diào)用的方法范咨,但實(shí)際上View
的生命周期就3個(gè)階段:
- 處理控件動(dòng)畫(huà)階段
- 處理測(cè)量的階段
- 處理繪制的階段
重寫(xiě)了幾個(gè)方法:
public class LifecycleView extends View {
private final String TAG = "LifecycleView";
public LifecycleView(Context context) {
this(context, null);
Log.e(TAG, "&&&第1個(gè)構(gòu)造方法");
}
public LifecycleView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.e(TAG, "&&&第2個(gè)構(gòu)造方法");
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
Log.e(TAG, "&&&解析布局文件onFinishInflate()");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e(TAG, "&&&測(cè)量onMeasure()");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.e(TAG, "&&&布局onLayout()");
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.e(TAG, "&&&控件大小發(fā)生改變onSizeChanged()");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e(TAG, "&&&繪制onDraw()");
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
Log.e(TAG, "&&&控件焦點(diǎn)改變onFocusChanged()");
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
Log.e(TAG, "&&&獲取或者失去焦點(diǎn)onWindowFocusChanged()");
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Log.e(TAG, "&&&開(kāi)始附加在屏幕上onAttachedToWindow()");
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.e(TAG, "&&&與屏幕失去連接onDetachedFromWindow()");
}
@Override
protected void onAnimationStart() {
super.onAnimationStart();
Log.e(TAG, "&&&動(dòng)畫(huà)開(kāi)始o(jì)nAnimationStart()");
}
@Override
protected void onAnimationEnd() {
super.onAnimationEnd();
Log.e(TAG, "&&&動(dòng)畫(huà)結(jié)束onAnimationEnd()");
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
Log.e(TAG, "&&&可見(jiàn)性發(fā)生改變onWindowVisibilityChanged()");
}
}
加載LifecycleView
所在的Activity
時(shí):
onFinishInflate()
這個(gè)方法進(jìn)行解析View
的xml
文件,如果使用構(gòu)造方法1
厂庇,就不會(huì)回調(diào)這個(gè)方法渠啊。測(cè)量方法和布局方法會(huì)被回調(diào)好幾次。而onDraw()
竟然也回調(diào)了兩次权旷,一直覺(jué)得會(huì)被調(diào)用一次
點(diǎn)擊回退鍵替蛉,結(jié)束掉Activity
時(shí):
關(guān)閉時(shí)倒是比較容易理解
3. 最后 <p>
關(guān)于構(gòu)造方法3
和4
具體在哪種情景下會(huì)被調(diào)用,在哪個(gè)具體時(shí)機(jī)被調(diào)用沒(méi)有搞明白拄氯,暫時(shí)決定先擱下躲查,下篇打算開(kāi)始學(xué)習(xí)事件體系。有知道的译柏,請(qǐng)告訴一下
繼承ViewGroup
的知識(shí)點(diǎn)镣煮,在上篇已經(jīng)有所了解。而繼承現(xiàn)有的View
和ViewGroup
鄙麦,和直接繼承View
和ViewGroup
大體思路是一致的典唇,甚至還簡(jiǎn)單一點(diǎn)
View
的事件分發(fā),涉及到責(zé)任鏈模式
本人很菜胯府,有錯(cuò)誤介衔,請(qǐng)指出
共勉 : )