Android 控制元件架構與自定義控制元件詳解
架構:
· PhoneWindow 將一個 DecorView 設定為整個應用視窗的根 View,這裡面所有 View 的監聽事件,都透過 WindowManagerService 來接收。DecorView 分為 TitleView 和 ContentView,ContentView 是一個 ID 為 content 的 FrameLayout
· 在 onCreate() 方法中呼叫 setContentView() 方法後,ActivityManagerService 會回撥onResume() 方法,此時系統才會把整個 DecorView 新增到 PhoneWindow 中,並讓其顯示出來,從而完成最終的介面繪製。
View 的測量:
測量 View 的類:MeasureSpec 類,它是一個32位的 int 值,高兩位為測量模式,低30位為測量大小,使用位運算提高並最佳化效率。
重寫 onMeasure() 後,最終要做的是把測量後的寬高值作為引數設定給 setMeasureDimension() 方法。
[程式碼]java程式碼:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ setMeasureDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); }
//可作為模板程式碼! private int measureWidth(int measureSpec){ int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if(specMode == MeasureSpec.EXACTLY){//精確值模式,指定具體數值 result = specSize; }else{ result = 200;//先設定一個預設大小 //最大值模式,layout_width 或 layout_height 為 wrap_content 時,控制元件大小隨控制元件的內容變化而變化,此時控制元件尺寸只要不超過父控制元件允許的最大尺寸即可。 if(specMode == MeasureSpec.AT_MOST){ result = Math.min(result, specSize);//取出我們指定的大小和 specSize 中最小的一個來作為最後的測量值 } //MeasureSpec.UNSPECIFIED 不指定其大小,View 想多大就多大 } return result; } |
即,如果不重寫 onMeasure() 方法,系統則會不知道該預設多大尺寸,就會預設填充整個父佈局,所以,重寫 onMeasure() 方法的目的,就是為了能夠給 View 一個 wrap_content 屬性下的預設大小。
View 的繪製
onDraw() 中的引數,就是 Canvas 物件,使用該物件進行繪圖,而在其他地方,則需要 new 出該物件:
[程式碼]java程式碼:
1 |
Canvas canvas = new Canvas(bitmap); |
傳進去的 bitmap 是與這個 bitmap 建立的 Canvas 畫布緊密聯絡的,這個過程稱為裝載畫布。該 bitmap 用來儲存所有繪製在 Canvas 上的畫素資訊。所有的 Canvas.drawXXX 方法都發生在這個 bitmap 上。
[程式碼]java程式碼:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 |
@Override protected void onDraw(Canvas canvas){ //... //在 onDraw 方法中繪製兩個 bitmap canvas.drawBitmap(bitmap1, 0, 0, null); canvas.drawBitmap(bitmap2, 0, 0, null); //... } private void otherMethod(){ //將 bitmap2 裝載到另一個 Canvas 物件中 Canvas mCanvas = new Canvas(bitmap2); //其他地方使用 Canvas 物件的繪圖方法在裝載 bitmap2 的 Canvas 物件上進行繪圖 mCanvas.drawXXX } |
透過 mCanvas 將繪製效果作用在了 bitmap2 上,再重新整理 View 的時候,就會發現透過 onDraw()方法畫出來的 bitmap2 已經改變,因為 bitmap2 承載了在 mCanvas 上所進行的繪圖操作。我們沒有將圖形直接繪製在 onDraw() 方法制定的那塊畫布上,而是透過改變 bitmap,讓 View 重繪,從而顯示改變之後的 bitmap。
ViewGroup 的測量
當 ViewGroup 的大小為 wrap_content 時,ViewGroup 需要對子 View 進行遍歷,以便獲得所有子 View 大小從而決定自己的大小,即呼叫子 View 的 Measure 方法來獲得每一個子 View 的測量結果。
子 View 測量完畢後,ViewGroup 執行 Layout 過程時,同樣是遍歷呼叫子 View 的 Layout 方法,並指定其具體顯示的位置,從而來決定其佈局位置。
自定義 View
[程式碼]java程式碼:
1 2 3 4 5 6 |
@Override protected void onDraw(Canvas canvas){ //在回撥父類方法前,對 TextView 來說是在繪製文字內容之前,實現邏輯 super.onDraw(canvas); //之後,繪製文字之後 } |
在 attrs.xml 中透過使用
[程式碼]java程式碼:
1 2 3 4 |
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar); mLeftColor = ta.getColor(R.styleable.TopBar_leftTextColor, 0); //完成資源回收,避免重新建立的時候的錯誤 ta.recycle(); |
自定義 ViewGroup
重寫 onMeasure() 來對子 View 進行測量,重寫 onLayout() 確定子 View 位置,重寫onTouchEvent() 增加響應事件。
例項需求:自定義 ViewGroup 實現類似 ScrollView 上下滑動,同時增加粘性效果。即,當一個子 View 向上滑動大於一定距離後,鬆開將自動上滑,顯示下一個子 View,否則回到原始位置。
步驟一:先實現類似 ScrollView 功能:
[程式碼]java程式碼:
01 02 03 04 05 06 07 08 09 10 |
@override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); //遍歷通知子 View 對其自身進行測量 for(int i = 0;i View childView = getChildAt(i); measureChild(childView, widthMeasureSpec, heightMeasureSpec); } } |
步驟二:再對子 View 進行放置位置設定,讓每個子 View 都顯示完整的一屏。所以,本例中ViewGroup 的高度就是子 View 的個數乘以螢幕高度,然後遍歷設定每個子 View 放置的位置
[程式碼]java程式碼:
1 2 3 4 5 6 7 8 9 |
@Override protected void onLayout(boolean changed, int l, int t, int r, int b){ int childCount = getChildCount(); //設定 ViewGroup 高度 MarginLayoutParams mlp = (MarginLayoutParams)getLayoutParams(); mlp.height = mScreenHeight.childCount; setLayoutParams(mlp); //修改子 View 的 top 和 bottom 屬性,使它們依次排列 for(int i=0; i |
步驟三:
[程式碼]java程式碼:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
@Override public boolean onTouchEvent(MotionEvent event){ int y = (int)event.getY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN: mLastY = y; mStart = getScrollY();//記錄按下位置 break; case MotionEvent.ACTION_MOVE: if(!mScroller.isFinished()){ mScroller.abortAnimation(); } int dy = mLastY - y; if(getScrollY() dy = 0; } if(getScrollY() >getHeight - mScreenHeight){ dy = 0; } scrollBy(0, dy);//隨手指滾動 dy mLastY = y; break; case MotionEvent.ACTION_UP: mEnd = getScrollY(); int dScrollY = mEnd - mStart; if(dScrollY > 0){//上滑 if(dScrollY mScroller.startScroll(0,getScrollY(), 0, -dScrollY); }else{//大於,則滾動完剩餘的距離 mScroller.startScroll(0,getScrollY(), 0, mScreenHeight-dScrollY); } }else{//同理 if(-dScrollY mScroller.startScroll(0, getScrollY(), 0, -dScrollY); }else{ mScroller.startScroll(0, getScrollY(), 0, -mScreenHeight - dScrollY); } } break; } postInvalidata(); return true; }
@Override public void computeScroll(){ super.computeScroll(); if(mScroller.computeScrollOffset()){ scrollTo(0, mScroller.getCurrY()); postInvalidate(); } } |
事件攔截機制
點選 View 的 log:
[程式碼]java程式碼:
1 2 3 4 5 6 7 8 |
ViewGroupA dispatchTouchEvent ViewGroupA onInterceptTouchEvent ViewGroupB dispatchTouchEvent ViewGroupB onInterceptTouchEvent View dispatchTouchEvent View onTouchEvent //last event, will back to parent ViewGroupB onTouchEvent ViewGroupA onTouchEvent |
所以事件傳遞順序是:先執行 dispatchTouchEvent() 然後是 onInterceptTouchEvent()。返回值:True,攔截,不繼續;False,不攔截,繼續流程。初始返回是 false。
事件處理順序是:onTouchEvent()。返回值:True,處理了,不稽核;False,給上級處理。初始返回是 false。
即:
· 分發、攔截:如果某個 ViewGroup 直接使用 dispatchTouchEvent() 返回了 true ,則分發攔截結束,不再向其子 View 傳遞,則,直接執行該 ViewGroup 的 onTouchEvent(),然後繼續向上處理對應 ViewGroup 的 onTouchEvent()。
· 處理:如果某個 View 直接在 onTouchEvent() 中返回了 true。則上級不再執行onTouchEvent()。所有的處理在此結束。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1020/viewspace-2814781/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- VirtualView Android 實現詳解(三)—— 新增一個自定義控制元件ViewAndroid控制元件
- 【Android】自定義樹形控制元件Android控制元件
- Android自定義多宮格解鎖控制元件Android控制元件
- Android自定義View--翻書控制元件(一)AndroidView控制元件
- Android自定義控制元件(神級)+MediaRecoder錄音Android控制元件
- Android自定義控制元件 帶文字提示的SeekBarAndroid控制元件
- 自定義控制元件ViewPager控制元件Viewpager
- 自定義Switch控制元件控制元件
- iOS自定義控制元件:簡易下拉控制元件iOS控制元件
- iOS自定義控制元件 SlideriOS控制元件IDE
- iOS自定義控制元件 AlertViewiOS控制元件View
- iOS自定義控制元件 SegmentiOS控制元件
- winform 自定義容器控制元件ORM控制元件
- WPF Blend 自定義控制元件控制元件
- Flutter 之 自定義控制元件Flutter控制元件
- QT常用控制元件(三)——自定義控制元件封裝QT控制元件封裝
- 4. 自定義控制元件(4) --- 自定義屬性控制元件
- 自定義控制元件 --- 電池icon控制元件
- Flutter 自定義縮放控制元件Flutter控制元件
- Qt實現自定義控制元件QT控制元件
- AngularJS自定義表單控制元件AngularJS控制元件
- 從Android到ReactNative開發(三、自定義原生控制元件支援)AndroidReact控制元件
- Android開發之自定義隨機驗證碼控制元件Android隨機控制元件
- Android自定義控制元件(高手級)--JOJO同款能力分析圖Android控制元件
- Android自定義控制元件(高手級)–JOJO同款能力分析圖Android控制元件
- 自定義控制元件總結和思考控制元件
- WPF自定義FixedColumnGrid佈局控制元件控制元件
- 【自定義使用者控制元件】CNMButton控制元件
- UWP 自定義密碼框控制元件密碼控制元件
- iOS 自定義拖拽式控制元件:QiDragViewiOS控制元件View
- C#自定義控制元件—指示燈C#控制元件
- Android自定義控制元件之區域性圖片放大鏡–BiggerViewAndroid控制元件View
- Android自定義控制元件之區域性圖片放大鏡--BiggerViewAndroid控制元件View
- 從 Android 到 React Native 開發(三、自定義原生控制元件支援)AndroidReact Native控制元件
- Flutter自定義控制元件第一式,炫酷“蛛網”控制元件Flutter控制元件
- wpf自定義控制元件新增引用資源控制元件
- 簡單的自定義表單控制元件控制元件
- Qt自定義開關按鈕控制元件QT控制元件