Android自定义View和Canvas绘图解析.docx
- 文档编号:7308526
- 上传时间:2023-01-22
- 格式:DOCX
- 页数:47
- 大小:33.79KB
Android自定义View和Canvas绘图解析.docx
《Android自定义View和Canvas绘图解析.docx》由会员分享,可在线阅读,更多相关《Android自定义View和Canvas绘图解析.docx(47页珍藏版)》请在冰豆网上搜索。
Android自定义View和Canvas绘图解析
Android自定义View和Canvas绘图解析
自定义view的流程分为measure、layout、draw三个主要步骤,今天我们通过源码来分下下measure的过程
我们从顶级view开始,顶级view即DecorView,view的事件都是先经过这个DecorView,接下来我们来看看这个DecorView的MeasureSpec的创建过程:
ViewRoot对应ViewRootImpl类,是连接WindowManager和DecorView的纽带,进入ViewRootImpl中,查看measureHierarchy方法,有如下代码:
finalDisplayMetricspackageMetrics=res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth,mTmpValue,true);
intbaseSize=0;
if(mTmpValue.type==TypedValue.TYPE_DIMENSION){
baseSize=(int)mTmpValue.getDimension(packageMetrics);
}
if(DEBUG_DIALOG)Log.v(mTag,"Window"+mView+":
baseSize="+baseSize
+",desiredWindowWidth="+desiredWindowWidth);
if(baseSize!
=0&&desiredWindowWidth>baseSize){
childWidthMeasureSpec=getRootMeasureSpec(baseSize,lp.width);
childHeightMeasureSpec=getRootMeasureSpec(desiredWindowHeight,lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
这里只是截选一部分的源码,我们看到这个baseSize,其实就是屏幕的尺寸大小,获取宽的MeasureSpc的方法:
childWidthMeasureSpec=getRootMeasureSpec(baseSize,lp.width);
这里传入的参数是屏幕尺寸以及DecorView自身的大小,接着我们来看getRootMeasureSpec方法:
privatestaticintgetRootMeasureSpec(intwindowSize,introotDimension){
intmeasureSpec;
switch(rootDimension){
caseViewGroup.LayoutParams.MATCH_PARENT:
//Windowcan'tresize.ForcerootviewtobewindowSize.
measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
break;
caseViewGroup.LayoutParams.WRAP_CONTENT:
//Windowcanresize.Setmaxsizeforrootview.
measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
break;
default:
//Windowwantstobeanexactsize.Forcerootviewtobethatsize.
measureSpec=MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
break;
}
returnmeasureSpec;
}
就是这个方法确定了DecorView的MeasureSpec,这里分了三种情况,
1.如果传入的view大小为math_parent,那么这个view的mode为EXACTLY,大小为屏幕的尺寸.
2.如果传入的view大小为wrap_content,那么这个view的mode为AT_MOST,大小为屏幕的尺寸.
3.如果传入的view大小为一个具体的值,那么这个view的mode为EXACTLY,大小为view本身大小。
以上就是DecorView的MeaureSpec的整个创建的过程了。
看了顶级view之后我们来看普通的view,普通的view的measure过程是由viewgroup传递过来的,接着我们来看看viewgroup的measureChildWithMargins方法:
protectedvoidmeasureChildWithMargins(Viewchild,
intparentWidthMeasureSpec,intwidthUsed,
intparentHeightMeasureSpec,intheightUsed){
finalMarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams();
finalintchildWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft+mPaddingRight+lp.leftMargin+lp.rightMargin
+widthUsed,lp.width);
finalintchildHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop+mPaddingBottom+lp.topMargin+lp.bottomMargin
+heightUsed,lp.height);
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
这个方法获得了子view的MeasureSpec,并且将其传入子view的measure方法中,这里重点来看下viewgroup是如何创建子view的MeasuerSpec的。
来看getChildMeasureSpec方法内部的实现:
publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension){
intspecMode=MeasureSpec.getMode(spec);
intspecSize=MeasureSpec.getSize(spec);
intsize=Math.max(0,specSize-padding);
intresultSize=0;
intresultMode=0;
switch(specMode){
//Parenthasimposedanexactsizeonus
caseMeasureSpec.EXACTLY:
if(childDimension>=0){
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize.Sobeit.
resultSize=size;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize.Itcan'tbe
//biggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}
break;
//Parenthasimposedamaximumsizeonus
caseMeasureSpec.AT_MOST:
if(childDimension>=0){
//Childwantsaspecificsize...sobeit
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize,butoursizeisnotfixed.
//Constrainchildtonotbebiggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize.Itcan'tbe
//biggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}
break;
//Parentaskedtoseehowbigwewanttobe
caseMeasureSpec.UNSPECIFIED:
if(childDimension>=0){
//Childwantsaspecificsize...lethimhaveit
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize...findouthowbigitshould
//be
resultSize=View.sUseZeroUnspecifiedMeasureSpec?
0:
size;
resultMode=MeasureSpec.UNSPECIFIED;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize....findouthow
//bigitshouldbe
resultSize=View.sUseZeroUnspecifiedMeasureSpec?
0:
size;
resultMode=MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspectionResourceType
returnMeasureSpec.makeMeasureSpec(resultSize,resultMode);
}
这个方法很长,但是我们只需要注意到AT_MOST跟EXACTLY这两种情况就行,稍微分析下这个过程:
首先要理解这个方法的三个参数,第一个是父view的MeasureSpec,第二个是父view已占用的大小,第三个是view的LayoutParams的大小,如果不理解可以看看ViewGroup的MeasureChildWithMargins方法中的调用:
finalintchildWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft+mPaddingRight+lp.leftMargin+lp.rightMargin
+widthUsed,lp.width);
第二个参数很长,mPaddingLeft+mPaddingRight+lp.leftMargin+lp.rightMargin+withUsed,这些所有的值都有一个共同特点,就是这些位置是不能摆放任何view的,即父view已经占用的地盘,现在是不是对参数更加理解了呢。
接着我们回到getChildMeasureSpec方法中继续看看viewGroup到底是怎么创建view的MeasureSpec的。
第一步:
根据参数一,即传入的父view的MeasureSpec获得父view的Mode和Size。
这里的第三行代码:
intsize=Math.max(0,specSize-padding);
这个size表示取0与父容器中可占用的位置的最大值,可以直接理解为父view的大小。
第二步:
根据父view的Mode分情况处理,到这一步我们应该就清楚为什么说view的大小是由父view的MeasureSpec与本身LayoutParmas大小共同决定的吧。
这里我们依然只看AT_MOST跟EXACTLY两种情况,
switch(specMode){
//Parenthasimposedanexactsizeonus
caseMeasureSpec.EXACTLY:
if(childDimension>=0){
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize.Sobeit.
resultSize=size;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize.Itcan'tbe
//biggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}
break;
//Parenthasimposedamaximumsizeonus
caseMeasureSpec.AT_MOST:
if(childDimension>=0){
//Childwantsaspecificsize...sobeit
resultSize=childDimension;
resultMode=MeasureSpec.EXACTLY;
}elseif(childDimension==LayoutParams.MATCH_PARENT){
//Childwantstobeoursize,butoursizeisnotfixed.
//Constrainchildtonotbebiggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}elseif(childDimension==LayoutParams.WRAP_CONTENT){
//Childwantstodetermineitsownsize.Itcan'tbe
//biggerthanus.
resultSize=size;
resultMode=MeasureSpec.AT_MOST;
}
break;
这里有个比较难以理解的值就是childDimension>0,这个其实就表示view的大小是一个具体的值比如100dp,因为view的match_parent和wrap_content在系统内部定义的都是负数,一个是-1.一个是-2,所以判断childDimension>0即,view的大小为一个具体的值。
接着就比较好理解了,我们来稍微总结下:
无论父view是match_parent还是wrap_content,只要view是一个具体的值,view的Mode永远都是EXACTLY,大小均是view本身定义的大小。
父view模式如果是EXACTLY,--->子view如果是mathch_parent,那么子view的大小是父view的大小,模式也跟父view一样为EXACTLY.子view如果是wrap_content,大小还是父view的大小,模式为AT_MOST
父view模式如果是AT_MOST,--->子view如果是math_parent,那么子view大小为父view大小,模式与父view一样都是AT_MOST,子view如果是wrap_content,子view大小为父view大小,模式为AT_MOST
上面说的有点绕,但其实我们只需要记住一点,无论上面那种情况,子view在wrap_content下,大小都是父view的大小,到这里我们是不是就能理解为什么在自定义view的过程中如果不重写onMeasure,wrap_content是和match_parent是一个效果了吧。
以上过程是viewGroup中创建子view的MeasureSpec的过程,有了这个MeasureSpec,测量子view大小就很简单了,我们可以看到在ViewGroup获取到子view的MeasureSpec之后,传入到子view的measure方法中:
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
进入到view的measure方法,
不知不觉我们已经从viewgroup进入到了view的测量过程,
这里是不是突然意识到,ViewGroup根本没有测绘自己本身啊,只是获取到子view的MeasureSpec然后传入子view的measure方法里去,这是因为ViewGroup是个抽象类,本身并没有定义测量的过程,ViewGroup的onMeasure需要各个子类去实现,比如LinearLayout、RelativeLayout等等,并且每个子类的测量过程都不一样,这个我们后面会讲,现在我们还是接着看view的Measure过程。
上面说到viewgroup将创建的子view的MeasureSpec传入到了view的Measure方法中,那么我们就来看看View的Measure方法:
publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec){
booleanoptical=isLayoutModeOptical(this);
if(optical!
=isLayoutModeOptical(mParent)){
Insetsinsets=getOpticalInsets();
intoWidth=insets.left+insets.right;
intoHeight=insets.top+insets.bottom;
widthMeasureSpec=MeasureSpec.adjust(widthMeasureSpec,optical?
-oWidth:
oWidth);
heightMeasureSpec=MeasureSpec.adjust(heightMeasureSpec,optical?
-oHeight:
oHeight);
}
//Suppresssignextensionforthelowbytes
longkey=(long)widthMeasureSpec<<32|(long)heightMeasureSpec&0xffffffffL;
if(mMeasureCache==null)mMeasureCache=newLongSparseLongArray
(2);
finalbooleanforceLayout=(mPrivateFlags&PFLAG_FORCE_LAYOUT)==PFLAG_FORCE_LAYOUT;
//OptimizelayoutbyavoidinganextraEXACTLYpasswhentheviewis
//alreadymeasuredasthecorrectsize.InAPI23andbelow,this
//extrapassisrequiredtomakeLinearLayoutre-distributeweight.
finalbooleanspecChanged=widthMeasureSpec!
=mOldWidthMeasureSpec
||heightMeasureSpec!
=mOldHeightMeasureSpec;
finalbooleanisSpecExactly=MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.EXACTLY
&&MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.EXACTLY;
finalbooleanmatchesSpecSize=getMeasuredWidth()==MeasureSpec.getSize(widthMeasureSpec)
&&getMeasuredHeight()==Measur
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Android 自定义 View Canvas 绘图 解析
![提示](https://static.bdocx.com/images/bang_tan.gif)