你需要知道的Android View的创建.docx
- 文档编号:24922189
- 上传时间:2023-06-02
- 格式:DOCX
- 页数:17
- 大小:194.40KB
你需要知道的Android View的创建.docx
《你需要知道的Android View的创建.docx》由会员分享,可在线阅读,更多相关《你需要知道的Android View的创建.docx(17页珍藏版)》请在冰豆网上搜索。
你需要知道的AndroidView的创建
你需要知道的AndroidView的创建
View的创建与绘制一向是很多人望而止步的问题。
然而我们在平常的应用开发中是最经常运用到的setContentView(),我们都会用在Activity的onCreate()的时候调用setContentView()来加载编辑好的XML布局。
但是实际上创建与绘制一个View,内部的实现方式的确比我们表面所编写的代码复杂得多,导致大家没能深入去了解View的创建与绘制。
接下来我们一步步来了解View的创建与绘制。
在研究setContentView()方法前,我们首先先看一下这图:
上图中,DecorView是个应用窗口的最顶层View。
(Decor的英文全称是Decoration,即“修饰”的意思)。
DecorView只有一个子元素是垂直LinearLayout。
在LinearLayout下有两个子布局,第一个是ViewStub,ViewStub就是ActionBar,它会根据theme判断有没使用ActionBar来决定是否引入ActionBar布局。
第二个是FrameLayout,这就是我们应用真实使用的父布局。
大家可以通过sdk工具HierarchyViewer来查看验证一下ViewTree的情况。
Window,中文解析“窗口”。
它是一个宏观的概念。
该类是一个抽象类,提供了绘制窗口的通用API,我们可以理解为它是一个载体。
接着我们看一下PhoneWindow,它是Android中Window的具体实现类。
PhoneWindow位于/frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java
PhoneWindow继承于Window类,我们可以通过实现具体抽象方法去绘制窗口,该类还包含DecorView内部类。
我们平时调用的setContentView()方法设置Activity的用户界面时,实际上是对PhoneWindow的ViewTree的设置。
我们通过一个比喻来理解他们之间的关系。
Window类相当于一幅画(抽象概念,什么画我们未知),PhoneWindow为一副齐白石先生的山水画(具体概念,我们知道了是谁的、什么性质的画),DecorView则为该山水画的具体内容(有山、有水、有树,各种界面)。
DecorView呈现在PhoneWindow上。
好了,有了这部分的认识之后,我们就开始从源码的角度去认识View的创建。
当我们自定义Activity继承Android.app.Activity时候,调用的setContentView()如下:
publicvoidsetContentView(@LayoutResintlayoutResID){
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()方法返回一个PhoneWindow对象,那就是说调用的是PhoneWindow的setContentView()方法。
源码如下:
@Override
publicvoidsetContentView(intlayoutResID){
//Note:
FEATURE_CONTENT_TRANSITIONSmaybesetintheprocessofinstallingthewindow
//decor,whenthemeattributesandthelikearecrystalized.Donotcheckthefeature
//beforethishappens.
if(mContentParent==null){
//mContentParent即为上面提到的ContentView的父容器,若为空则调用installDecor()生成
installDecor();
}elseif(!
hasFeature(FEATURE_CONTENT_TRANSITIONS)){
//mContentParent不为null,则移除decorView的所有子View
mContentParent.removeAllViews();
}
if(hasFeature(FEATURE_CONTENT_TRANSITIONS)){
finalScenenewScene=Scene.getSceneForLayout(mContentParent,layoutResID,
getContext());
transitionTo(newScene);
}else{
//一般情况会来到这里,调用mLayoutInflater.inflate()方法来填充布局
//填充布局也就是把我们设置的ContentView加入到mContentParent中
mLayoutInflater.inflate(layoutResID,mContentParent);//2
}
finalCallbackcb=getCallback();
if(cb!
=null&&!
isDestroyed()){
//调用onContentChanged()回调方法通知Activity窗口内容发生了改变
cb.onContentChanged();
}
}
首先判断了mContentParent是否为null,如果为空则执行installDecor()方法,那么这个mContentParent又是什么呢?
我们看一下它的注释。
//Thisistheviewinwhichthewindowcontentsareplaced.Itiseither
//mDecoritself,orachildofmDecorwherethecontentsgo.
privateViewGroupmContentParent;
它是一个ViewGroup类型,结合2处代码,可以得知,这个mContentParent是我们设置的布局的父布局。
梳理下:
Activity通过PhoneWindow的setContentView方法来设置布局,而设置布局之前,会先判断是否存在mContentParent,而我们设置的布局文件则是mContentParent的子元素。
接着我们看一下installDecor(),我们看一下PhoneWindow#installDecor:
privatevoidinstallDecor(){
if(mDecor==null){
mDecor=generateDecor();//1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if(!
mInvalidatePanelMenuPosted&&mInvalidatePanelMenuFeatures!
=0){
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if(mContentParent==null){
mContentParent=generateLayout(mDecor);//2
...
}
}
}
首先,会执行1处代码,调用PhoneWindow#generateDecor方法:
protectedDecorViewgenerateDecor(){
returnnewDecorView(getContext(),-1);
}
可以看出,这里实例化了DecorView,而DecorView则是PhoneWindow类的一个内部类,继承于FrameLayout,由此可知它也是一个ViewGroup。
DecorView上面我们已经对它进行过一次解析了,大家可以返回去回顾下。
接下来我们看2处PhoneWindow#generateLayout方法的代码:
protectedViewGroupgenerateLayout(DecorViewdecor){
//Applydatafromcurrenttheme.
//从主题文件中获取样式信息
//加载TitleBar方法一
TypedArraya=getWindowStyle();
...
if(a.getBoolean(R.styleable.Window_windowNoTitle,false)){
requestFeature(FEATURE_NO_TITLE);
}elseif(a.getBoolean(R.styleable.Window_windowActionBar,false)){
//Don'tallowanactionbarifthereisnotitle.
requestFeature(FEATURE_ACTION_BAR);
}
if(...){
...
}
//Inflatethewindowdecor.
//加载窗口布局
intlayoutResource;
//加载TitleBar方法二
intfeatures=getLocalFeatures();
//System.out.println("Features:
0x"+Integer.toHexString(features));
if((features&(1< =0){ layoutResource=R.layout.screen_swipe_dismiss; }elseif(...){ ... } Viewin=mLayoutInflater.inflate(layoutResource,null);//加载layoutResource decor.addView(in,newViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));//往DecorView中添加子View,即mContentParent mContentRoot=(ViewGroup)in; ViewGroupcontentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);//这里获取的就是mContentParent if(contentParent==null){ thrownewRuntimeException("Windowcouldn'tfindcontentcontainerview"); } if((features&(1< =0){ ProgressBarprogress=getCircularProgressBar(false); if(progress! =null){ progress.setIndeterminate(true); } } if((features&(1< =0){ registerSwipeCallbacks(); } //Remainingsetup--ofbackgroundandtitle--thatonlyapplies //totop-levelwindows. ... returncontentParent; } 由以上代码可以看出,该方法还是做了相当多的工作的,首先根据设置的主题样式来设置DecorView的风格,比如说有没有titlebar之类的,就是解析我们为Activity设置theme的地方,至于Theme的设置: 1.我们可以在AndroidManifest里面进行设置,为我们的Activity配置相应属性,即android: theme=”“,PhoneWindow对象调用getWindowStyle()方法获取值。 2.也可以在setContentView()前调用requestFeature,指定requestFeature()指定窗口修饰符,PhoneWindow对象调用getLocalFeature()方法获取值; 对Theme操作完,我们才对layoutResource赋值的,因此我相信有不少人都曾经遇到一个错误——“requestFeature()mustbecalledbeforeaddingcontent”。 接着通过对features和mIsFloating的判断,设置窗口的风格修饰为layoutResource进行赋值。 得到了layoutResource以后,通过LayoutInflater.inflate()方法生成View对象。 并加入到decor中。 这就是为DecorView添加子View,而这里的子View则是上面提到的mContentParent。 如果上面设置了FEATURE_NO_ACTIONBAR,那么DecorView就只有mContentParent一个子View,这也解释了mContentParent对象注释: mContentParent是DecorView本身或者是DecorView的一个子元素。 小结: DecorView是顶级View,内部有titlebar和contentParent两个子元素,而内部根据theme设置TitleBar,和选择系统中的布局文件,将布局文件通过inflate转化为view,加入到mDecor中;这些布局文件中都包含一个id为content的FrameLayout,将其引用返回给mContentParent。 了解完PhoneWindow#installDecor后我们接着PhoneWindow#setContentView(),看到那部分2处代码: mLayoutInflater.inflate(layoutResID,mContentParent);相信LayoutInflater大家跟setContentView()一样常用。 因为在一些动态加载View和BaseAdapter适配器的代码编写中我们都会用到。 我们来看一下它的代码: publicViewinflate(XmlPullParserparser,@NullableViewGrouproot){ returninflate(parser,root,root! =null); } publicViewinflate(@LayoutResintresource,@NullableViewGrouproot,booleanattachToRoot){ finalResourcesres=getContext().getResources(); if(DEBUG){ Log.d(TAG,"INFLATINGfromresource: \""+res.getResourceName(resource)+"\"(" +Integer.toHexString(resource)+")"); } finalXmlResourceParserparser=res.getLayout(resource); try{ returninflate(parser,root,attachToRoot); }ally{ parser.close(); } } LayoutInflater.inflate()将上面创建的decorView作为root的参数。 上面的代码比较简单,重点是return那行的inflate(),我们看一下里面的实现过程: publicViewinflate(XmlPullParserparser,@NullableViewGrouproot,booleanattachToRoot){ synchronized(mConstructorArgs){ Trace.traceBegin(Trace.TRACE_TAG_VIEW,"inflate"); finalContextinflaterContext=mContext; finalAttributeSetattrs=Xml.asAttributeSet(parser); ContextlastContext=(Context)mConstructorArgs[0]; mConstructorArgs[0]=inflaterContext; Viewresult=root; try{ //Lookfortherootnode. inttype; //一直读取xml文件,直到遇到开始标记 while((type=parser.next())! =XmlPullParser.START_TAG&& type! =XmlPullParser.END_DOCUMENT){ //Empty } if(type! =XmlPullParser.START_TAG){ thrownewInflateException(parser.getPositionDescription() +": Nostarttagfound! "); } finalStringname=parser.getName(); if(DEBUG){ System.out.println("**************************"); System.out.println("Creatingrootview: " +name); System.out.println("**************************"); } //单独处理 if(TAG_MERGE.equals(name)){ if(root==null||! attachToRoot){ thrownewInflateException(" +"ViewGrouprootandattachToRoot=true"); } //递归地填充布局 rInflate(parser,root,inflaterContext,attrs,false); }else{ //Tempistherootviewthatwasfoundinthexml //能在XML发现根View finalViewtemp=createViewFromTag(root,name,inflaterContext,attrs); ViewGroup.LayoutParamsparams=null; if(root! =null){ if(DEBUG){ System.out.println("Creatingparamsfromroot: "+ root); } //Createlayoutparamsthatmatchroot,ifsupplied //获取父容器的布局参数(LayoutParams) params=root.generateLayoutParams(attrs); if(! attachToRoot){ //Setthelayoutparamsfortempifwearenot //attaching.(Ifweare,weuseaddView,below) //若attachToRoot参数为false,则我们只会将父容器的布局参数设置给根View temp.setLayoutParams(params); } } if(DEBUG){ System.out.println("----->startinflatingchildren"); } //Inflateallchildrenundertempagainstitscontext. //递归加载根View的所有子View rInflateChildren(parser,temp,attrs,true); if(DEBUG){ System.out.println("----->doneinflatingchildren"); } //Wearesupposedtoattachalltheviewswefound(inttemp) //toroot.Dothatnow. //若父容器不为空且attachToRoot为true,则将父容器作为根View的父View包裹上来 if(root! =null&&attachToRoot){ root.addView(temp,params); } //Decidewhethertoreturntherootthatwaspassedinorthe //topviewndinxml. //若root为空或是attachToRoot为false,则以根View作为返回值 if(root==null||! attachToRoot){ result=temp; } } }catch(XmlPullParserExceptione){ InflateExceptionex=newInflateException(e.getMessage()); ex.initCause(e); throwex; }catch(Exceptione){ InflateExceptionex=newInflateException( parser.getPositionDescription() +": "+e.getMessage()); ex.initCause(e); throwex; }finally{ //Don'tretainstaticreferenceoncontext. mC
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 你需要知道的Android View的创建 需要 知道 Android View 创建