view的繪制流程
View的繪制流程從ViewRoot的performTraversals
方法開始,經(jīng)過measure
酪夷、layout
和draw
三大流程。
performMeasure
方法中會(huì)調(diào)用measure
方法,在measure
方法中又會(huì)調(diào)用onMeasure
方法皮钠,在onMeasure方法中會(huì)對(duì)所有的子元素進(jìn)行measure過程,這個(gè)時(shí)候measure流程就從父容器傳遞到子元素了赠法,這樣就完成了一次measure過程麦轰,layout和draw的過程類似。
measure過程決定了view的寬高砖织,在幾乎所有的情況下這個(gè)寬高都等同于view最終的寬高款侵。layout過程決定了view的四個(gè)頂點(diǎn)的坐標(biāo)和view實(shí)際的寬高,通過getWidth
和getHeight
方法可以得到最終的寬高侧纯。draw過程決定了view的顯示新锈。
自定義view的分類
1. 繼承View重寫onDraw
需要自己支持wrap_content和padding
2. 繼承ViewGroup重新定義一種新布局
3. 繼承特定的View(比如TextView)進(jìn)行功能擴(kuò)展
不需要自己支持wrap_content和padding
4. 繼承特定的ViewGroup(比如LinearLaout)
添加自定義屬性
1. 創(chuàng)建自定義屬性xml,如attrs.xml(以attrs_開頭命名),例如:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color" />
</declare-styleable>
</resources>
2. 解析自定義屬性的值并進(jìn)行處理
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);
a.recycle();
init();
}
private void init() {
mPaint.setColor(mColor);
}
有很多關(guān)于三個(gè)構(gòu)造函數(shù)
使用時(shí)機(jī):
- 在代碼中直接new一個(gè)Custom View實(shí)例的時(shí)候,會(huì)調(diào)用第一個(gè)構(gòu)造函數(shù).
- 在xml布局文件中調(diào)用Custom View的時(shí)候,會(huì)調(diào)用第二個(gè)構(gòu)造函數(shù).
- 在xml布局文件中調(diào)用Custom View,并且Custom View標(biāo)簽中還有自定義屬性時(shí),這里調(diào)用的還是第二個(gè)構(gòu)造函數(shù).也就是說,系統(tǒng)默認(rèn)只會(huì)調(diào)用Custom View的前兩個(gè)構(gòu)造函數(shù),至于第三個(gè)構(gòu)造函數(shù)的調(diào)用,通常是我們自己在構(gòu)造函數(shù)中主動(dòng)調(diào)用的(例如,在第二個(gè)構(gòu)造函數(shù)中調(diào)用第三個(gè)構(gòu)造函數(shù)).
3. 在布局文件中使用
首先在根部局添加
xmlns:app="http://schemas.android.com/apk/res-auto"
然后就可以正常使用
app:circle_color="@color/light_green"
注意事項(xiàng)
1. 直接繼承View或者ViewGroup的控件眶熬,需要讓view支持wrap_content壕鹉,如果有必要,還得讓你的View支持padding聋涨。繼承特定的View例如TextView則不需要考慮晾浴。解決方法如下:
/**
* 解決wrap_content不支持的問題
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST
&& heightSpecMode == MeasureSpec.AT_MOST) {
// 指定wrap_content模式(MeasureSpec.AT_MOST)的默認(rèn)寬高,比如200px
setMeasuredDimension(200, 200);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 200);
}
}
/**
* 針對(duì)padding的問題牍白,繪制的時(shí)候需要考慮padding的計(jì)算脊凰,在xml中設(shè)置是無用的
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingLeft();
final int paddingTop = getPaddingLeft();
final int paddingBottom = getPaddingLeft();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(width, height) / 2;
// 繪制一個(gè)圓
canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2,
radius, mPaint);
}
2. 除非明確使用handle發(fā)消息,盡量不要在View中使用Handler茂腥,因?yàn)関iew內(nèi)部本身已經(jīng)提供了post
系列的方法狸涌,完全可以替代Handler的作用
3. view中如果有線程,廣播或者動(dòng)畫最岗,需要在onDetachedFromWindow
方法中及時(shí)停止帕胆。
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
// 線程移除
if (mRunnable != null) {
removeCallbacks(mRunnable);
}
// 銷毀廣播
mContext.unregisterReceiver(myBroadcastReceiver);
}
4. 處理好view的滑動(dòng)沖突情況
以下是兩種可以水平切換的父容器嵌套ListView的滑動(dòng)沖突偽代碼,我們根據(jù)不同滑動(dòng)策略修改ACTION_MOVE中的判斷條件即可解決問題般渡。
- 外部攔截法
需要重寫父容器的onInterceptTouchEvent方法懒豹,比如我們想攔截第三方庫控件的事件,可以在外層加一個(gè)父布局重寫onInterceptTouchEvent
public class MyLinearlayout extends LinearLayout {
private int mLastX = 0;
private int mLastY = 0;
public MyLinearlayout(Context context) {
super(context);
}
public MyLinearlayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLinearlayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
boolean intercepted = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
// 如果水平方向位移大則攔截
if (Math.abs(deltaX) - Math.abs(deltaY) > 3) {
intercepted = true;
}else{
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return intercepted;
}
}
onInterceptTouchEvent是在ViewGroup里面定義的驯用。Android中的layout布局類一般都是繼承此類的脸秽。onInterceptTouchEvent是用于攔截手勢事件的,每個(gè)手勢事件都會(huì)先調(diào)用onInterceptTouchEvent蝴乔。
onInterceptTouchEvent()用于處理事件并改變事件的傳遞方向记餐。返回值為false時(shí)事件會(huì)傳遞給子控件的onInterceptTouchEvent();返回值為true時(shí)事件會(huì)傳遞給當(dāng)前控件的onTouchEvent()薇正,而不在傳遞給子控件片酝,這就是所謂的Intercept(截?cái)?囚衔。
- 內(nèi)部攔截法
1. 父容器不攔截任何事件,所有的事件傳遞給子元素
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
// 父元素必須不攔截ACTION_DOWN
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
子元素需要?jiǎng)t直接消耗雕沿,否則交由父容器處理
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
// 避免滑動(dòng)沖突练湿,本范圍內(nèi)告訴父控件不攔截
parent.requestDisallowInterceptTouchEvent(true);
break;
}
// 如果水平方向位移大則交給父容器(針對(duì)滑動(dòng)策略修改只需要此處條件)
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
if (Math.abs(deltaX) > Math.abs(deltaY)) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
附:
View派生出的直接子類有:AnalogClock,ImageView,KeyboardView, ProgressBar,SurfaceView, TextView,ViewGroup,ViewStub
View派生出的間接子類有:AbsListView,AbsSeekBar, AbsSpinner, AbsoluteLayout, AdapterView,AdapterViewAnimator, AdapterViewFlipper, AppWidgetHostView, AutoCompleteTextView,Button,CalendarView, CheckBox, CheckedTextView, Chronometer, CompoundButton,
ViewGroup派生出的直接子類有:AbsoluteLayout,AdapterView,FragmentBreadCrumbs,FrameLayout, LinearLayout,RelativeLayout,SlidingDrawer
ViewGroup派生出的間接子類有:AbsListView,AbsSpinner, AdapterViewAnimator, AdapterViewFlipper, AppWidgetHostView, CalendarView, DatePicker, DialerFilter, ExpandableListView, Gallery, GestureOverlayView,GridView,HorizontalScrollView, ImageSwitcher,ListView
參考:
《Android開發(fā)藝術(shù)探索》——任玉剛