查看: 319|回复: 0

[Java学习] Android布局优化:ViewStub标签实现延迟加载(源码解析原理)

发表于 2017-8-8 08:00:03
1.ViewStub好处

ViewStub is a lightweight view with no dimension that doesn’t draw anything or participate in the layout. it's an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime.

  1. Android官方对ViewStub的解析:1.ViewStub一个不可见2.大小为0的试图. (下面会分析这两点实现)
  2. ViewStub好处:显示优酷视频加载评论列表的ListView,当没有数据或者网络加载失败时,如果inflate空列表的ListView会占用资源;当有数据时,才会inflate列表的ListView,延迟加载了布局.
复制代码
2.ViewStub使用步骤
  1. <ViewStub
  2. android:id="@+id/list_stub"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:layout="@layout/stub_list"
  6. android:inflatedId="@+id/stub_list"/>
  7. stub_list.xml文件
  8. <ListView
  9. xmlns:android="http://schemas.android.com/apk/res/android"
  10. android:id="@+id/list"
  11. android:layout_width="match_parent"
  12. android:layout_height="match_parent"/>
复制代码
每一个ViewStub必须有android:layout属性,其中android:inflatedId的值就是被inflate的View的ID. findViewById(R.id.list_stub)).setVisibility(View.VISIBLE);
// or
ListView listView = (ListView)((ViewStub) findViewById(R.id.list_stub)).inflate();

可以通过这两种方式inflate布局,第二种方式inflate布局不需要findViewById()找ListView.查看源码发现,可以通过设置setOnInflateListener()回调监听获取inflate的View.

3.ViewStub源码分析
  1. public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  2. super(context);
  3. final TypedArray a = context.obtainStyledAttributes(attrs,
  4. R.styleable.ViewStub, defStyleAttr, defStyleRes);
  5. // 解析android:inflatedId属性,其值是被inflate的View的ID
  6. mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
  7. // 解析android:layout属性,其值是被inflate的View布局
  8. mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
  9. // 解析android:id属性,可以通过findViewByID找到该ViewStub
  10. mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
  11. a.recycle();
  12. // 最重要的两点
  13. setVisibility(GONE); // 设置不可见
  14. setWillNotDraw(true); // 本View不会调用onDraw()方法绘制内容
  15. }
复制代码
  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. // 设置试图大小为0
  4. setMeasuredDimension(0, 0);
  5. }
复制代码
  1. public void setVisibility(int visibility) {
  2. if (mInflatedViewRef != null) {
  3. // 已经inflate()已经调用,直接获取该inflate的View控制可见性
  4. View view = mInflatedViewRef.get();
  5. if (view != null) {
  6. view.setVisibility(visibility);
  7. } else {
  8. throw new IllegalStateException("setVisibility called on un-referenced view");
  9. }
  10. } else {
  11. // 如果没有调用过inflate()方法,并且设置了试图可见,就会调用inflate()加载布局
  12. super.setVisibility(visibility);
  13. if (visibility == VISIBLE || visibility == INVISIBLE) {
  14. inflate();
  15. }
  16. }
  17. }
复制代码
  1. public View inflate() {
  2. final ViewParent viewParent = getParent();
  3. if (viewParent != null && viewParent instanceof ViewGroup) {
  4. if (mLayoutResource != 0) {
  5. final ViewGroup parent = (ViewGroup) viewParent;
  6. final LayoutInflater factory;
  7. if (mInflater != null) {
  8. factory = mInflater;
  9. } else {
  10. factory = LayoutInflater.from(mContext);
  11. }
  12. // 加载真正的去加载布局试图
  13. final View view = factory.inflate(mLayoutResource, parent,
  14. false);
  15. // 设置mInflatedId给inflate的View
  16. if (mInflatedId != NO_ID) {
  17. view.setId(mInflatedId);
  18. }
  19. // 获取ViewStub在视图中的位置
  20. final int index = parent.indexOfChild(this);
  21. // 从视图移除ViewStub
  22. parent.removeViewInLayout(this);
  23. final ViewGroup.LayoutParams layoutParams = getLayoutParams();
  24. // 将inflate的试图加载父布局中,位置是被移除的ViewStub位置(也就是该ViewStub被替换成layoutView)
  25. if (layoutParams != null) {
  26. parent.addView(view, index, layoutParams);
  27. } else {
  28. parent.addView(view, index);
  29. }
  30. mInflatedViewRef = new WeakReference<View>(view);
  31. // 试图加载完成回调
  32. if (mInflateListener != null) {
  33. mInflateListener.onInflate(this, view);
  34. }
  35. return view;
  36. } else {
  37. throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
  38. }
  39. } else {
  40. throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
  41. }
复制代码

}

  1. // 设置回调监听
  2. public void setOnInflateListener(OnInflateListener inflateListener) {
  3. mInflateListener = inflateListener;
  4. }
  5. public static interface OnInflateListener {
  6. // inflated:被inflate的View
  7. void onInflate(ViewStub stub, View inflated);
  8. }
复制代码
4.ViewStub使用中注意事项 一旦调用setVisibility(View.VISIBLE)或者inflate()方法之后,该ViewStub将会从试图中被移除(此时调用findViewById()是找不到该ViewStub对象). 如果指定了mInflatedId , 被inflate的layoutView的id就是mInflatedId. 被inflate的layoutView的layoutParams与ViewStub的layoutParams相同.

以上三点注意的事项可以从源码中得知:

  1. //注意事项第二点
  2. if (mInflatedId != NO_ID) {
  3. view.setId(mInflatedId);
  4. }
  5. //注意事项第一点
  6. final int index = parent.indexOfChild(this);
  7. parent.removeViewInLayout(this);
  8. final ViewGroup.LayoutParams layoutParams = getLayoutParams();
  9. if (layoutParams != null) {
  10. //注意事项第三点
  11. parent.addView(view, index, layoutParams);
  12. } else {
  13. parent.addView(view, index);
  14. }
复制代码


回复

使用道具 举报