Viewstub 以及 view.setVisible(GONE/VISIBLE) 的实现原理
前言
最近在面試的時候被面試官問到ViewStub內部是如何使用占位的時候,我是一臉懵逼,說實話我之前對UI上的一些控件內部的代碼看的非常少,所以一時答不上來,這個東西其實并不說有多難,而是我們平時在開發的過程中只是簡單的去調用了一下但是并沒有深入的去了解其內部的原理。其實面試的過程就是我們一個查漏補缺的過程中,讓我們的各個知識面都更加的能提高,同時做到知其然更知其所以然。
gone和visible
一個view被設置了visibility=gone,在顯示的時候它會不會被繪制? - 知乎
1、invisible
view設置為invisible時,view在layout布局文件中會占用位置,但是view為不可見,該view還是會創建對象,會被初始化,會占用資源。
2、gone
view設置gone時,view在layout布局文件中不占用位置,但是該view還是會創建對象,會被初始化,會占用資源。GONE需要重新的布局和通知上級View去刷新,有緩存還要清空緩存
3、viewstub
viewstub是一個輕量級的view,它不可見,不用占用資源,只有設置viewstub為visible或者調用其inflater()方法時,其對應的布局文件才會被初始化。但是viewstub的引用對象需要是一個布局layout文件,如果要是單個的view的話,viewstub就不能滿足要求,就需要調用view的gone或者invisible屬性了。
https://www.zhihu.com/question/54416902/answer/476979353
GONE真的隱藏;
INVISIBLE不可見但是預留了View的位置;
/* Check if the GONE bit has changed */if ((changed & GONE) != 0) {needGlobalAttributesUpdate(false);requestLayout();//異同點1111111if (((mViewFlags & VISIBILITY_MASK) == GONE)) {if (hasFocus()) clearFocus();clearAccessibilityFocus();destroyDrawingCache();//異同點2222222if (mParent instanceof View) {// GONE views noop invalidation, so invalidate the parent((View) mParent).invalidate(true);}// Mark the view drawn to ensure that it gets invalidated properly the next// time it is visible and gets invalidatedmPrivateFlags |= PFLAG_DRAWN;}if (mAttachInfo != null) {mAttachInfo.mViewVisibilityChanged = true;}}/* Check if the VISIBLE bit has changed */if ((changed & INVISIBLE) != 0) {needGlobalAttributesUpdate(false);/** If this view is becoming invisible, set the DRAWN flag so that* the next invalidate() will not be skipped.*/mPrivateFlags |= PFLAG_DRAWN;if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) {// root view becoming invisible shouldn't clear focus and accessibility focusif (getRootView() != this) {if (hasFocus()) clearFocus();clearAccessibilityFocus();}}if (mAttachInfo != null) {mAttachInfo.mViewVisibilityChanged = true;}}如果在GONE和INVISIBLE兩者都可以完成你的效果,那么你應該選擇INVISIBLE。因為從源碼中來看GONE需要重新的布局和通知上級View去刷新,有緩存還要清空緩存;從視圖變更開銷的來說INVISIBLE要更加的劃算一些,如果你的View不是十分占用資源的情況!!!
android中View的GONE和INVISIBLE的原理 - soft.push("zzq") - 博客園
ViewStub介紹
一些布局控件在開始時并不需要顯示,在程序啟動后再根據業務邏輯進行顯示,通常的做法是在 xml中將其定義為不可見,然后在代碼中通過setVisibility()更新其可見性,但是這樣做會對程序性能產生不利影響,因為雖然該控件的初始狀態是不可見,但仍然會在程序啟動時進行創建和繪制,增加了程序的啟動時間。正是由于這種情況的存在,Android系統提供了ViewStub框架,能夠很容易實現“懶加載”以提升程序性能,本文從“使用方法”和“實現原理”兩個方面對其進行講解,希望能對大家有所幫助。
平時我們在開發Android的界面的時候,如果遇到不需要顯示的控件或者是布局的時候挺通常我們都會將其設置為View.Gone或者是View.INVISIBLE來達到我們想要的目的的,這樣做的優點就是邏輯簡單而且控制起來比較靈活,但是其缺點就是耗資源,其實在內部Xml解析的時候同樣也會將其解析并且實例化、設置屬性的,同樣還是會消耗系統資源的。這個時候ViewStub就閃亮登場了,為了更好的方便理解,首先我們看看其大概用法,同時分析源代碼以后再來總結其結論
用法
首先我們在布局文件里面使用兩個ViewStub,然后設置其 id 和layout 布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><Buttonandroid:id="@+id/display"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="顯示/隱藏"android:textColor="@color/colorAccent" /><ViewStubandroid:id="@+id/style_1"android:layout_width="200dip"android:layout_height="200dip"android:layout="@layout/layout_content_first" /><ViewStubandroid:id="@+id/style_2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout="@layout/layout_content_second" /></LinearLayout> public class MainActivity extends AppCompatActivity {private ViewStub mViewStub1;private ViewStub mViewStub2;private View mViewStubContentView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//首先根據 id 獲取 ViewStubmViewStub1 = findViewById(R.id.style_1);mViewStub2 = findViewById(R.id.style_2);//同時在我們需要的時候,初始化 ViewStub 包裹的布局,其實ViewStub的延遲加載就是這么個原理的mViewStubContentView = mViewStub1.inflate();//同時獲取 ViewStub的 LayoutParams 參數ViewGroup.LayoutParams params = mViewStub1.getLayoutParams();Log.i("LOH", params.width + "...height..." + params.height);//我們使用display按鈕來控制 ViewStub加載出來以后的view的顯示與隱藏findViewById(R.id.display).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if(mViewStubContentView.getVisibility() == View.VISIBLE) {mViewStubContentView.setVisibility(View.GONE);}else {mViewStubContentView.setVisibility(View.VISIBLE);}}});} }上面就是一個非常簡單的 ViewStub 的使用案例,如果我們需要顯示ViewStub 中布局文件的話,可以調用inflate 方法或者是也可以調用 ViewStub.setVisible(View.VISIBLE)就能將布局顯示出來。
代碼分析
構造方法分析
public final class ViewStub extends View {.......//一般ViewStub 在xml中引用的話,都是走這個構造方法的。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);//首先獲取 inflateId 編號mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);//然后獲取自定義屬性 inflatedId 也就是 布局文件(xml 文件)mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);//獲取 ViewStub 定義的 id 編號mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);//記住這里需要回收(因為編譯器會提示說這里內存泄漏)a.recycle();//這是是核心關鍵,首先設置 當前的View隱藏,同時設置自己不參與繪制,接著我們在 onDraw方法setVisibility(GONE);setWillNotDraw(true);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(0, 0);}@Overridepublic void draw(Canvas canvas) {} }通過上面的代碼我們可以很清楚的知道了ViewStub不參與在View的繪制中,首先是設置了View.GONE,接著調用了View方法里面的 setWillNotDraw 不參與界面繪制,而且自己的 draw方法也未任何的實現。最后將自己的寬度和高度都設置為了0。
通過setVisible 方法顯示界面
public void setVisibility(int visibility) {//首先第一次調用的 mInflatedViewRef 是為空的,所以就進入else 分支if (mInflatedViewRef != null) {View view = mInflatedViewRef.get();if (view != null) {view.setVisibility(visibility);} else {throw new IllegalStateException("setVisibility called on un-referenced view");}} else {//首先這里會直接調用 view的 setVisibility方法super.setVisibility(visibility);//接著判斷我們傳進來的 visibility的值,如果還是 GONE的話則不做處理,最后還是調用inflateif (visibility == VISIBLE || visibility == INVISIBLE) {inflate();}}}通過上面的代碼分析我們可以得出 setVisibility 最后調用的還是 inflate,所以這個方法才是關鍵的
public View inflate() {final ViewParent viewParent = getParent();//首先判斷 ViewStub是否存在父控件,同時父控件是否 ViewGroupif (viewParent != null && viewParent instanceof ViewGroup) {//同時必須設置 ViewStub的layout 信息,不能單獨設置 View。if (mLayoutResource != 0) {final ViewGroup parent = (ViewGroup) viewParent;//將 ViewStub 的layout布局文件轉化為 View,但是不添加到 parent中final View view = inflateViewNoAdd(parent);//最后在viewParen中找到ViewStub的位置,同時將inflate出來的View替換ViewStub。replaceSelfWithView(view, parent);mInflatedViewRef = new WeakReference<>(view);if (mInflateListener != null) {mInflateListener.onInflate(this, view);}return view;} else {throw new IllegalArgumentException("ViewStub must have a valid layoutResource");}} else {//ViewStub 不能單獨使用,比如是 ViewGroup的一個子View。throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");}}最后將實例化出來的view替換ViewStub在viewParent中的位置
private void replaceSelfWithView(View view, ViewGroup parent) {//首先獲取ViewStub 在 parent中的位置final int index = parent.indexOfChild(this);//同時將其從父控件中移除parent.removeViewInLayout(this);//注意這里獲取的是 ViewStub的 LayoutParams,也就是 layout出來的參數是沒效果的。只能設置ViewStub的參數才行。final ViewGroup.LayoutParams layoutParams = getLayoutParams();if (layoutParams != null) {parent.addView(view, index, layoutParams);} else {parent.addView(view, index);}}分析
這個類設計的其實非常的簡單,其代碼也就僅僅只有幾百行。通過我們上面對代碼的分析可以將其
總結如下
ViewStub 通過設置GONE 以及設置寬和高都為0,以及調用函數setWillNotDraw(true)來達到自己不繪制,不渲染在界面的效果,其實僅僅就是作為一個占著坑的意思。
ViewStub 只能調用一次 setVisibility 方法,而 setVisibility 最后調用的還是 inflate 方法。在 replaceSelfWithView 中 indexOfChild(this)代碼中,如果ViewStub被移除了以后,index則是 -1那么 addView的時候則會拋出異常的。
ViewStub layoutParams 加入到載入的android:layotu視圖上。而其根節點 layoutParams 設置無效
總結
通過上面的源代碼我們可以分析得出ViewStub的懶加載的原理,首先通過 ViewStub占住位置而且又不繪制和顯示在界面(原因看分析),接著我們在需要的時候調用ViewStub的setVisible方法和inflate方法來將頁面顯示,其原來就是將 layout 文件渲染成 view然后添加到其父控件(ViewGroup中),主要是替換之前ViewStub之前占用的位置來達到顯示界面。
?
總結
以上是生活随笔為你收集整理的Viewstub 以及 view.setVisible(GONE/VISIBLE) 的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 51Nod 1530 稳定方块
- 下一篇: Jmeter分布式测试过程中遇到的问题及