分類:
- 繼承View重寫onDraw方法:這種情況下颖榜,需要自己支持wrap_content炬转,并且padding也需要自己處理笙瑟。
- 繼承ViewGroup派生特殊的layout:需要處理ViewGroup的測量和布局這兩個過程,并同時處理子元素的測量和布局過程喷橙。
- 繼承特定的View(比如TextView):這種方法一般不需要自己處理wrap_content和padding啥么。
- 繼承特定的ViewGroup(比如LinearLayout)
自定義View須知:
- 對于繼承View和ViewGroup的情況下,需要處理wrap_content和padding
- 盡量不要在View中使用handler,因為View內(nèi)部本身就提供了post系列贰逾。
- View中如果有線程或動畫悬荣,需要及時停止。當(dāng)View不可見或View的Activity推出時疙剑,要及時停止線程和動畫氯迂,否則有可能造成內(nèi)存泄漏践叠。我們一般是在onDetacheddFromWindow中去停止,因為當(dāng)View不可見或Activity退出時嚼蚀,該方法會被調(diào)用禁灼。
- View帶有滑動嵌套情形時,需要處理好滑動沖突轿曙。
示例一:繼承View重寫onDraw方法
這里我們來實現(xiàn)自定義圓弄捕,首先看到onMeasure方法:
解析傳入的寬高 --- 考慮wrap_contet情況 --- 若存在,則設(shè)置一個默認的值
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取到width导帝,height對應(yīng)的mode和size
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//考慮wrap_content的情況守谓,需要為其設(shè)置默認的值,這里設(shè)置為200dp
//對于寬高都設(shè)定為wrap_content時
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(200, 200);
}
//對于高設(shè)定為wrap_content的情況
else if (widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(200, heightSpecSize);
}
//對于寬設(shè)定為wrap_content的情況
else if (heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize, 200);
}
}
接下來是您单,實現(xiàn)onDraw方法:
要注意的一點是斋荞,這里要處理padding的情況
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//獲取到padding值
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
//獲取到真實的寬高
int height = getHeight() - paddingTop - paddingBottom;
int width = getWidth() - paddingLeft - paddingRight;
//以最小的值/2作為半徑
int radius = Math.min(width, height) / 2;
//進行畫圓
canvas.drawCircle(paddingLeft + width/2 , paddingTop + height/2, radius, mPaint);
}
我們還可以為其設(shè)置自定義屬性:
a. 在res/values中創(chuàng)建一個xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
其中,name表示為屬性名虐秦,format表示為屬性類型平酿,這里是color類型。
b. 在布局中引入:xmlns:app = "http://schemas.android.com/apk/res-auto"
并設(shè)置自定義控件:
<com.example.viewtest.CircleView
android:id="@+id/circleView"
android:background="#000"
app:circle_color = "@color/gray_color"
android:padding="10dp"
android:layout_margin="10dp"
android:layout_width="200dp"
android:layout_height="wrap_content" />
我們可以看到其中的app:cirle_color表示為自定義的屬性羡疗。
c. 在自定義View中獲取到屬性:
//獲取到自定義屬性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
//獲取到自定義屬性的值,其中第二個參數(shù)為默認參數(shù)别洪,表示若不存在叨恨,則設(shè)置為默認值
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
完整代碼如下:
public class CircleView extends View {
//定義顏色和畫筆
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
//獲取到自定義屬性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
//獲取到自定義屬性的值,其中第二個參數(shù)為默認參數(shù)挖垛,表示若不存在痒钝,則設(shè)置為默認值
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
init();
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
init();
}
//給畫筆設(shè)置顏色
private void init(){
mPaint.setColor(mColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取到width,height對應(yīng)的mode和size
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//考慮wrap_content的情況痢毒,需要為其設(shè)置默認的值送矩,這里設(shè)置為200dp
//對于寬高都設(shè)定為wrap_content時
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(200, 200);
}
//對于高設(shè)定為wrap_content的情況
else if (widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(200, heightSpecSize);
}
//對于寬設(shè)定為wrap_content的情況
else if (heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize, 200);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//獲取到padding值
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
//獲取到真實的寬高
int height = getHeight() - paddingTop - paddingBottom;
int width = getWidth() - paddingLeft - paddingRight;
//以最小的值/2作為半徑
int radius = Math.min(width, height) / 2;
//進行畫圓
canvas.drawCircle(paddingLeft + width/2 , paddingTop + height/2, radius, mPaint);
}
}
實現(xiàn)效果:
自定義View.png
實例二:自定義的ViewGroup
實現(xiàn)一個類似ViewPager的自定義ViewGroup:
a. 實現(xiàn)onMeasure方法:
遍歷孩子元素進行測量 --- 考慮wrap_content情況:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//定義兩個變量,作為測量的寬和高
int measureWidth = 0;
int measureHeight = 0;
//獲取到孩子個數(shù)
final int childCount = getChildCount();
//遍歷孩子元素哪替,對孩子元素分別進行測量
measureChildren(widthMeasureSpec, heightMeasureSpec);
//解析寬高栋荸,獲取到模式和大小
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
//考慮wrap_content情況
//孩子為0的情況下
if (childCount == 0){
setMeasuredDimension(0 ,0);
}
//寬高都是為wrap_content
else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
//獲取到對應(yīng)的寬度
measureWidth = childView.getMeasuredWidth() * childCount;
//設(shè)置孩子的高度作為當(dāng)前高度
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(measureWidth, measureHeight);
} else if(widthSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measureWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpecSize, measureHeight);
}
}
b. 實現(xiàn)布局方式:
遍歷所有的孩子元素,若孩子元素不為GONE模式凭舶,則進行放置其位置晌块。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
//遍歷所有的孩子元素
for (int i = 0; i < childCount; i ++){
final View childView = getChildAt(i);
//若當(dāng)前的孩子元素不為GONE,則對該孩子進行放置其位置
if (childView.getVisibility() != View.GONE){
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}