三公充气打火机打不出火能分析出那家赢吗?

尊敬的用户您好:
您访问的网站被机房安全管理系统拦截,有可能是以下原因造成:
1.您的网站未备案,或者原备案号被取消。
2.域名在访问黑名单中。
3.其它原因造成的人为对您域名的拦截。《必赢》斗牛牛技巧赢钱培训,斗牛牛技巧赢钱培训,斗牛牛技巧赢钱培训, - 全球塑胶网1
热门标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&咨询热线:8
&当前位置:&&&&&&&&&&&&&
《必赢》斗牛牛技巧赢钱培训
相关供应商推广:供应《必赢》斗牛牛技巧赢钱培训1联系人:唐经理(联系时请说明是在全球塑胶网上看到的)联系电话:微信同号手机:地址:广东 广州市 白云区
《必赢》斗牛牛技巧赢钱培训【+微◆信==我来教教你怎么赢】我们做的是长期生意,靠的是老客户长期支持】【环球时报综合报道】从19日晚间开始,整肃教育系统的消息一直在土耳其NTV等主流媒体上滚动播出。《环球时报》特约记者在安卡拉等地看到,不少当地报纸刊登大幅照片宣传政府的行动。&我们会将他们连根拔起。&土耳其总理耶尔德勒姆周二的这句誓言,被多家报纸援引。
更多新产品微信看视频技术咨询热线/微信:联系人:唐经理
广州好再来科技有限公司全体人员欢迎您!免费培训!包培训,产品真实性!能上门看货,才能买的放心,用的舒心。网购需小心,上门看货才是硬道理。一不小心就买到假货或者是被吊包了请认准我们公司微信和号码:
买扑克报牌设备要先了解清楚。能不能帮你派上场。不要动不动就问价格,派不上场我送给你,你也不会要啊,关键是设备能帮你搞到钱才是重要。不是多少钱问题。设备要隐蔽。报牌要准确,&
这样的效果您还在等什么呢?客户自带工具来当场做实验 & &先试后买&
最新扑克感应分析仪;
一:扑克感应器的装备:【主机+感应管+耳塞+扑克牌=免镜头扑克感应分析仪】无需镜头,可以直接准确的知道每家牌的大小,这大大的增加了的隐蔽性,操作简单方便,使我们在任何场合都可战无不胜。
1、扑克感应器报牌速度仅0.3秒,100%报牌准确。快、准、稳!
2、能疾速准确地辨认变形牌、翘牌、拱牌等,能360 全方位主动查找报牌辨认。
3、扑克感应器可提供80公分,1.2米,1.5米,2米的间隔,供玩家挑选
4、超长待机时辰,实际操作时辰跨过6小时,配上备用电池可达17小时以上。
5、扑克感应器操作简略,便当。
6、扑克感应器集成主动查找功用,操作时无需隐形镜头,主动感应。
7、选用AKK原厂家出的新K1为主机身描绘,产品功用健旺完全,安全可靠,实战最强。
8、扑克感应器集结全国各地上千种玩法,如:三公、斗牛、金花、十三水、小九、牌九、九点半,二八杠等,还可以依照客户的需求订制新玩法。
普通扑克牌感应分析仪:(不用加工、任何普通扑克牌)
  普通扑克感应分析仪,是一款能够剖析普通扑克的电脑分析仪。扑克牌无需提前加工,无需扫描镜头,直接感应即可,就能分析出牌的大小与花色,并让使用者提前知道结果。普通扑克感应器镜头最多可离 扑克10米,整套设备体型小,无需提前加工,无需现场安装镜头,镜头可自己安 装在随身物品饰品之内,或者是手表,手机,火机里面。具有报牌速度快,准确率高,使用方便简单,隐蔽性高等效果。是玩普通扑克牌最高科技,最先进的产品。
扑克感应器的特点:
1、无需摆放镜头,扑克牌任意摆放;
2、感应时间快,效果百分百;
3、任何场所都常高兴;
4、除了包含单人操作分析仪的所有优点外,此产品更加人性化,隐蔽性更加好,使你能够百战百胜!
如果想百战百胜,渴望赢钱,那就赶紧咨询我们,了解更多关于扑克报牌器的详情,早日使用,早日赢钱,万千财富等你来!
高科技光纤数字扑克感应器
& & &不需要佩戴手镜、更不需要在桌面上放任何东西,隐蔽性强也是单人操作最好的辅助产品,让您稳赢不输!高科技光纤数字扑克感应器
1、对于此款产品不需要多人配合也不需要任何镜头,放在桌面上,单人即可现场操作不受场地限制以及强烈灯光的影响。
2、人坐在周边只要将感应器放在口袋里距离扑克牌80公分以内,便可探测出点数及那家大小等,想要谁赢尽在掌控之中。
3、场地不用安装任何东西,桌边也不需要安装会发烫的扫描镜头。
4、一对一专用牌,目前市面上任何产品都检验不出破绽,绝非以往需要扫描变牌的分析仪可比喻的。
5、全国各地不同玩法及出牌头方式都可以按要求设置如三公、斗牛、金花、嗖哈、宝宝、对子均包括在内。
6、外观采用高端智能手机设计,拿的出手的分析仪才让更加的羡慕。
7、扑克感应器的准确率高达百分之一百,速度超快只要在你放牌的一瞬间即刻报出最大家点数。
8、全国所有扑克牌种类均有现货
【普通扑克牌光感分析仪 】【普通扑克牌光感分析仪 】
普通扑克牌光感分析仪 产品介绍:技术咨询热线/微信:联系人:唐经理&
第一;不用配我们的牌,不需要ink">加工任何扑克牌,任何在超市买得扑克牌都可以钟分析结果。
第二;支持全国普通扑克牌玩法,三公,梭哈,斗地主,斗牛,金花,九点,九点半,宝子,对子等等
第三;设备体型小,操作简单,随身携带,没有任何操作难度,安全隐蔽无任何破绽
第四,台面上不用放任何东西,不管别人怎么洗牌,切牌,一样能提前知道结果
第五;不用弹牌角,不需要翘牌,没有任何拿牌手势的讲究,更没有固定放牌的位置
第六;自己不用动牌一样报牌,牌洗好没有发出来之前就报牌
第七;感应范围大,距离远,在范围内感应波长360&自动搜索任何普通扑克牌
第八;低消耗,电池可使用达10个小时左右
第九;各种玩法,一键设置,轻轻松松提前知道结果,不受任何光源的限制
第十;来人请自己带上你们的普通扑克牌来试效果,同时产品免费保修一年,三十天内包退包换。
本产品具有以下各种优点;
单人操作分析仪
(1)S6真机分析仪,外观时尚,高端大气上档次,功能齐全;
(2)单人单机,操作简单方便,市面上任意普通牌都可玩;
(3)不受场地限制,站着坐着都可以,桌面上不需要放任何东西;
(4)牌任意洗、切、拨等都可报牌(可知道哪家牌最大),不需要多余动作;
(5)超宽范围镜头,自动扫描扑捉牌,0.1秒快速识别,分析报牌准确无误。做庄闲都可以拿大牌
(6)一对一蓝牙隐形耳机语音传送,语音更清晰,操作更隐蔽;
(7)适合全国各地玩法:十三水、梭哈、三公金花等等各种玩法。
X光番摊分析仪
1.准确率高,可以准确分析出豆子的数目,无论是盖在碗里的还是随意抓在手上的都可分析2.出来,绝不报错。
2..超强的穿透力,可以轻松穿透杯碗,穿透两厘米厚的木板也毫无压力。
3.扫面穿透距离远,5-10内都能准确分析出豆子数目。
4.整体式机组设计,安装灵活方便,控制方便,安全隐蔽。
5.一机多用,运行稳定,高效节能。
X光番摊分析仪,玩番摊的最佳高科技产品。
便携式微型CT透视仪,最新一代娱乐设备,是最新猜宝道具,是对场地、娱乐用品不作任何加工处理的道具,被称为千中之王。
它是根据医用CT和安检视透仪改进而成,具有隐蔽性强,不限场地、操作简单、反应快、体积小等特点。
只需用CT光头(在5米之内)对准物体一晃,即可看穿任何普通瓷器,金属,竹筒,布匹等制品。可直接视透瓷碗(杯)、金属杯、塑料杯、纸盒、布料,普通麻将,扑克,骰子等物品。
场外100米-5000米之内主机显示屏上即可显示被盖物体内的物体的大小,花色,点数,方位,花纹图案及形状,图象清晰。
探测仪可安装在打火机、手机、手表及戒指等日常用品内,这些物品还可正常使用。
探测仪和显示屏的有效距离为米,该仪器需2人操作,牌具无需任何加工处理,可直接看穿。
此仪器无线接收信号,体积小,携带方便,隐蔽性强。
本公司谨重承诺!产品货真价实!可免费先试后买。
如果您对本产品有什么疑问和兴趣的话欢迎随时致电
各位新老顾客,如果你对本公司这款新产品感兴趣可以继续浏览以下内容:
领航一号感应筒子隆重上市,六大优势让您驰骋沙场、百战百胜!
优势一:独家脚控遥控器,叠好牌后自动取数据 领航1号,采用最新重力感应技术,独创脚控遥控器,操作更加简单方便,彻底解放了双手,使台面操作更自然,更安全!牌叠好后自动读取数据。
优势二;坐庄、坐闲,想赢就赢 1:无需控色,庄家智能动牌形成平点或生死门,点哪家,杀哪家。2.任意开门,闲家听生死门钓鱼赢庄家。
优势三:中途可变化开门方式、中途任意过牌都可智能识别 领航一号筒子中途变化开门方式也可智能识别,中途任意过牌也可准确报出结果。
优势四:支持全国各地各种玩法 领航1号,实用功能更加丰富齐全,搜罗万千玩法于一身,甚至各种特殊玩法都能轻松应对。
优势五:手动、脚控摇控器可任意选配,功能可根据客户需求自定义选择 。
优势六:品质卓越
1、待机时间长,提前1个月放置于牌场都毫无问题。
2.唤醒时间短,使用时,60秒极速唤醒,临场想用就用。
3.满电可使用8-10小时。
老人头的苹果六机变牌 & &
功能齐全:
1.可以双卡双待,加载内存卡 &
2.可以玩游戏、听歌、语音视频&
3.前后摄像头可以照相、可以看电影 &
4.可以连接wifi &
5.可以下载安装各种应用APP&
6.可以上微信、QQ、接打电话等等
电话:微信同号
地址:广东 广州市 白云区
分享转载请注明本文地址:/html/sellinfo/195/.htm
免责声明:以上展示的"《必赢》斗牛牛技巧赢钱培训"信息由自行提供,内容的真实性、准确性和合法性由发布企业负责,对此不承担责任。
风险防范建议:为保障您的利益,建议优先选择该公司的供应信息分布在以下分类中,请查看
采购信息快速发布通道
是否E站通会员:是否
该企业提供的其它产品
相关采购信息
最近供应信息
最近公司信息
最近行情信息
最近资讯信息
搜 索相关搜索: 版权所有 ©
网络实名:全球塑胶网
总机:8 9 客服热线:2 传真:0本网客服QQ: 展会合作QQ: 全球塑胶网业务QQ:Copyright ? 2005-. All Rights ReservedAndroid應用層View繪制流程與源碼分析
【工匠若水 http://blog.csdn.net/yanbober 轉載煩請注明出處,尊重分享成果】
還記得前面《Android應用setContentView與LayoutInf
【工匠若水
轉載煩請注明出處,尊重分享成果】
還記得前面這篇文章嗎?我們有分析到Activity中界面加載顯示的基本流程原理,記不記得最終分析結果就是下面的關系:
看見沒有,如上圖中id为content的內容就是整個View樹的結構,所以對每個具體View對象的操作,其實就是個遞歸的實現。
前面文章的3-1小節說過Android中的任何一個布局、任何一個控件其實都是直接或間接繼承自View實現的,當然也包括我們後面一步一步引出的自定義控件也不例外,所以說這些View應該都具有相同的繪制流程與機制才能顯示到屏幕上(因为他們都具備相同的父類View,可能每個控件的具體繪制邏輯有差異,但是主流程都是一样的)。經過總結發現每一個View的繪制過程都必須經曆三個最主要的過程,也就是measure、layout和draw。
既然一個View的繪制主要流程是這三步,那一定有一個開始地方呀,就像一個類從main函數執行一样呀。對於View的繪制開始調運地方這裏先给出結論,本文後面會反過來分析原因的,先往下看就行。具體結論如下:
整個View樹的繪圖流程是在ViewRootImpl類的performTraversals()方法(這個方法巨長)開始的,該函數做的執行過程主要是根據之前設置的狀態,判斷是否重新計算視圖大小(measure)、是否重新放置視圖的位置(layout)、以及是否重繪 (draw),其核心也就是通過判斷來選擇順序執行這三個方法中的哪個,如下:
private void performTraversals() {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
mView.draw(canvas);
* Figures out the measure spec for the root view in a window based on it's
* layout params.
* windowSize
The available width or height of the window
* rootDimension
The layout params for one dimension (width or height) of the
* The measure spec to use to measure the root view.
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureS
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
return measureS
可以看見這個方法的注釋說是用來測Root View的。上面傳入参數後這個函數走的是MATCH_PARENT,使用MeasureSpec.makeMeasureSpec方法組裝一個MeasureSpec,MeasureSpec的specMode等於EXACTLY,specSize等於windowSize,也就是为何根視圖總是全屏的原因。
其中的mView就是View對象。如下就是整個流程的大致流程圖:
如下我們就依據View繪制的這三個主要流程進行詳細剖析(基於Android5.1.1 API 22源碼進行分析)。
【工匠若水
轉載煩請注明出處,尊重分享成果】
2 View繪制流程第一步:遞歸measure源碼分析
整個View樹的源碼measure流程圖如下:
measure源碼分析
先看下View的measure方法源碼,如下:
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* widthMeasureSpec Horizontal space requirements as imposed by the
* heightMeasureSpec Vertical space requirements as imposed by the
* #onMeasure(int, int)
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
看見注釋信息沒有,他告訴你了很多重要信息。为整個View樹計算實際的大小,然後設置實際的高和寬,每個View控件的實際寬高都是由父視圖和自身决定的。實際的測量是在onMeasure方法進行,所以在View的子類需要重寫onMeasure方法,這是因为measure方法是final的,不允許重載,所以View子類只能通過重載onMeasure來實現自己的測量邏輯。
這個方法的兩個参數都是父View傳遞過來的,也就是代表了父view的規格。他由兩部分組成,高16位表示MODE,定義在MeasureSpec類(View的內部類)中,有三種類型,MeasureSpec.EXACTLY表示確定大小, MeasureSpec.AT_MOST表示最大大小, MeasureSpec.UNSPECIFIED不確定。低16位表示size,也就是父View的大小。對於系統Window類的DecorVIew對象Mode一般都为MeasureSpec.EXACTLY ,而size分別對應屏幕寬高。對於子View來說大小是由父View和子View共同决定的。
在這裏可以看出measure方法最終回調了View的onMeasure方法,我們來看下View的onMeasure源碼,如下:
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
* &strong&CONTRACT:&/strong& When overriding this method, you
* &em&must&/em& call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* &code&IllegalStateException&/code&, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* widthMeasureSpec horizontal space requirements as imposed by the parent.
The requirements are encoded with
{@link android.view.View.MeasureSpec}.
* heightMeasureSpec vertical space requirements as imposed by the parent.
The requirements are encoded with
{@link android.view.View.MeasureSpec}.
* #getMeasuredWidth()
* #getMeasuredHeight()
* #setMeasuredDimension(int, int)
* #getSuggestedMinimumHeight()
* #getSuggestedMinimumWidth()
* android.view.View.MeasureSpec#getMode(int)
* android.view.View.MeasureSpec#getSize(int)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
看見沒有,其實注釋已經很詳細了(自定義View重寫該方法的指導操作注釋都有說明),不做過多解釋。
對於非ViewGroup的View而言,通過調用上面默認的onMeasure即可完成View的測量,當然你也可以重載onMeasure並調用setMeasuredDimension來設置任意大小的布局,但一般不這麼做,因为這種做法不太好,至於为何不好,後面分析完你就明白了。
我們可以看見onMeasure默認的實現僅僅調用了setMeasuredDimension,setMeasuredDimension函數是一個很關鍵的函數,它對View的成員變量mMeasuredWidth和mMeasuredHeight變量賦值,measure的主要目的就是對View樹中的每個View的mMeasuredWidth和mMeasuredHeight進行賦值,所以一旦這兩個變量被賦值意味着該View的測量工作結束。既然這样那我們就看看設置的默認尺寸大小吧,可以看見setMeasuredDimension傳入的参數都是通過getDefaultSize返回的,所以再來看下getDefaultSize方法源碼,如下:
public static int getDefaultSize(int size, int measureSpec) {
int result =
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specS
看見沒有,如果specMode等於AT_MOST或EXACTLY就返回specSize,這就是系統默認的規格。
回過頭繼續看上面onMeasure方法,其中getDefaultSize参數的widthMeasureSpec和heightMeasureSpec都是由父View傳遞進來的。getSuggestedMinimumWidth與getSuggestedMinimumHeight都是View的方法,具體如下:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
看見沒有,建議的最小寬度和高度都是由View的Background尺寸與通過設置View的miniXXX屬性共同决定的。
到此一次最基礎的元素View的measure過程就完成了。上面說了View實際是嵌套的,而且measure是遞歸傳遞的,所以每個View都需要measure。實際能夠嵌套的View一般都是ViewGroup的子類,所以在ViewGroup中定義了measureChildren, measureChild,
measureChildWithMargins方法來對子視圖進行測量,measureChildren內部實質只是循環調用measureChild,measureChild和measureChildWithMargins的區別就是是否把margin和padding也作为子視圖的大小。如下我們以ViewGroup中稍微复雜的measureChildWithMargins方法來分析:
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
* child The child to measure
* parentWidthMeasureSpec The width requirements for this view
* widthUsed Extra space that has been used up by the parent
horizontally (possibly by other children of the parent)
* parentHeightMeasureSpec The height requirements for this view
* heightUsed Extra space that has been used up by the parent
vertically (possibly by other children of the parent)
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
關於該方法的参數等說明注釋已經描述的夠清楚了。該方法就是對父視圖提供的measureSpec参數結合自身的LayoutParams参數進行了調整,然後再來調用child.measure()方法,具體通過方法getChildMeasureSpec來進行参數調整。所以我們繼續看下getChildMeasureSpec方法代碼,如下:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension &= 0) {
resultSize = childD
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize =
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize =
resultMode = MeasureSpec.AT_MOST;
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
可以看見,getChildMeasureSpec的邏輯是通過其父View提供的MeasureSpec参數得到specMode和specSize,然後根據計算出來的specMode以及子View的childDimension(layout_width或layout_height)來計算自身的measureSpec,如果其本身包含子視圖,則計算出來的measureSpec將作为調用其子視圖measure函數的参數,同時也作为自身調用setMeasuredDimension的参數,如果其不包含子視圖則默認情況下最終會調用onMeasure的默認實現,並最終調用到setMeasuredDimension。
所以可以看見onMeasure的参數其實就是這麼計算出來的。同時從上面的分析可以看出來,最終决定View的measure大小是View的setMeasuredDimension方法,所以我們可以通過setMeasuredDimension設定死值來設置View的mMeasuredWidth和mMeasuredHeight的大小,但是一個好的自定義View應該會根據子視圖的measureSpec來設置mMeasuredWidth和mMeasuredHeight的大小,這样的靈活性更大,所以這也就是上面分析onMeasure時說View的onMeasure最好不要重寫死值的原因。
可以看見當通過setMeasuredDimension方法最終設置完成View的measure之後View的mMeasuredWidth和mMeasuredHeight成員才會有具體的數值,所以如果我們自定義的View或者使用現成的View想通過getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之後被調用才能返回有效值。
還記得前面文章3-3小節探討的inflate方法加載一些布局顯示時指定的大小失效問題嗎?當時只给出了結論,現在给出了詳細原因分析,我想不需要再做過多解釋了吧。
至此整個View繪制流程的第一步就分析完成了,可以看見,相對來說還是比較复雜的,接下來進行小結。
measure原理總結
通過上面分析可以看出measure過程主要就是從頂層父View向子View遞歸調用view.measure方法(measure中又回調onMeasure方法)的過程。具體measure核心主要有如下幾點:
MeasureSpec(View的內部類)測量規格为int型,值由高16位規格模式specMode和低16位具體尺寸specSize組成。其中specMode只有三種值:
MeasureSpec.EXACTLY
MeasureSpec.AT_MOST
MeasureSpec.UNSPECIFIED
View的measure方法是final的,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯。
最頂層DecorView測量時的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法確定的(LayoutParams寬高参數均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計算。
只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin参數。
View的布局大小由父View和子View共同决定。
使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之後被調用才能返回有效值。
【工匠若水
轉載煩請注明出處,尊重分享成果】
3 View繪制流程第二步:遞歸layout源碼分析
在上面的背景介紹就說過,當ViewRootImpl的performTraversals中measure執行完成以後會接着執行mView.layout,具體如下:
private void performTraversals() {
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
可以看見layout方法接收四個参數,這四個参數分別代表相對Parent的左、上、右、下坐標。而且還可以看見左上都为0,右下分別为上面剛剛測量的width和height。
至此又回歸到View的layout(int l, int t, int r, int b)方法中去實現具體邏輯了,所以接下來我們開始分析View的layout過程。
整個View樹的layout遞歸流程圖如下:
layout源碼分析
layout既然也是遞歸結構,那我們先看下ViewGroup的layout方法,如下:
public final void layout(int l, int t, int r, int b) {
super.layout(l, t, r, b);
看着沒有?ViewGroup的layout方法實質還是調運了View父類的layout方法,所以我們看下View的layout源碼,如下:
public void layout(int l, int t, int r, int b) {
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
看見沒有,類似measure過程,lauout調運了onLayout方法。
對比上面View的layout和ViewGroup的layout方法可以發現,View的layout方法是可以在子類重寫的,而ViewGroup的layout是不能在子類重寫的,言外之意就是說ViewGroup中只能通過重寫onLayout方法。那我們接下來看下ViewGroup的onLayout方法,如下:
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
看見沒有?ViewGroup的onLayout()方法竟然是一個抽象方法,這就是說所有ViewGroup的子類都必須重寫這個方法。所以在自定義ViewGroup控件中,onLayout配合onMeasure方法一起使用可以實現自定義View的复雜布局。自定義View首先調用onMeasure進行測量,然後調用onLayout方法動態獲取子View和子View的測量大小,然後進行layout布局。重載onLayout的目的就是安排其children在父View的具體位置,重載onLayout通常做法就是寫一個for循環調用每一個子視圖的layout(l, t, r, b)函數,傳入不同的参數l, t, r, b來確定每個子視圖在父視圖中的顯示位置。
再看下View的onLayout方法源碼,如下:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
我勒個去!是一個空方法,沒啥可看的。
既然這样那我們只能分析一個現有的繼承ViewGroup的控件了,就拿LinearLayout來說吧,如下是LinearLayout中onLayout的一些代碼:
public class LinearLayout extends ViewGroup {
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
layoutHorizontal(l, t, r, b);
看見沒有,LinearLayout的layout過程是分Vertical和Horizontal的,這個就是xml布局的orientation屬性設置的,我們为例說明ViewGroup的onLayout重寫一般步驟就拿這裏的VERTICAL模式來解釋吧,如下是layoutVertical方法源碼:
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingL
int childT
int childL
final int width = right -
int childRight = width - mPaddingR
int childSpace = width - paddingLeft - mPaddingR
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
childTop = mPaddingTop + bottom - top - mTotalL
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
case Gravity.TOP:
childTop = mPaddingT
for (int i = 0; i & i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.
if (gravity & 0) {
gravity = minorG
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightM
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightM
case Gravity.LEFT:
childLeft = paddingLeft + lp.leftM
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerH
childTop += lp.topM
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
從上面分析的ViewGroup子類LinearLayout的onLayout實現代碼可以看出,一般情況下layout過程會参考measure過程中計算得到的mMeasuredWidth和mMeasuredHeight來安排子View在父View中顯示的位置,但這不是必須的,measure過程得到的結果可能完全沒有實際用處,特別是對於一些自定義的ViewGroup,其子View的個數、位置和大小都是固定的,這時候我們可以忽略整個measure過程,只在layout函數中傳入的4個参數來安排每個子View的具體位置。
到這裏就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()這兩對方法之間的區別(上面分析measure過程已經說過getMeasuredWidth()、getMeasuredHeight()必須在onMeasure之後使用才有效)。可以看出來getWidth()與getHeight()方法必須在layout(int l, int t, int r, int b)執行之後才有效。那我們看下View源碼中這些方法的實現吧,如下:
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
public final int getWidth() {
return mRight - mL
public final int getHeight() {
return mBottom - mT
public final int getLeft() {
public final int getRight() {
public final int getTop() {
public final int getBottom() {
這也解釋了为什麼有些情況下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()會得到不同的值,所以這裏不做過多解釋。
到此整個View的layout過程分析就算結束了,接下來進行一些總結工作。
layout原理總結
整個layout過程比較容易理解,從上面分析可以看出layout也是從頂層父View向子View的遞歸調用view.layout方法的過程,即父View根據上一步measure子View所得到的布局大小和布局参數,將子View放在合适的位置上。具體layout核心主要有以下幾點:
View.layout方法可被重載,ViewGroup.layout为final的不可重載,ViewGroup.onLayout为abstract的,子類必須重載實現自己的位置邏輯。
measure操作完成後得到的是對每個View經測量過的measuredWidth和measuredHeight,layout操作完成之後得到的是對每個View進行位置分配後的mLeft、mTop、mRight、mBottom,這些值都是相對於父View來說的。
凡是layout_XXX的布局屬性基本都針對的是包含子View的ViewGroup的,當對一個沒有父容器的View設置相關layout_XXX屬性是沒有任何意義的(前面也有提到過)。
使用View的getWidth()和getHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onLayout流程之後被調用才能返回有效值。
【工匠若水
轉載煩請注明出處,尊重分享成果】
4 View繪制流程第三步:遞歸draw源碼分析
在上面的背景介紹就說過,當ViewRootImpl的performTraversals中measure和layout執行完成以後會接着執行mView.layout,具體如下:
private void performTraversals() {
final Rect dirty = mD
canvas = mSurface.lockCanvas(dirty);
mView.draw(canvas);
draw過程也是在ViewRootImpl的performTraversals()內部調運的,其調用順序在measure()和layout()之後,這裏的mView對於Actiity來說就是PhoneWindow.DecorView,ViewRootImpl中的代碼會創建一個Canvas對象,然後調用View的draw()方法來執行具體的繪制工。所以又回歸到了ViewGroup與View的樹狀遞歸draw過程。
先來看下View樹的遞歸draw流程圖,如下:
如下我們詳細分析這一過程。
draw源碼分析
由於ViewGroup沒有重寫View的draw方法,所以如下直接從View的draw方法開始分析:
public void draw(Canvas canvas) {
if (!dirtyOpaque) {
drawBackground(canvas);
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
if (!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
onDrawScrollBars(canvas);
看見整個View的draw方法很复雜,但是源碼注釋也很明顯。從注釋可以看出整個draw過程分为了6步。源碼注釋說(”skip step 2 & 5 if possible (common case)”)第2和5步可以跳過,所以我們接下來重點剩餘四步。如下:
第一步,對View的背景進行繪制。
可以看見,draw方法通過調運drawBackground(canvas);方法實現了背景繪制。我們來看下這個方法源碼,如下:
private void drawBackground(Canvas canvas) {
final Drawable background = mB
if (mBackgroundSizeChanged) {
background.setBounds(0, 0,
mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
background.draw(canvas);
第三步,對View的內容進行繪制。
可以看到,這裏去調用了一下View的onDraw()方法,所以我們看下View的onDraw方法(ViewGroup也沒有重寫該方法),如下:
* Implement this to do your drawing.
* canvas the canvas on which the background will be drawn
protected void onDraw(Canvas canvas) {
可以看見,這是一個空方法。因为每個View的內容部分是各不相同的,所以需要由子類去實現具體邏輯。
第四步,對當前View的所有子View進行繪制,如果當前的View沒有子View就不需要進行繪制。
我們來看下View的draw方法中的dispatchDraw(canvas);方法源碼,可以看見如下:
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* canvas the canvas on which to draw the view
protected void dispatchDraw(Canvas canvas) {
看見沒有,View的dispatchDraw()方法是一個空方法,而且注釋說明了如果View包含子類需要重寫他,所以我們有必要看下ViewGroup的dispatchDraw方法源碼(這也就是剛剛說的對當前View的所有子View進行繪制,如果當前的View沒有子View就不需要進行繪制的原因,因为如果是View調運該方法是空的,而ViewGroup才有實現),如下:
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenC
final View[] children = mC
for (int i = 0; i & childrenC i++) {
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
if (mDisappearingChildren != null) {
for (int i = disappearingC i &= 0; i--) {
more |= drawChild(canvas, child, drawingTime);
可以看見,ViewGroup確實重寫了View的dispatchDraw()方法,該方法內部會遍曆每個子View,然後調用drawChild()方法,我們可以看下ViewGroup的drawChild方法,如下:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
可以看見drawChild()方法調運了子View的draw()方法。所以說ViewGroup類已經为我們重寫了dispatchDraw()的功能實現,我們一般不需要重寫該方法,但可以重載父類函數實現具體的功能。
第六步,對View的滾動條進行繪制。
可以看到,這裏去調用了一下View的onDrawScrollBars()方法,所以我們看下View的onDrawScrollBars(canvas);方法,如下:
* &p&Request the drawing of the horizontal and the vertical scrollbar. The
* scrollbars are painted only if they have been awakened first.&/p&
* canvas the canvas on which to draw the scrollbars
* #awakenScrollBars(int)
protected final void onDrawScrollBars(Canvas canvas) {
可以看見其實任何一個View都是有(水平垂直)滾動條的,只是一般情況下沒讓它顯示而已。
到此,View的draw繪制部分源碼分析完畢,我們接下來進行一些總結。
draw原理總結
可以看見,繪制過程就是把View對象繪制到屏幕上,整個draw過程需要注意如下細節:
如果該View是一個ViewGroup,則需要遞歸繪制其所包含的所有子View。
View默認不會繪制任何內容,真正的繪制都需要自己在子類中實現。
View的繪制是借助onDraw方法傳入的Canvas類來進行的。
區分View動畫和ViewGroup布局動畫,前者指的是View自身的動畫,可以通過setAnimation添加,後者是專門針對ViewGroup顯示內部子視圖時設置的動畫,可以在xml布局文件中對ViewGroup設置layoutAnimation屬性(譬如對LinearLayout設置子View在顯示時出現逐行、隨機、下等顯示等不同動畫效果)。
在獲取畫布剪切區(每個View的draw中傳入的Canvas)時會自動處理掉padding,子View獲取Canvas不用關注這些邏輯,只用關心如何繪制即可。
默認情況下子View的ViewGroup.drawChild繪制順序和子View被添加的順序一致,但是你也可以重載ViewGroup.getChildDrawingOrder()方法提供不同順序。
【工匠若水
轉載煩請注明出處,尊重分享成果】
你可能已經看見了,在上面分析View的三步繪制流程中最後都有調運一個叫invalidate的方法,這個方法是啥玩意?为何出現頻率這麼高?很簡單,我們拿出來分析分析不就得了。
來看一下View類中的一些invalidate方法(ViewGroup沒有重寫這些方法),如下:
* Mark the area defined by dirty as needing to be drawn. If the view is
* visible, {@link #onDraw(android.graphics.Canvas)} will be called at some
* point in the future.
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
* &b&WARNING:&/b& In API 19 and below, this method may be destructive to
* {@code dirty}.
* dirty the rectangle representing the bounds of the dirty region
public void invalidate(Rect dirty) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
dirty.right - scrollX, dirty.bottom - scrollY, true, false);
* Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The
* coordinates of the dirty rect are relative to the view. If the view is
* visible, {@link #onDraw(android.graphics.Canvas)} will be called at some
* point in the future.
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
* l the left position of the dirty region
* t the top position of the dirty region
* r the right position of the dirty region
* b the bottom position of the dirty region
public void invalidate(int l, int t, int r, int b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future.
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
public void invalidate() {
invalidate(true);
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
* invalidateCache Whether the drawing cache for this view should be
invalidated as well. This is usually true for a full
invalidate, but may be set to false if the View's contents or
dimensions have not changed.
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
final AttachInfo ai = mAttachI
final ViewParent p = mP
if (p != null && ai != null && l & r && t & b) {
final Rect damage = ai.mTmpInvalR
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
看見沒有,View的invalidate(invalidateInternal)方法實質是將要刷新區域直接傳遞给了父ViewGroup的invalidateChild方法,在invalidate中,調用父View的invalidateChild,這是一個從當前向上級父View回溯的過程,每一層的父View都將自己的顯示區域與傳入的刷新Rect做交集 。所以我們看下ViewGroup的invalidateChild方法,源碼如下:
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachI
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
這個過程最後傳遞到ViewRootImpl的invalidateChildInParent方法結束,所以我們看下ViewRootImpl的invalidateChildInParent方法,如下:
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
scheduleTraversals();
return null;
看見沒有?這個ViewRootImpl類的invalidateChildInParent方法直接返回了null,也就是上面ViewGroup中說的,層層上級傳遞到ViewRootImpl的invalidateChildInParent方法結束了那個do while循環。看見這裏調運的scheduleTraversals這個方法嗎?scheduleTraversals會通過Handler的Runnable發送一個異步消息,調運doTraversal方法,然後最終調用performTraversals()執行重繪。開頭背景知識介紹說過的,performTraversals就是整個View數開始繪制的起始調運地方,所以說View調運invalidate方法的實質是層層上傳到父級,直到傳遞到ViewRootImpl後觸發了scheduleTraversals方法,然後整個View樹開始重新按照上面分析的View繪制流程進行重繪任務。
到此View的invalidate方法原理就分析完成了。
上面分析invalidate方法時注釋中說該方法只能在UI Thread中執行,其他線程中需要使用postInvalidate方法,所以我們來分析分析postInvalidate這個方法源碼。如下:
public void postInvalidate() {
postInvalidateDelayed(0);
繼續看下他的調運方法postInvalidateDelayed,如下:
public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachI
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
我們繼續看他調運的ViewRootImpl類的dispatchInvalidateDelayed方法,如下源碼:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
看見沒有,通過ViewRootImpl類的Handler發送了一條MSG_INVALIDATE消息,繼續追蹤這條消息的處理可以發現:
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
看見沒有,實質就是又在UI Thread中調運了View的invalidate();方法,那接下來View的invalidate();方法我們就不說了,上名已經分析過了。
到此整個View的postInvalidate方法就分析完成了。
依據上面對View的invalidate分析我總結繪制如下流程圖:
依據上面對View的postInvalidate分析我總結繪制如下流程圖:
關於這兩個方法的具體流程和原理上面也分析過了,流程圖也给出了,相信已經很明確了,沒啥需要解釋的了。所以我們對其做一個整體總結,歸納出重點如下:
invalidate系列方法請求重繪View樹(也就是draw方法),如果View大小沒有發生變化就不會調用layout過程,並且只繪制那些“需要重繪的”View,也就是哪個View(View只繪制該View,ViewGroup繪制整個ViewGroup)請求invalidate系列方法,就繪制該View。
常見的引起invalidate方法操作的原因主要有:
直接調用invalidate方法.請求重新draw,但只會繪制調用者本身。
觸發setSelection方法。請求重新draw,但只會繪制調用者本身。
觸發setVisibility方法。 當View可視狀態在INVISIBLE轉換VISIBLE時會間接調用invalidate方法,繼而繪制該View。當View的可視狀態在INVISIBLE\VISIBLE 轉換为GONE狀態時會間接調用requestLayout和invalidate方法,同時由於View樹大小發生了變化,所以會請求measure過程以及draw過程,同样只繪制需要“重新繪制”的視圖。
觸發setEnabled方法。請求重新draw,但不會重新繪制任何View包括該調用者本身。
觸發requestFocus方法。請求View樹的draw過程,只繪制“需要重繪”的View。
分析完invalidate後需要你回過頭去想一個問題。還記不記得這篇文章的開頭背景介紹,我們說整個View繪制流程的最初代碼是在ViewRootImpl類的performTraversals()方法中開始的。上面當時只是告訴你了這個結論,至於這個ViewRootImpl類的performTraversals()方法为何會被觸發沒有說明原因。現在我們就來分析一下這個觸發的源頭。
讓我們先把大腦思考暫時挪回到這篇博文的setContentView機制分析中(不清楚的請點擊先看這篇文章再回過頭來繼續看)。我們先來看下那篇博文分析的PhoneWindow的setContentView方法源碼,如下:
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
mContentParent.addView(view, params);
這個方法是Activity中setContentView的實現,我們繼續看下這個方法裏調運的addView方法,也就是ViewGroup的addView方法,如下:
public void addView(View child) {
addView(child, -1);
public void addView(View child, int index) {
addView(child, index, params);
public void addView(View child, int index, LayoutParams params) {
requestLayout();
invalidate(true);
看見addView調運invalidate方法沒有?這不就真相大白了。當我們寫一個Activity時,我們一定會通過setContentView方法將我們要展示的界面傳入該方法,該方法會講我們界面通過addView追加到id为content的一個FrameLayout(ViewGroup)中,然後addView方法中通過調運invalidate(true)去通知觸發ViewRootImpl類的performTraversals()方法,至此遞歸繪制我們自定義的所有布局。
【工匠若水
轉載煩請注明出處,尊重分享成果】
6 View的requestLayout方法源碼分析
requestLayout方法分析
和invalidate類似,其實在上面分析View繪制流程時或多或少都調運到了這個方法,而且這個方法對於View來說也比較重要,所以我們接下來分析一下他。如下View的requestLayout源碼:
public void requestLayout() {
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
看見沒有,當我們觸發View的requestLayout時其實質就是層層向上傳遞,直到ViewRootImpl为止,然後觸發ViewRootImpl的requestLayout方法,如下就是ViewRootImpl的requestLayout方法:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
看見沒有,類似於上面分析的invalidate過程,只是設置的標記不同,導致對於View的繪制流程中觸發的方法不同而已。
requestLayout方法總結
可以看見,這些方法都是大同小異。對於requestLayout方法來說總結如下:
requestLayout()方法會調用measure過程和layout過程,不會調用draw過程,也不會重新繪制任何View包括該調用者本身。
7 View繪制流程總結
至此整個關於Android應用程序開發中的View繪制機制及相關重要方法都已經分析完畢。關於各個方法的總結這裏不再重复,直接通過該文章前面的目錄索引到相應方法的總結小節進行查閱即可。
【工匠若水
轉載煩請注明出處,尊重分享成果】From:
------分隔線----------------------------
上一篇:沒有了
下一篇:沒有了
【工匠若水 http://blog.csdn.net/yanbober 轉載煩請注明出處,尊重分享...
仿獵豹垃圾清理(實現原理+源碼)
轉載請注明出處: 仿獵豹垃...
Ted Mosby - 軟件架構
作者:Hannes Dorfmann
原文鏈接 : [http://hannes...
在大型軟件系統中,为了監測軟件運行狀況及排查軟件故...
& & & &我們知道,Android系統在启動的時候,會對一...
一、架構圖
下邊是一張Struts2的官方文檔中的Struts2的構架圖

我要回帖

更多关于 zipp打火机打不出火 的文章

 

随机推荐