文章僅做個人筆記使用:
是所有控件的基類钓丰,ViewGroup同樣也繼承了View疹蛉。通過四個參數(shù)確定他在父容器中的位置磺平,左上角(left靴姿,top)右下角(right,bottom)沃但。
MotionEvent:觸摸事件。down,move,up
getX/getY:獲取相對于View的坐標(biāo)
getRawX/getRawY:獲取相對于手機屏幕的坐標(biāo)
TouchSlop:系統(tǒng)能識別出的最小滑動距佛吓,不同的設(shè)備值不同宵晚。
View的滑動
第一種:通過View 本身提供的scrollTo/scrollBy方法實現(xiàn)滑動垂攘。
第二種:通過動畫給View增加平移效果達到滑動。
第三種:通過改變View的LayoutParams使得View重新布局從而實現(xiàn)滑動淤刃。
scrollTo和scrollBy的區(qū)別:
- scrollBy內(nèi)部調(diào)用了scrollTo晒他。scrollBy是基于當(dāng)前位置的相對滑動。scrollTo是絕對滑動钝凶,因此如果使用相同輸入?yún)?shù)多次調(diào)用scrollTo,由于View的初始位置是不變的仪芒,所以只會出現(xiàn)一次View滾動的效果。
- 兩者只能對view中的內(nèi)容滑動而非View本身滑動耕陷。
區(qū)別:
第一種只能滑動view內(nèi)容不能滑動View本身掂名,例如一個TextView通過scrollTo移動,只會將其中的文字內(nèi)容移動哟沫,TextView并不會移動饺蔑。第二種如果可以使用屬性動畫就可以避免交互問題,而且復(fù)雜的移動效果動畫有明顯優(yōu)勢嗜诀。第三種操作有些復(fù)雜猾警。
View的事件分發(fā)機制:
主要方法:
dispatchTouchEvent():用來進行事件分發(fā),如果事件能夠傳遞給該View此方法一定會被調(diào)用隆敢,返回結(jié)果受當(dāng)前View的onTouchEvent()和下級View的dispatchTouchEvent()決定发皿。表示是否消耗該事件。
onInterceptTouchEvent():截聽:在dispatchTouchEvent()內(nèi)部調(diào)用拂蝎,判斷是否攔截該事件穴墅,如果當(dāng)前View進行攔截,那么在同一個事件序列當(dāng)中該方法不會再被調(diào)用温自。
onTouchEvent():在dispatchTouchEvent()中調(diào)用玄货,用來處理事件,返回結(jié)果表示是否消耗該事件悼泌,如果不消耗松捉,該View在同一個時間序列中不會再接收到事件。
三者的關(guān)系:事件的傳遞過程都是由外而內(nèi)的馆里,當(dāng)一個事件產(chǎn)生后總是會先傳遞給Activity隘世,Activity會傳遞給Window(是抽象的,唯一實現(xiàn)是phoneWindow)鸠踪,Window會傳給DecorView丙者,進而傳給頂級View。當(dāng)一個根View接收到一個事件后慢哈,他的dispatchTouchEvent()會被調(diào)用,如果他的onInterceptTouchEvent()方法返回true則表示他要攔截該事件永票,接著他的onTouchEvent就會被調(diào)用卵贱。如果onInterceptTouchEvent()返回false表示不攔截該事件滥沫,那么該View的子View的dispatchTouchEvent就會被調(diào)用。如果沒有子View那么他的父View的onTouchEvent()就會被調(diào)用键俱,如果父View的onToucheEvent也返回false兰绣,那么事件就會繼續(xù)往上傳遞最終會被Activity的onTouchEvent()處理。
如果一個View設(shè)置OnTouchListener那么是否消耗事件還需要看OnTouchListener中onTouch方法的返回值编振,如果返回false則onTouchEvent會被調(diào)用缀辩,如果返回true則onTouchEvent不會被調(diào)用。
ViewGroup默認(rèn)不攔截任何事件踪央,源碼中ViewGroup的onInterceptTouchEvent()默認(rèn)返回false.
View是沒有onInterceptTouchEvent()方法的臀玄,一旦有事件傳入就會調(diào)用onTouchEvent(),onTouchEvent默認(rèn)都會消耗掉事件畅蹂,除非設(shè)置不可點擊健无。View的longClickable默認(rèn)都是false,clickable需要區(qū)分液斜,button默認(rèn)為true,textVIew默認(rèn)為false.
- 事件序列:從手指觸摸屏幕到離開屏幕這個過程中產(chǎn)生的一些列事件累贤。
- DecorView繼承自FrameLayout他的子view 一般情況下是一個LinerLayout分上下兩部分上部分標(biāo)題欄下面是內(nèi)容欄。通過setContentView()設(shè)置的View是他(內(nèi)容欄)的一個子元素少漆,也就是頂級元素臼膏,頂級元素一般都是ViewGroup。
- 通過requestDisallowInterceptTouchEvent()可以在子元素中敢于父元素的的事件分發(fā)過程示损。ACTION_DOWN事件除外渗磅。
- 是否能夠接收事件:
1.子元素是否在播放動畫。
2.事件是否落在子元素的區(qū)域內(nèi)屎媳。
View的繪制流程
三大流程通過ViewRoot(對應(yīng)ViewRootImpl夺溢,是DecorView和WindowManger的紐帶)完成的,是從ViewRoot的performTraversals(是否需要重新計算measure烛谊、是否重新安置是否位置风响、是否重新繪制)開始的,依次執(zhí)行
- performMeasure(measure----onMeasure)
- performLayout(layout----onLayout)
- performDraw(draw----onDraw)丹禀。
measure:
用來測量view的寬高状勤。
獲取寬:getMeasureWidth
獲取高:getMeasureHeight
在大多數(shù)情況下這是View的最終寬高。
MeasureSpec:
32位的int值双泪,前兩位SpecMode(測量模型)持搜,后30位SpecSize(測量模型下的規(guī)格大小)。
父容器影響View的MeasureSpec創(chuàng)建焙矛,在測量過程中葫盼,系統(tǒng)會將View的LayoutParams根據(jù)父容器施加的規(guī)則轉(zhuǎn)換成對應(yīng)的MeasureSpec,然后再根據(jù)這個measureSpec來測量出View的寬高村斟。
SpecMode類型:
1.UNSPECIFIED:父容器不對子View做任何限制贫导,要多大給多大抛猫。
2.EXACTLY:父容器已經(jīng)檢測出View所需要的精確大小,這種模式下孩灯,SpecSize就是View的最終大小闺金。對應(yīng)的就是LayoutParams中的match_partent和具體值。
3.AT_MOST:父容器指定了一個固定大小(SpecSize),子View的寬高不能超過指定的大小峰档,具體情況看不同View的實現(xiàn)败匹。對應(yīng)的是LayoutParams中的wrap_content
- 普通View的MeasureSpec由父容器的MeasureSpce和自身的LayoutParams決定的,還有自身的padding和margin讥巡。
- DecorView的MeasureSpec由窗口的尺寸和自身的layoutParams決定掀亩。
MeasureSpec確定以后onMeasure()就可以測量View的寬高。
view的measure過程:
View的測量過程通過measure方法完成尚卫,measure會調(diào)用OnMeasure來完成归榕,通過setMeasuredDimension()設(shè)置View寬高的測量值。
viewGroup的measure過程:
除了完成自己的measure過程以外吱涉,還會遍歷調(diào)用所有子元素measure方法刹泄,各個子元素去遞歸執(zhí)行這個過程。和View不同的是ViewGroup是一個抽象類
在Activity的onCreater怎爵、onStart特石、onResume中均無法獲得View的寬高?
是因為View的measure過程和Activity生命周期不是同步執(zhí)行的鳖链。
解決方案:
1.View初始化完成后會調(diào)用onWindowFocusChanged()姆蘸,但是該方法會被調(diào)用很多次,當(dāng)Activity得到或者失去焦點都會被調(diào)用芙委。
2.view.Post(new Runnable())逞敷,將一個runnable投遞到消息隊列尾部,等looper調(diào)用的時候View也完成了初始化灌侣。
3.ViewTreeObserver()當(dāng)view樹放生改變也是獲取的好時候推捐,但是同樣會有多次,因為只要View 樹改變就會調(diào)用侧啼。
layout:
用來確定View在父容器中的放置位置及四個定點的位置以及實際View的寬高牛柒。
獲取定點:getTop、getBottom痊乾、getLeft皮壁、getRight。
獲取寬:getWidth
獲取高:getHight
在這里獲取的寬高是View的最終寬高哪审。
ViewGroup如何擺放自己的子元素蛾魄?
layout方法通過setFrame會確定View的四個定點位置,完成View的擺放,接著會調(diào)用OnLayout方法會確定所有子元素的位置滴须,View和ViewGroup都沒有真正實現(xiàn)onLayout缴川。遍歷所有子元素調(diào)用setChildFrame,而setChildFrame只是調(diào)用了子元素的layout方法描馅,進而調(diào)用setFrame確定位置,經(jīng)過層層遍歷完成View樹的擺放而线。
為什么說measure中測量的值不是View的最終大忻邸?
在View的默認(rèn)實現(xiàn)中膀篮,測量寬高和最終寬高是一致的嘹狞。但在某些特定環(huán)境下:1.重寫layout方法,對定點做更改誓竿,雖然沒有什么實際意義但是確實導(dǎo)致測量值和最終值不一樣磅网。2.在某些measure需要重復(fù)測量的情況下,在前幾次的過程中和最終值會有誤差存在筷屡。
draw:
負(fù)責(zé)將View繪制在屏幕上涧偷,只有Draw方法完成后才能呈現(xiàn)在屏幕上。
繪制流程:
1.繪制背景background.draw(canvas)
2.繪制自己onDraw
3.繪制子元素(dispatchDraw)
4.繪制裝飾
View的刷新機制:
requestLayout:如果一個view的大小毙死、形狀和位置發(fā)生變化燎潮,就需要對這個view重新進行測量和布局,但不會進行繪制扼倘。
子View調(diào)用requestLayout方法确封,會標(biāo)記當(dāng)前View及父容器,同時逐層向上提交再菊,直到ViewRootImpl處理該事件爪喘,ViewRootImpl會調(diào)用PerformTraversals開始三大流程的工作,從measure開始纠拔,對于每一個含有標(biāo)記位的view及其子View都會進行測量秉剑、布局、繪制绿语。invalidate:該方法的調(diào)用會引起View樹的重繪秃症,不會引起View重新測量和布局,并且只會重繪調(diào)用者本身吕粹。常用于內(nèi)部調(diào)用(比如 setVisiblity())或者需要刷新界面的時候,需要在主線程(即UI線程)中調(diào)用該方法种柑。
postInvalidate:和invalidate區(qū)別在于,在非UI線程調(diào)用匹耕,如果要在子線程中調(diào)用invalidate,需要配合Handler聚请。
View的加載流程:
view隨著Activity的創(chuàng)建而加載,startActivity()啟動一個Activity時,在Activity的handleLaunchActivity()中調(diào)用Activity的OnCreate()驶赏,在onCreate()中調(diào)用setContentView()來創(chuàng)建DecorView炸卑,并將layout加載道根布局中,當(dāng)進入handleResumeActivity()時煤傍,Activity的onResume()方法會被調(diào)用盖文,然后WindowManager會將根布局設(shè)置給ViewRootImpl,這樣根布局就被加載到Window中了,此時界面還要通過View的measure蚯姆,layout五续,draw才能完成View的工作流程。
View的繪制是有ViewRoot來負(fù)責(zé)的龄恋,每一個DecorView都有一個與之關(guān)聯(lián)的ViewRoot疙驾,這種關(guān)聯(lián)由WindowManger來維護,將根布局和ViewRoot關(guān)聯(lián)之后郭毕,ViewRootImpl的requedtLayout會被調(diào)用完成初步布局它碎,通過sucheduleTraversals()向主線程發(fā)送遍歷請求,最終調(diào)用ViewRootImpl的PerformTraversals()完成view的measure显押、layout扳肛、draw。
自定義View的分類乘碑?
- 繼承View:需要自己支持wap_content和padding;
- 繼承ViewGroup:需要自行處理ViewGroup的測量和布局敞峭,并同時處理自元素的測量和布局。
- 繼承特定的View:一般用來對一些原生的view進行擴展蝉仇,比如不希望ViewPager左右滑動旋讹,就可以繼承ViewPager然后重寫ScrollTo()禁止他滑動。
- 繼承特定的ViewGroup
為什么要在繼承View后支持warp_content?
- 如果直接繼承View或者ViewGroup需要自在OnMeasur()中設(shè)置warp_content時自身的大小轿衔,否則在使用過程中設(shè)置warp_content是不起作用的沉迹,效果和設(shè)置match_parent是一樣的。因為在View中使用warp_content他的specMode是AT_MOST害驹,這種模式下View的大小就是specSize鞭呕,也就是父容器中現(xiàn)在剩余的大小,在這種模式下match_parent也是如此宛官。
自定義View需要注意些什么葫松?
- 盡量不要在View中使用Handler,因為View內(nèi)部本身提供了Post系列方法底洗。
- 在合適的時候關(guān)閉線程和動畫腋么,否則會造成內(nèi)存泄漏。比如OnDetachedFromWindow()方法亥揖,該方法會在包含該View的Acticity退出或者該View被Remove()的時候會調(diào)用珊擂。對應(yīng)的方法是OnAttachedToWindow();
- View帶有滑動嵌套情景時圣勒,需要處理好滑動沖突。
- 不要再onDraw()中創(chuàng)建對象摧扇,onDraw()會被頻繁調(diào)用圣贸,導(dǎo)致Gc頻頻回收,導(dǎo)致內(nèi)存抖動扛稽。在view的構(gòu)造函數(shù)中進行創(chuàng)建對象吁峻。
- 降低View的刷新頻率,盡可能減少不必要的invalidate()調(diào)用在张,盡量調(diào)用帶參的invalidate()锡搜,無參是會刷新整個View,帶參則是制定性局部刷新瞧掺。
- 在Activity盡可能保存自己的狀態(tài)和屬性。
View的滑動沖突如何產(chǎn)生凡傅?
界面中內(nèi)外兩層同時可以滑動辟狈,就會產(chǎn)生滑動沖突。
- 外部和內(nèi)部滑動方向不一致
- 外部和內(nèi)部滑動方向一致
- 多層多個方向滑動
如何解決View滑動沖突夏跷?
- 通過判定滑動方向為水平方向還是垂直方向來決定由誰來攔截事件哼转。
如何判定滑動方向?
1.依據(jù)滑動路徑和水平方向的夾角
2.依據(jù)水平方向和垂直方向上的距離差來判斷
3.依據(jù)水平和垂直方向的速度差來做判斷
如果在水平滑動停止之前用戶迅速垂直滑動怎么辦槽华?
這個時候就會導(dǎo)致界面水平方向無法滑動到終點壹蔓。避免這種狀態(tài)的發(fā)生時,當(dāng)水平方向正在滑動時猫态,下個序列的點擊事件還交給父容器處理佣蓉。