invalidate和requestLayout
Invalidate:
To farce a view to draw,call invalidate().——摘自View類原始碼
從上面這句話看出,invalidate方法會執行draw過程,重繪View樹。
當View的appearance發生改變,比如狀態改變(enable,focus),背景改變,隱顯改變等,這些都屬於appearance範疇,都會引起invalidate操作。
所以當我們改變了View的appearance,需要更新介面顯示,就可以直接呼叫invalidate方法。
View(非容器類)呼叫invalidate方法只會重繪自身,ViewGroup呼叫則會重繪整個View樹。
RequestLayout:
To initiate a layout, call requestLayout(). This method is typically called by a view on itself when it believes that it can no longer fit within its current bounds.——摘自View原始碼
從上面這句話看出,當View的邊界,也可以理解為View的寬高,發生了變化,不再適合現在的區域,可以呼叫requestLayout方法重新對View佈局。
View執行requestLayout方法,會向上遞迴到頂級父View中,再執行這個頂級父View的requestLayout,所以其他View的onMeasure,onLayout也可能會被呼叫。
總結:
View繪製分三個步驟,順序是:onMeasure,onLayout,onDraw。經程式碼親測,log輸出顯示:呼叫invalidate方法只會執行onDraw方法;呼叫requestLayout方法只會執行onMeasure方法和onLayout方法,並不會執行onDraw方法。
所以當我們進行View更新時,若僅View的顯示內容發生改變且新顯示內容不影響View的大小、位置,則只需呼叫invalidate方法;若View寬高、位置發生改變且顯示內容不變,只需呼叫requestLayout方法;若兩者均發生改變,則需呼叫兩者,按照View的繪製流程,推薦先呼叫requestLayout方法再呼叫invalidate方法。
requestLayout示例:實現可移動元件:https://blog.csdn.net/qq_39658819/article/details/78994308
import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.FrameLayout; /** * 步驟一:自定義可移動元件 * @author NewBies * @date 2017/12/26 */ public class VertexView extends android.support.v7.widget.AppCompatTextView{ private int startX; private int startY; private int endX; private int endY; private FrameLayout.LayoutParams layoutParams; public VertexView(Context context) { super(context); } public VertexView(Context context, AttributeSet attrs) { super(context, attrs); } public VertexView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 步驟二:重寫onTouchEvent事件 * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event){ //步驟三:獲取手機觸控點的橫座標和縱座標 endX = (int)event.getX(); endY = (int)event.getY(); //步驟四:獲取佈局引數例項,注意:xx.LayoutParams這裡的xx應該是該元件的父佈局型別 //注意:這句話必須在該元件已經新增到父佈局中才會起作用,所以這句話我沒有寫在建構函式中,而是寫在這裡 layoutParams = (FrameLayout.LayoutParams) this.getLayoutParams(); switch (event.getAction()){ //監聽按下去的事件,這個事件在每次拖動時,必定會執行,也只執行一次 case MotionEvent.ACTION_DOWN: //將按下去的點記錄為起始點 startX = endX; startY = endY; break; //步驟五:監聽移動事件,該事件會在拖動時執行N次 case MotionEvent.ACTION_MOVE: //計算移動的距離 int offsetX = endX - startX; int offsetY = endY - startY; //呼叫layout方法來重新放置它的位置 layoutParams.setMargins(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); //重新整理 requestLayout(); break; //監聽擡起事件,該事件同按下去的時間一樣,只執行一次 case MotionEvent.ACTION_UP: break; default:break; } //這裡應該返回true,這裡涉及到了android的事件攔截機制,大致意思是,我的事件是在哪裡處理,就在那裡的事件返回TRUE return true; } }