ViewStub延迟加载
在項目中,難免會遇到這種需求,在程序運行時需要動態(tài)根據(jù)條件來決定顯示哪個View或某個布局,最通常的想法就是把需要動態(tài)顯示的View都先寫在布局中,然后把它們的可見性設為View.GONE,最后在代碼中通過控制View.VISIABLE動態(tài)的更改它的可見性。這樣的做法的優(yōu)點是邏輯簡單而且控制起來比較靈活。但是它的缺點就是,耗費資源,雖然把View的初始可見View.GONE但是在Inflate布局的時候View仍然會被Inflate,也就是說仍然會創(chuàng)建對象,會被實例化,會被設置屬性。也就是說,會耗費內(nèi)存等資源。
推薦的做法是使用android.view.ViewStub,ViewStub是一個輕量級的View,使用非常簡單:
mViewStub = (ViewStub) this.findViewById(R.id.viewstub);
mViewStub.inflate();
它一個不可見的,不占布局位置,占用資源非常小的控件,相當于一個“占位控件”。使用時可以為ViewStub指定一個布局,在Inflate布局的時候,只有ViewStub會被初始化,然后當ViewStub被設置為可見的時或調(diào)用了ViewStub.inflate()的時候,ViewStub所指向的布局就會被inflate實例化,且此布局文件直接將當前ViewStub替換掉,然后ViewStub的布局屬性(layout_margin***、layout_width等)都會傳給它所指向的布局。這樣,就可以使用ViewStub在運行時動態(tài)顯示布局,節(jié)約內(nèi)存資源。
下面我們從ViewStub源碼來看下inflate()方法的實現(xiàn)原理:
public View inflate() {final ViewParent viewParent = getParent();if (viewParent != null && viewParent instanceof ViewGroup) {if (mLayoutResource != 0) {final ViewGroup parent = (ViewGroup) viewParent;final LayoutInflater factory;if (mInflater != null) {factory = mInflater;} else {factory = LayoutInflater.from(mContext);}final View view = factory.inflate(mLayoutResource, parent,false);if (mInflatedId != NO_ID) {view.setId(mInflatedId);}final int index = parent.indexOfChild(this);parent.removeViewInLayout(this);final ViewGroup.LayoutParams layoutParams = getLayoutParams();if (layoutParams != null) {parent.addView(view, index, layoutParams);} else {parent.addView(view, index);}mInflatedViewRef = new WeakReference<View>(view);if (mInflateListener != null) {mInflateListener.onInflate(this, view);}return view;} else {throw new IllegalArgumentException("ViewStub must have a valid layoutResource");}} else {throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");}}我們先從方法的入口開始看:
1、在第2行,首先是得到ViewStub它的父視圖對象。
2、然后在第4行一開始肯定是能進入判斷的,mLayoutResource就是需要inflate的布局資源,然后在第13行填充這個布局資源。
3、然后在第21行,重要的來了,parent.removeViewInLayout(this);這段代碼是什么意思呢?看方法名字就知道了,this是代表ViewStub對象,意思就是把當前ViewStub對象從父視圖中移除了。
4、然后第23~28行,就是得到ViewStub的LayoutParams布局參數(shù)對象,如果存在就把它賦給被inflate的布局對象,然后把inflate的布局對象添加到父視圖中。
5、最后返回inflate的布局對象。
好了,源碼解析完畢!!!
從上述可知,當我們第二次調(diào)用ViewStub.inflate()方法的時候,因為已經(jīng)移除了ViewStub對象,在第2、4行,得到的viewParent就為null,此時判斷時候就會走else拋出一個IllegalStateException異常:ViewStub must have a non-null ViewGroup viewParent。
需要注意的幾點:
1.ViewStub之所以常稱之為“延遲化加載”,是因為在教多數(shù)情況下,程序無需顯示ViewStub所指向的布局文件,只有在特定的某些較少條件下,此時ViewStub所指向的布局文件才需要被inflate,且此布局文件直接將當前ViewStub替換掉,具體是通過viewStub.infalte()或viewStub.setVisibility(View.VISIBLE)來完成。
2.正確把握住ViewStub的應用場景非常重要,因為使用ViewStub可以優(yōu)化布局,一般應用在當前布局或控件在用戶使用較少情況下,這樣可以提高性能,節(jié)約內(nèi)存,加快界面渲染。
3.對ViewStub的inflate操作只能進行一次,因為inflate的時候是將它指向的布局實例化并替換掉當前ViewStub本身(由此體現(xiàn)出了ViewStub“占位”性質(zhì)),一旦替換后,此時原來的布局文件中就沒有ViewStub控件了,因此,如果多次對ViewStub進行infalte,會出現(xiàn)錯誤信息:ViewStub must have a non-null ViewGroup viewParent。
4.3中所講到的ViewStub指向的布局文件解析inflate并替換掉當前ViewStub本身,并不是完全意義上的替換(與include標簽不太一樣),替換時,布局文件的layout params是以ViewStub為準,其他布局屬性是以布局文件自身為準。
5.ViewStub本身是不可見的,對ViewStub.setVisibility(int visibility)與其他View控件不一樣,我們可以從源碼角度來看一下ViewStub.setVisibility()方法的作用:
這個方法意思就是ViewStub的setVisibility()設置成View.VISIBLE或INVISIBLE如果是首次使用,都會自動inflate其指向的布局文件,并替換ViewStub本身,再次使用則是相當于對其指向的布局文件設置可見性。
好了,原理講了那么多,來看看代碼怎么實現(xiàn)吧:
使用了ViewStub的activity_main.xml:
hide_layout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#00ffff"android:orientation="vertical" ><Buttonandroid:id="@+id/hide_layout_btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="Click me" /> </LinearLayout>代碼文件:
public class MainActivity extends ActionBarActivity {private ViewStub mViewStub;private Switch mSwitch;private boolean flag = false;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mViewStub = (ViewStub) this.findViewById(R.id.viewstub);//實例化ViewStubmSwitch = (Switch) findViewById(R.id.switch1);mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {if (isChecked) {if(!flag){mViewStub.inflate();//ViewStub只能被inflate一次,會返回一個填充資源的View對象//mViewStub.setVisibility(View.VISIBLE);)flag = true;}else{mViewStub.setVisibility(View.VISIBLE);}Button mBtn = (Button) findViewById(R.id.hide_layout_btn);//ViewStub被替換的布局內(nèi)的控件mBtn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(getApplicationContext(), "Click me!",Toast.LENGTH_SHORT).show();}});} else {mViewStub.setVisibility(View.GONE);}}});} }注:使用ViewStub被替換的布局中的控件,直接findViewById即可。
減少視圖層級merge
<merge />標簽在UI的結(jié)構(gòu)優(yōu)化中起著非常重要的作用,它可以刪減多余的層級,優(yōu)化UI。<merge />多用于替換FrameLayout(因為所有的 Activity視圖的根結(jié)點都是FrameLayout,如果當前的布局根結(jié)點是Framelayout,那么可以用merge替代,減少多余的層級)或者當一個布局 包含另一個時,<merge />標簽消除視圖層次結(jié)構(gòu)中多余的視圖組。例如你的主布局文件是垂直布局,又include引入了一個垂直布局,這是 如果include布局使用的LinearLayout就沒意義了,使用的話反而減慢你的UI渲染。這時可以使用<merge />標簽進行優(yōu)化。<merge xmlns:android="http://schemas.android.com/apk/res/android"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text=""/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text=""/> </merge>總結(jié)
以上是生活随笔為你收集整理的ViewStub延迟加载的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 逆向知识第十讲,循环在汇编中的表现形式,
- 下一篇: Notadd 2.0 全新 Node.j