在上一篇文章android 自定義view 中我們總結了自定義View的幾大步驟如下:
1、自定義View的屬性
2、在View的構造方法中獲得我們自定義的屬性
3、重寫onMesure方法
4、重寫onLayout方法
5、重寫onDraw方法
因爲上篇文章中我們完成了LabelImageView中的一個LabelView子view,所以不用重寫onLayout方法,下面接着上篇文章我們來完成LabelImageView這個ViewGroup控件。既然LabelImageView是一個ViewGroup,那麼我們就必須自己決定其子控件的佈局,我們的子控件就是一個ImgeView和許多的LabelView。而onLayout就是給子控件佈局的地方。在上代碼前,我們先看看效果:
上面的效果就是在ImageView裏面可以添加我們自己的標籤,這個在某些APP裏面可以應用到,下面是LabelImageView的onLayout方法代碼:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(changed){
int childCount = getChildCount();
for(int i=0;i<childCount;i++){
View v = getChildAt(i);
if(v instanceof ImageView){ //佈局ImageView
v.layout(l+getPaddingLeft(),t+getPaddingTop(),r-getPaddingRight(),b-getPaddingBottom());
}else if(v instanceof LabelView){//佈局LabelView
LabelData labelData = ((LabelView)v).getLabelData();
float pos[] = labelData.getPosition();
int cl = (int) (getWidth()*pos[0]);
int ct = (int) (getHeight()*pos[1]);
int cr = cl+ v.getMeasuredWidth();
int cb = ct+ v.getMeasuredHeight();
v.layout(cl,ct,cr,cb);
}
}
}
}
其實就是獲取子控件,然後調用子控件的layout方法進行佈局,注意這個changed屬性,google官方api裏說當view有新的size或position時changed爲true,但當我用動畫改變一個view的大小或位置時,onlayout方法是不會調用的,用setLayoutParams重新設置View的寬高,changed確實會爲true。
在android開發時,有時我們需要得到某個View的寬高,最簡單的方法就是調用View的getHeight()和getWidth()了,但是使用時很多時候都發現他們返回的是0,得不到我們想要的值。下面打印下activity的啓動流程來分析下:
只有在activity調用了onAttachToWindow後,纔會開啓view的onMeasure方法,測量view的大小,只有在調用了view的onlayout方法後,view纔有真正的寬高,view的getWidth()和getHeight()才能得到真的的值。所以我們在activity的onResume方法裏及其前面的生命週期裏都無法得到view的寬高。如果我們必須要得到view的寬高呢,有辦法麼?答案當然是有,下面介紹三種常見的方法:
方法一:
//手動調用測量方法。 制定測量規則 參數表示size + mode
int width =View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
int height =View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
mLabelImageView.measure(width,height);
//調用measure方法之後就可以獲取寬高。
height=mLabelImageView.getMeasuredHeight();
width=mLabelImageView.getMeasuredWidth();
Log.e(TAG,"----------- measure width: "+ width + " height: " + height);
注意,measure()方法是實際測量的方法,而在繪製佈局過程中調用的onMeasure()只是制定測量規則. 在自定義佈局中我們一般重寫onMeasure()方法,measure()方法是final的,子類無法重寫。 measure函數有2個參數,int
widthMeasureSpec 和 int heightMeasureSpec表示具體的測量規則。
這兩個數值不是普通的數值, 它表示: size + mode ,例如:
int widthMeasureSpec= View.MeasureSpec.makeMeasureSpec(1000,View.MeasureSpec.EXACTLY);
int heightMeasureSpec= View.MeasureSpec.makeMeasureSpec(1000,View.MeasureSpec.AT_MOST);
模式分爲:
View.MeasureSpec.EXACTLY:表示父視圖希望子類的大小是specSize中制定的大小.
View.MeasureSpec.AT_MOST:父試圖希望子類的大小最高不超過specSize中制定的大小.
View.MeasureSpec.UNSPECIFIED:父試圖不對子類實施任何限制,子試圖可以得到自己想得到的任意大小.
值得一提的是View.MeasureSpec.UNSPECIFIED 其值爲0,所以前面可以簡寫成:view.measure(0,0),但是值得注意的是,這個只是測量值,一般情況下view的測量值寬高跟真實值寬高是一致的,但是也有不一樣的情況,比如ImageView設置不同的scaleType,測量值跟真實值有可以就不一樣。
方法二(設置View樹樁結構監聽器):
mLabelImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
mLabelImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int h = mLabelImageView.getHeight();
int w = mLabelImageView.getWidth();
Log.e(TAG,"----------- onGlobalLayout width: "+ w + " height: " + h);
}
});
方法三(增加組件繪製之前的監聽):
mLabelImageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mLabelImageView.getViewTreeObserver().removeOnPreDrawListener(this);
int h = mLabelImageView.getHeight();
int w = mLabelImageView.getWidth();
Log.e(TAG,"----------- onPreDraw width: "+ w + " height: " + h);
return false;
}
});
下面看看打印信息:
如上正確打印出了view的寬高,推薦使用後面兩種方式。
未完待續!