如今需求的多種多樣,app要求各種絢麗的效果,使得andriod開發的我們無法避免一個問題,最終逼得我們不得不去自定義一些view來實現需求。
閱讀了下 Android 我眼中的自定義View(2) 這篇文章,給自己做一個筆記。總結了下:
通常自定義view有一下幾種:
1、直接繼承View,或者ViewGroup去重寫一個自定義view。
2、繼承某個已有的控件,比如TextView去重寫一個自定義view。
3、繼承某個已有的ViewGroup,比如LinearLayout去重寫一個自定義view。
對于第一種方式,需要注意的是,在內容區域未超過屏幕尺寸的情況下,我們一般需要在onMeasure()中重新測量ViewGroup尺寸來對wrap_content屬性進行支持,如果內容區域的大小超過屏幕尺寸,我們就必須在onMeasure()中重新測量ViewGroup的尺寸,否則ViewGroup的最大尺寸為屏幕尺寸,導致ViewGroup中的內容顯示不全。因為,此時wrap_content和match_parent的效果是一樣的,同時各種padding也是無效的,因此是需要你自己onMeasure的。
直接繼承View的話,通常模板應該是這樣的:
public class CircleView extends View {
.......省略若干代碼........
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(500, 500);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(500, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 500);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
//需要在這里設置padding哦
int radius = Math.min((width - getPaddingLeft() - getPaddingRight()) / 2,
(height - getPaddingTop() - getPaddingBottom()) / 2);
canvas.drawColor(Color.GRAY);
canvas.drawCircle(width / 2, height / 2, radius, mPaint);
}
}
直接繼承viewGroup的話,模板大概是這樣的:
public class TestViewGroup extends ViewGroup {
//使ViewGroup支持margin屬性
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
width += childView.getMeasuredWidth() + params.rightMargin + params.leftMargin;
if (i == 0) {
height += childView.getMeasuredHeight() + params.topMargin + params.bottomMargin;
}
}
if (width > getScreenWidth()) {
setMeasuredDimension(width, height);
} else {
setMeasuredDimension((widthSpecMode == MeasureSpec.AT_MOST) ? width : widthSpecSize,
(heightSpecMode == MeasureSpec.AT_MOST) ? height : heightSpecSize);
}
}
//需要layout了,因為他要指定他的子view的拜訪位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = 0;
View lastChildView = null;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
left += params.leftMargin;
if (lastChildView != null) {
left += lastChildView.getMeasuredWidth() + ((MarginLayoutParams) lastChildView.getLayoutParams()).rightMargin;
}
int right = left + childView.getMeasuredWidth();
int top = params.topMargin;
int bottom = childView.getMeasuredHeight() + top;
childView.layout(left, top, right, bottom);
lastChildView = childView;
}
}
然后對于第二大類,通常繼承一個特定的view組件,是不需要寫onMeasure
和onLayout
的,除非有必要,因為google的android工程師做的太牛逼了。
參考文章: