日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

自定义控件从入门到轻生之---解锁新姿势

發布時間:2024/3/12 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自定义控件从入门到轻生之---解锁新姿势 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

所有blog局限于博主水平有限,很多不足之處大家可以指出共同探討進步。

尊重原創轉載請注明:From 倪大葉http://blog.csdn.net/renyi0109 侵權必究!雖然我不知道具體怎么究,但是看大家都這么寫我也就這么寫吧

上次說到View的事件分發從根本原則上清楚了幾條事件分發的法……..好好好,上次是說到我侄兒拿了一道初一得數學題讓我解惑一下,先不說這道題的本身,本著程序員看問題從本質出發的角度,我覺得就算我給他把答案寫出來他下次再碰到類似的題或者同類詭異的題肯定還是做不出來,不能舉一反三講來何用?所以我本著從源頭上解決的思想告訴他: 以后碰到這種題,寫個解字可以得一分…

上次講的事件分發是在現有View層面上的玩意兒,今天我們倒敘回去看看一個View的從無到有是怎么生成的也就是常說的View繪制流程。在這之前先大概講講Android下的”View 框架”

Android四大組件各司其職,前有Activity賣弄風騷,后有service暗度陳倉,再有BroadCastReceiver左右逢源,最后有ContentProvider不知道在干嘛,而Activity正是負責前臺接待客戶的,所以我們就從Activity開始講講View繪制流程的框架(這部分并不是這篇blog的重點,所以就不貼源碼了)雖然Activity是負責和用戶交互和顯示視圖的但其實Activity并不直接控制它們,而是由他內部一個叫window的哥們來負責的,每個Activity在attach的時候會創建一個唯一的window對象并把自己作為接口回調注冊給window,以用于window將一些Activity關心的玩意兒丟回來,比如上一篇說到的觸摸事件就是由window丟給Activity的。我們要想在Activity顯示什么布局,直接在onCreate中調用setContentView就可以了,這個方法其實就是調用了window的setContentView,window內部會把我們設置的布局文件裝載到一個叫DecorView的東西上,這個DecorView就是我們能看到的手機畫面的根視圖,它是一個FrameLayout里面只有一個Linearlayout子View,Linearlayout又分為titleView(就是title布局,actionBar和普通title就是顯示在這里)和ContentView(這就是顯示我們設置布局的地方了),這時候敏感的哥們可能會問那狀態欄呢?狀態欄和我們應用可沒什么關系,它其實是一個系統級的應用,最高優先級顯示在手機上。到這兒雖然已經把我們想顯示的視圖設置給了DecorView,但是這時候decorView和window其實還沒有關聯起來,要將DecorView添加到window上才能真正的顯示出來,這個添加過程就要靠一個叫ViewRootImpl的類,說到這個類就吊了,基本所有View處理都是它來完成的,比如我們接下來要講的measure layout draw的發起源頭都是在這個類, ViewRootImpl可以看作是DecorView和window連接的紐帶,DecorView添加到window的大概流程 windowManager(window的管理器,負責操作window的)->windowManagerGobal(addView) ->ViewRootImpl 最后就是ViewRootImpl的setView來完成添加,而這個方法是一個IPC過程,添加的最終實現是一個叫Session的遠程實現類。都講到這里了 就順便再提一下VIewRootImpl的performTraversals方法,接下來要講到的View的測量啊布局什么的源頭都是由該方法發起的

if (lp.horizontalWeight > 0.0f) {width += (int) ((mWidth - width) lp.horizontalWeight);//生成子View寬的測量規格 這個MeasureSpec是整個measure的核心數據類,子View的onMeasure方法中的測量規格都是由父元素給的,//至于有什么用后面講測量的時候再細說,而這個規格就是在這里生成的,當然這只是生成的起點,每層View拿到這個規格都有可能會根據//自己的需要做一些修改再傳給子ViewchildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY);measureAgain = true;}if (lp.verticalWeight > 0.0f) {height += (int) ((mHeight - height) * lp.verticalWeight);//同上childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);measureAgain = true;}if (measureAgain) {if (DEBUG_LAYOUT) Log.v(TAG,"And hey let's measure once more: width=" + width+ " height=" + height);//發起測量,正式開始走View的測量邏輯performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}//發起布局 performLayout(lp, desiredWindowWidth, desiredWindowHeight);//發起繪制performDraw();

上面說的這些只是最最最最范闊的介紹,里面有很多細節值得大家去研究學習,當然要真的弄懂上面這一套View原理框架并不是一件容易的事,好了扯了這么多該吃正餐了
首先先跳出程序的思維來看下生活中如果我們要畫出一張圖需要哪什么步驟
1.首先不管要畫什么東西得先有張紙
2.有了紙你得知道你要畫得圖像大概有多大
3.你要把這個圖像畫在什么位置
4.你用什么畫,怎么畫,畫什么

完成上面幾步基本就可以畫出一個View了,那我們把這幾步映射到Android的View繪制里來, 紙就是我們的canvas,這個東西不用關心系統已經給我們準備好了而且也必須畫在系統給我們準備的這張紙上才能顯示出來,畫多大就是View的測量方法measure,畫在哪就是layout方法,畫什么就是draw方法

onMeasure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {....if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||....if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag back//這里就是調用onMeasure的地方onMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;} else {long value = mMeasureCache.valueAt(cacheIndex);// Casting a long to int drops the high 32 bits, no mask neededsetMeasuredDimensionRaw((int) (value >> 32), (int) value);mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}....mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension}

可能有人還不是很清楚measure和onMeasure的區別,其實看看measure的源碼就能看出是一個測量的控制中心,比如一些測量執行的判斷,狀態的記錄等等,當然實際的測量onMeasure也是由measure來負責控制的,還有各位老板注意看這是一個final方法,final!!所以不要再問為什么測量不能復寫measure方法,同理 layout draw都是Android系統已經寫好的,不用你關心也輪不到你關心,提供給我們能做的就是 onMeasure這幾個方法,說白了 measure是測量的邏輯控制,而onMeasure是具體的測量實現
在江湖上流傳著一個說法,一個View的大小實際是由父View和自身共同決定的,View本身對自己大小的期望就是來自我們在代碼或者xml文件中給它設置的寬高,而父View對兒子得期望就是measuren的兩個int參數,這兩個參數由父親傳給兒子,兒子又傳給孫子,孫子再傳給曾孫….一直傳到買不起房找不了老婆沒辦法生兒子那一代.. 那么肯定有沒注意看的事兒逼會問那頂級父View的參數又是誰傳給他的呢?上面已經講了有個叫ViewRootImpl的上帝用performTraversals方法去生成這兩個int規則,至于大小就是屏幕大小。然后由它傳給頂級父View。 這兩個int 高2位為mode,低30位為size,至于是什么意思就要先講講今天的配角

MeasureSpec

//提取期望int型低30位為大小 int specSize = MeasureSpec.getSize(measureSpec) //提取期望int型中高兩位為mode int specMode = MeasureSpec.getMode(measureSpec)

這個mode有三種類型: MeasureSpec.EXACTLY , MeasureSpec.AT_MOST , MeasureSpec.UNSPECIFIED(這個我到現在都不知道有什么鳥用,我們用不到就不講了,想來應該是系統級控件會用到吧)

MeasureSpec.EXACTLY:
父View已經測量出子View的大小,你就直接用我給你的size就行,你自己設置的大小別用了

MeasureSpec.AT_MOST:
父View不強制要求子View多大,子View可以根據自己的期望來設置大小,但是前提是不能超過我給的Size大小
下面是View源碼的onMeasure邏輯

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//注意這個方法才是設置有效寬高的方法,前面說的一大堆的都是規則是理論,極端點你可以完全拋棄//系統的這一套規則,直接用這個方法隨便設置什么都可以,當然一般人不會這么做的setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}//根據父期望和自己的size來確定最終的size大小public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}

View只是很簡單的實現了最基本的onMeasure邏輯,基本所有View在繼承View的同時都會復寫自己邏輯的onMeasure方法,每個View基本都不一樣,在這我就不挑某一個特定View來看源碼了,但是為了讓大家更深刻的理解”父子共同決定View的大小”,我們自己簡單寫個onMeasure邏輯

//childSize: 子View自己希望的大小(來自xml文件中設置或者代碼)//parentMeasureSpec: 父View對子View的期望public int getMeasureHeightSize(int childSize, int parentMeasureSpec) {int result ;//提取父View期望modeint specMode = MeasureSpec.getMode(measureSpec);//提取父View期望大小int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED://父View對子View不做限制//子View想要多大就多大result = size;break;case MeasureSpec.AT_MOST: //父View不對子View做具體要求,但是不能大過父View給的最大值//如果子View想要的大小沒有超過父View的限定,那么直接用子View想要的大小,如果超過了,只能取到specSize這個限定值result = size <= specSize? size : specSize;break;case MeasureSpec.EXACTLY://父View已經測量出子View的精確大小//直接使用父View給的大小就好result = specSize;break;}return result;}

這只是很簡單的一個小例子,實際中要考慮的很多,比如獲取自身期望值得時候還需要加上padding,或者一些特有的屬性對值的影響,這里我們已經看到子View是怎么根據父View的期望和自身的設置值來最終確定大小的。我們來思考一個問題,這個父View的期望是依據什么得來的呢?有人會說不是ViewRootImpl傳下來的,然后再一層一層傳下去的嗎?是這樣沒錯,可是ViewRootImpl只是簡單將滿屏寬高作為規則向下傳遞,我上面說過這個規則每層View拿到他都可能根據自己的實際情況做修改再傳遞給子View,我們在View里沒看到類似的代碼是因為只有在ViewGroup系列類下才有合成childMesureSpec的邏輯。在我們ViewGroup中有幾個方法是用來計算合成對子View規則的相關方法,我們依次看看

//遍歷所有child,調用measureChild去合成對子View的測量規則 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {final int size = mChildrenCount;final View[] children = mChildren;for (int i = 0; i < size; ++i) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {measureChild(child, widthMeasureSpec, heightMeasureSpec);}}} //測量子View protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {//獲取child的 layoutParams ,里面包括了child對自己大小的一個設置信息final LayoutParams lp = child.getLayoutParams();//調用getChildMeasureSpec,根據父View的期望和子View希望值來合成對子View的規則final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);//將合成的規則傳遞給子View去測量child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}主要合成對子View期望規則的方法就是getChildMeasureSpec,我們進去詳細看看這個方法public static int getChildMeasureSpec(int spec, int padding, int childDimension) {//提取父View的mode和sizeint specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);//父View的期望size首選得先減去該View的padding,才是實際給內容的大小,這里做個小于0限制int size = Math.max(0, specSize - padding);//定義對子View規則的 size和modeint resultSize = 0;int resultMode = 0;//根據父View的mode分別進行合成switch (specMode) {// Parent has imposed an exact size on uscase MeasureSpec.EXACTLY: 父View有準確的大小要求if (childDimension >= 0) { //如果子View大小這只為一個精確值(childDimension大于0為精確數字,match_parent 和wrap_content都小于0)//直接用子View設置的值為規則的大小,mode肯定就為EXACTLY了resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {//如果為填滿父View//直接用父View的size,因為父View的大小是確定的所以子mode也是精確的 EXACTLYresultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果為包裹內容// 也用父View的size,因為父View的值是精確的,所以子View的大小不能超過這個限定resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST: //父View的mode為限定一個大小//這里條件和上面一樣,就直接看里面的生成了if (childDimension >= 0) {//直接用子View設置的值,mode也就為 EXACTLY了resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {//用父View給的大小,因為父View大小被限定了,所以子View也跟著被限定resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {//用父View給的大小,這時候父View是最大值限定,子View同樣不能超過父View,也同樣做最大值限定resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// 這個模式就不多分析了,和上面是一樣的case MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should// beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size.... find out how// big it should beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}

看到這里大家應該都清楚了onMeasure的測量邏輯以及測量規格的生成了吧,測量的東西差不多就是這些了,接下來看看onLayout

onLayout

我們已經測量出View的大小,接下來就該確定它應該所呆得位置了,layout和measure就有點不同了,比如measure是在父View和自身期望下共同決定大小的,但是最終還是View自己去調用setMeasure方法設置寬高,也就是說最終決定權還是在View本身,上面說過極端情況下你甚至可以完全不管父View的期望直接設置寬高都是可行的,而layout就沒有這個特權了,該放在那他只能提意見(xml文件設置或者代碼),最終決定權在父View,我們看下View中的onLayout:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

是一個空實現,onLayout和onMeasure也有點不同,我們說過measure是測量控制,onMeasure是實現具體設置寬高,這里onLayout其實只是用來控制子View該擺放的位置,具體擺放設置的代碼卻是layout作為實現,既然能作為父View那么絕逼是個ViewGroup吧,再去看下它里面的onLayout:

protected abstract void onLayout(boolean changed,int l, int t, int r, int b);

這特么不止是空實現而是一個抽象方法了,也就是說任何ViewGroup系列的子類都必須實現onLayout方法來擺放子View的位置并且這個layout工作系統不會再像onMeasure這樣幫你做好基本的邏輯了,你要自定義一個新控件就必須自己去實現onLayout來擺放子View。 所以像常用的 RelativeLayout,Linearlayout什么的都是必然有自己的onLayout邏輯,這也是他們的核心功能邏輯, 就不單獨挑一個出來看了,我們來自己簡單的自定義一個玩一下:

/*** 就簡單的寫個每個View占一排,類似豎直的LinearLayout*/ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { /* * 如果有子元素才需要的layout位置*/ if (getChildCount() > 0) { // 用來記錄已被占有的高度 int tempHeight = 0; // 遍歷子View并對其進行定位布局 for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); /*** 讓child進行布局,這layout的四個參數 left right top bottom 一會下面畫張圖介紹* 布局的參考坐標原點為父View的左上起點,因為是垂直布局,每次起點Y坐標會下移已經布局* 的高度*/ child.layout(0, mutilHeight, child.getMeasuredWidth(), child.getMeasuredHeight() + mutilHeight); // 改變高度倍增值 tempHeight += child.getMeasuredHeight(); } } }

好了一個自制Linearlayout就完了,你是不是覺得原來linearlayout太簡單了?你去看看Linearlayout的源碼會發現這特么不是坑爹嗎,其實吧這例子就是一個引子,實際的控件還需要考慮子View的margin,或者一些特定的屬性(比如 RelativeLayout的 在某元素下面,在父元素底部等等),還有父View本身的內邊距啥的。很多很多需要考慮的,所以可以看到系統每個容器View都有大量非常嚴謹的layout代碼來確保正確的放置View,所以要寫一個完全自定義的容器view并不是一件容易的事,這里就不這么詳細的去做了,因為那些都是一些邏輯處理了,我們只介紹基本的原理,大家自己可以下去想個控件出來練練手

onDraw

draw的代碼會有點多就不貼代碼了,主要在我們自己繪制以前系統會先做一些繪制,比如背景啊,滾動條啊,edge啥的,對我們來說知道就行。 而onDraw方法也是一個空實現,具體要畫的內容完全交給子View自己去實現, onDraw方法會有一個canvas參數,這個玩意兒就是我們上面說的畫布了,我們所有畫的東西都必須畫到這個畫布上才能顯示出來,而且沒錯這個canvas也是ViewRootImpl創建并且傳下來的。好了 我覺得draw已經講完了 哈哈。。。 其實draw可以說是繪制流程最簡單的也可以說是最復雜的,它完全放權給View自己愛咋畫咋畫,就像有兩個人給了他們兩張紙,一個就畫了個圓所以在這看來draw和簡單,另一個畫了一幅美女出浴圖在這看來draw就很難很復雜很有意思,所以這個方法沒什么好說的,能說的就是畫的過程和用的工具。但是那就是一個龐大的話題了,不在這系列blog討論范圍之內了 ,感興趣的自己去研究吧

好了就講到這,下一篇會基于這兩篇blog的內容做一個實用的小例子,當我正想關閉編輯器的時候我那侄兒又跑過來了。。我看著他手上并沒有拿試卷課本啥的心里暗松一口氣。。。 “叔叔,我聽說你玩英雄聯盟很厲害,能不能帶帶我” 呼~~ 果然不是問我怪題了,還好還好。”這個主要得多練,你現在還小要好好學習不能花時間在玩游戲上,你好好學習期末考好了假期我帶你玩!”,”真的?好,我白金1上磚石上了幾次都沒上去,這下有叔叔終于好了!”,”額。。。。。。。”,我想著我黃金3的賬號又陷入了沉思。。。

總結

以上是生活随笔為你收集整理的自定义控件从入门到轻生之---解锁新姿势的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。