Android ViewStub动态加载View
1.第一種使用方式:
在xml布局中指定要膨脹的布局:
<ViewStubandroid:id="@+id/statusBar"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout="@layout/base_view_status_bar" />在代碼中執行膨脹:
viewStub.inflate()2.第二種使用方式:
不在xml中指定布局,在代碼中動態指定layout布局:
viewStub.layoutResource = R.layout.xxx viewStub.inflate()3.第三種使用方式:
除了加載指定布局,我們可以通過反射加載指定的View對象:
?ViewStub通過inflate()函數膨脹一個布局,那我們看看inflate()函數的源碼:
public View inflate() {final ViewParent viewParent = getParent();if (viewParent != null && viewParent instanceof ViewGroup) {if (mLayoutResource != 0) {final ViewGroup parent = (ViewGroup) viewParent;final View view = inflateViewNoAdd(parent);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 {throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");}}其中,?重點函數為inflateViewNoAdd()和replaceSelfWithView(),我們看看這倆個方法的實現:
private View inflateViewNoAdd(ViewGroup parent) {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);}return view;}private void replaceSelfWithView(View view, ViewGroup parent) {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);}}?我們可以看到,inflateViewNoAdd()的作用是通過layoutId去加載一個view;replaceSelfWithView()是將自己從父布局中移除,然后添加View進去;至此,我們知道了我們所需要執行的代碼為:
public View inflate() {final ViewParent viewParent = getParent();final ViewGroup parent = (ViewGroup) viewParent;replaceSelfWithView(view, parent);mInflatedViewRef = new WeakReference<>(view);if (mInflateListener != null) {mInflateListener.onInflate(this, view);}}到這,事情就好辦了,我們只需要手動調用我們需要的方法即可,但ViewStub中這些方法是私有的,所以我們只能通過反射來調用了,反射代碼如下:
fun invokeInflate(view: View) {//當版本大于29時,我們可以直接通過view獲取到layoutId,則可以直接設置if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {viewStub.layoutResource = view.sourceLayoutResIdviewStub.inflate()return}//低版本則反射加載布局val viewGroup = viewStub.parent as ViewGroupval method =viewStub::class.java.getDeclaredMethod("replaceSelfWithView",View::class.java,ViewGroup::class.java)method.isAccessible = truemethod.invoke(viewStub, view, viewGroup)//對viewStub的mInflatedViewRef進行賦值,否則影響viewStub的#setVisibility()的調用val mInflatedViewRef = viewStub::class.java.getDeclaredField("mInflatedViewRef")mInflatedViewRef.isAccessible = truemInflatedViewRef[viewStub] = WeakReference(view)//通知viewStub已經膨脹,否則會影響ViewStubProxyval mInflateListener = viewStub::class.java.getDeclaredField("mInflateListener")mInflateListener.isAccessible = true(mInflateListener[viewStub] as ViewStub.OnInflateListener?)?.onInflate(viewStub,view)}?以上代碼只在Android8.0以上生效,因為ViewStub在Android5.0至Android7.0中,inflate()方法的源碼為:
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");}}我們可以看到,在inflate()中直接添加View,并沒有replaceSelfWithView()方法,所以在低版本中我們無法反射replaceSelfWithView(),因此,我們需要適配低版本,既然實現原理我們以及知道了,那只要我們自己實現一遍replaceSelfWithView()方法的內容即可:
fun invokeInflate(view: View) {//當版本大于29時,我們可以直接通過view獲取到layoutId,則可以直接設置if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {viewStub.layoutResource = view.sourceLayoutResIdviewStub.inflate()return}//低版本則反射加載布局val viewGroup = viewStub.parent as ViewGroup//不再使用反射,自己實現replaceSelfWithView()方法val index = viewGroup.indexOfChild(viewStub)viewGroup.removeViewInLayout(viewStub)viewStub.getLayoutParams()?.let { viewGroup.addView(view, index, it) }?: run { viewGroup.addView(view, index) }//對viewStub的mInflatedViewRef進行賦值,否則影響viewStub的#setVisibility()的調用val mInflatedViewRef = viewStub::class.java.getDeclaredField("mInflatedViewRef")mInflatedViewRef.isAccessible = truemInflatedViewRef[viewStub] = WeakReference(view)//通知viewStub已經膨脹,否則會影響ViewStubProxyval mInflateListener = viewStub::class.java.getDeclaredField("mInflateListener")mInflateListener.isAccessible = true(mInflateListener[viewStub] as ViewStub.OnInflateListener?)?.onInflate(viewStub,view) }至此,我們就完成了往ViewStub動態添加View的操作了,其實,一通操作下來,你會發現沒什么用處,畢竟你都有View了,不使用ViewStub,而是直接加載View進布局中即可了?
如果以上方法對你有用的話,記得點個贊噢!!!
總結
以上是生活随笔為你收集整理的Android ViewStub动态加载View的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DiskImage磁盘镜像工具下载使用手
- 下一篇: ionic拍照,从相册选择功能