做枪网站,seo单词优化,网站建设 代表联系群众,做网站码1.ViewStub
ViewStub是一个可用于性能优化的控件#xff0c;它是一个不可见的、零尺寸的View#xff0c;可以在运行时进行延迟加载一个布局文件#xff0c;从而提高显示速率。
viewstub和include比较像#xff0c;都是在一个布局文件中嵌入另外一个布局文件#xff0c;然…1.ViewStub
ViewStub是一个可用于性能优化的控件它是一个不可见的、零尺寸的View可以在运行时进行延迟加载一个布局文件从而提高显示速率。
viewstub和include比较像都是在一个布局文件中嵌入另外一个布局文件然而viewstub可以延迟加载它只会在手动指定加载的时候才会加载这个布局文件而include则会立即加载。
手动指定加载当ViewStub的setVisibility(int)方法或inflate()方法被调用时它才会加载被指定的布局并在父布局中将自己替换为加载的布局。替换后控件ViewStub会从布局树中移除。控件ViewStub的布局属性会传递给被加载的布局。
因此不是必须显示的布局就可以使用ViewStub来代替这样可以减少界面首次加载时资源消耗提升最初加载速度。
ViewStub实际中常用情景比如在无数据或者网络错误的时候需要单独显示一个布局那么这个布局就可以用ViewStub。
注意
①对ViewStub的inflate操作只能进行一次因为inflate的时候是将其指向的布局文件解析inflate并替换掉当前ViewStub本身由此体现出了ViewStub“占位符”性质一旦替换后此时原来的布局文件中就没有ViewStub控件了因此如果多次对ViewStub进行infalte会出现错误信息ViewStub must have a non-null ViewGroup viewParent。
②ViewStub指向的布局文件解析inflate并替换掉当前ViewStub本身并不是完全意义上的替换与include标签还不太一样替换时布局文件的layout params是以ViewStub为准其他布局属性是以布局文件自身为准。
2.ViewStub使用
①布局文件
在主布局中使用ViewStub标签来引入目标布局
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical TextView android:layout_widthwrap_content android:layout_heightwrap_content android:textstring/hello_world / Button android:idid/toggle android:layout_widthwrap_content android:layout_heightwrap_content android:onClickonClick android:text显示/隐藏 / ViewStub android:idid/vs android:layout_widthmatch_parent android:layout_heightmatch_parent android:inflatedIdid/inflated_id android:layoutlayout/view_stub_layout/
/LinearLayout
android:layout指定被加载替换的布局。 android:inflatedId指定被加载替换的布局的id。
其中被加载替换的布局view_stub_layout.xml
?xml version1.0 encodingutf-8?
LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical
TextView android:idid/tv android:layout_widthmatch_parent android:layout_heightwrap_content android:textvs中的tv /
/LinearLayout
②java文件
布局文件写好了接下来在程序运行时加载这个布局。
public class MainActivity extends Activity { private ViewStub stub; private boolean isShow true; private TextView tv; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); stub (ViewStub) findViewById(R.id.vs); //stub布局的加载有两种方式一种是stub.inflate();另一种是//stub.setVisibility(View.VISIBLE);findViewById View inflatedView stub.inflate(); //inflate()只能调用一次 //stub.setVisibility(View.VISIBLE); //View inflatedView this.findViewById( R.id.inflated_id); tv (TextView) inflatedView.findViewById( R.id.tv); //注意要先实例化stub然后才可以拿到tv inflatedView.setBackgroundColor( Color.BLUE); } public void onClick(View v){ switch (v.getId()) { case R.id.toggle: if (isShow) { stub.setVisibility(View.GONE); }else{ stub.setVisibility(View.VISIBLE); tv.setText(---); } isShow !isShow; break; default: break; } }
}
官方推荐加载首选方法是inflate()。调用inflate()方法后布局被加载替换同时返回布局对象。避免了使用findViewById()方法。
注意inflate()方法只能调用一次调用被移除而没有了父布局。第二次调用会抛出异常ViewStub must have a non-null ViewGroup viewParent 。
ViewStub可以设置加载监听回调成功加载后回调且只会回调一次。
viewStub.setOnInflateListener(new ViewStub.OnInflateListener() { /** * param stub ViewStub 对象 * param inflated 被加载填充的布局 */ Override public void onInflate(ViewStub stub, View inflated) { tv (TextView) inflated.findViewById(R.id.tv); tv.setText(ShowTitle); }
} 3.ViewStub源码
①构造方法
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr, defStyleRes); //获取在xml文件中定义的inflatedId属性 mInflatedId a.getResourceId( R.styleable.ViewStub_inflatedId, NO_ID); //获取到xml文件中定义的layout属性 mLayoutResource a.getResourceId( R.styleable.ViewStub_layout, 0); //获取xml文件中定义的id属性 mID a.getResourceId( R.styleable.ViewStub_id, NO_ID); a.recycle(); //设置ViewStub直接不显示。所以在xml文件中如何控制它的显示属性都是不显示的 setVisibility(GONE); // 设置ViewStub不尽兴绘制 setWillNotDraw(true);
}
②ViewStub的onMeasure和onDraw方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //设置宽和高都为0也就是控件的大小为0 setMeasuredDimension(0, 0);
}
Override
public void draw(Canvas canvas) { //不进行任何绘制
}
Override
protected void dispatchDraw(Canvas canvas) { }
③inflate()方法
public View inflate() { final ViewParent viewParent getParent(); //获取ViewStub在布局文件中的父布局 if (viewParent ! null viewParent instanceof ViewGroup) { if (mLayoutResource ! 0) { //mLayoutResource就是属性layout指定的真正要加载的布局 final ViewGroup parent (ViewGroup) viewParent; final View view inflateViewNoAdd( parent); //把真正要显示的View布局文件渲染成View对象并且给返回 replaceSelfWithView(view, parent); //将ViewStub从布局文件结构中移除并且把渲染好的View添加到ViewStub所处的位置 mInflatedViewRef new WeakReference(view); if (mInflateListener ! null) { mInflateListener.onInflate(this, view); //保存当前View对象的弱引用方便其他地方使用 } return view; //返回创建的View对象 } else { //当没有为ViewStub指定layut属性时会走这个case抛出异常 throw new IllegalArgumentException( ViewStub must have a valid layoutResource); } } else { //第一次调用ViewStub的inflate方法后会把ViewStub从布局文件结构中移除也就没有了ViewGroup。当第二次调用ViewStub的inflate方法后会走这个case抛出异常 throw new IllegalStateException( ViewStub must have a non-null ViewGroup viewParent); }
}
可以看到当viewParent为空或viewParent不是ViewGroup时就会报ViewStub must have a non-null ViewGroup viewParent错误。第一次调用inflate方法的时候不会报错肯定是进了ifif里面有一个方法replaceSelfWithView(view,parent)其中参数view就是在布局文件中给viewstub指定的layout所引用的那个布局参数parent就是getParent方法得到的也就是activity的填充布局LinearLayout。
其实inflate方法主要的就是inflateViewNoAdd和replaceSelfWithView这两个方法。其中inflateViewNoAdd方法负责获取到布局渲染器将真正需要展示的布局文件渲染成View并且给返回。replaceSelfWithView方法负责将ViewStub从布局文件结构中移除同时把渲染好的View添加到ViewStub之前所处的位置。之后把渲染好的View的弱引用给存储起来方便在setVisibility()方法中使用。
首先看inflateViewNoAdd方法
private View inflateViewNoAdd(ViewGroup parent) { final LayoutInflater factory; //布局填充器 if (mInflater ! null) { factory mInflater; } else { factory LayoutInflater.from(mContext); } // 把真正要显示的布局文件渲染成View对象 final View view factory.inflate( mLayoutResource, parent, false); // mInflatedId对应android:inflatedId如果指定了就为渲染好的View给设置进去 if (mInflatedId ! NO_ID) { view.setId(mInflatedId); } return view;
}
然后进去replaceSelfWithView方法看一下
private void replaceSelfWithView(View view, ViewGroup parent) { final int index parent.indexOfChild(this); //获取ViewStub在父布局中所处在的位置 parent.removeViewInLayout(this); //将ViewStub从父布局中移除 final ViewGroup.LayoutParams layoutParams getLayoutParams(); //获取ViewStub的布局参数 if (layoutParams ! null) { //当设置了布局参数例如 android:width50dp,height50dp)就将渲染好的View连同ViewStub的布局参数添加到ViewStub所处的位置 parent.addView(view, index, layoutParams); } else { //否则将渲染好的View添加到ViewStub所处的位置 parent.addView(view, index); }
}
首先通过parent.removeViewInLayout(this);把this也就是viewstub从父布局linearlayout中移除了。然后通过parent.addView()把view也就是layout引用的布局添加到了父布局LinearLayout中。
用layout inspector来查看一下
inflate前可以看到viewstub是灰色的 inflate后可以看到viewstub直接被移除了把引用布局直接放到view树里了。
所以第二次调用inflate方法时viewstub的parent已经为空了就会抛出此异常。
当调用textView viewStub.findViewById( R.id.tv);获取到的textview是空的而使用textView findViewById(R.id.tv);就可以直接拿到控件对象。
④setVisibility()方法
public void setVisibility(int visibility) { if (mInflatedViewRef ! null) { //mInflatedViewRef只有在inflate方法中被初始化了即当真正的布局文件被加载之后才不为空第二次就不为空 View view mInflatedViewRef.get(); //获取到当前的View if (view ! null) { view.setVisibility(visibility); //当前view不空则直接显示 } else { throw new IllegalStateException( setVisibility called on un-referenced view); } } else { //没有调用inflate的话会设置可见性第一次会为空 super.setVisibility(visibility); if (visibility VISIBLE || visibility INVISIBLE) { //如果当前设置可见性为 VISIBLE或者INVISIBLE的时候会调用inflate方法 inflate(); } }
}
首先使用mInflatedViewRef获取到view然后设置隐藏与显示mInflatedViewRef是一个view的弱引用WeakReferenceView。其实在上面的inflate方法中已经为其添加了mInflatedViewRef new WeakReference(view);这个view就是viewstub中的引用布局。
所以使用viewstub可以实现相同的显示或隐藏效果。
可以发现setVisibility方法中也调用了inflate方法因此即使没有调用inflate方法而是直接点击show按钮引用布局也可以绘制出来。
注意ViewStub本身是不可见的对ViewStub的setVisibility(..)与其他控件不一样ViewStub的setVisibility成View.VISIBLE或INVISIBLE时如果是首次使用都会自动inflate其指向的布局文件并替换ViewStub本身再次使用则是相当于对其指向的布局文件设置可见性。 注意
ViewStub支持使用 include 标签的布局。
ViewStub不支持使用 merge 标签的布局。