美文网首页
性能优化-布局

性能优化-布局

作者: 慎独静思 | 来源:发表于2022-04-12 06:27 被阅读0次

    布局优化是我们开发过程中最常见和最容易改善的地方,所以我们先从它开始入手。
    开始详细分析之前,我们先说一下对布局优化的认识。

    布局优化的目的是为了尽量减少布局层级,常用的方法包括:推荐使用相对布局,约束布局等构建布局文件,ViewStub, Merge, include。

    减少布局层级的目的是什么呢?是为了减少View树的深度,相应的也就减少了测量和绘制的工作量。

    如果布局中既可以使用LinearLayout也可以使用RelativeLayout,那么就采用LinearLayout,因为RelativeLayout功能复杂,布局需要花费更多CPU。如果功能需要嵌套才能完成,还是建议使用RelativeLayout。

    <include>

    开发过程中,我们可以提取布局文件中可复用的部分独立成一个xml文件,使用include进行复用。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/app_bg"
        android:gravity="center_horizontal">
    
        <include layout="@layout/titlebar"/>
    
        <TextView android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:text="@string/hello"
                  android:padding="10dp" />
    
        ...
    
    </LinearLayout>
    

    include支持的属性不多,如果要声明其他属性,需要同时指定layout_width和layout_height,如果include标签不指定属性,则默认采用要include 布局的root view的属性。
    如果include标签指定属性,则include的属性会覆盖 root view的属性。
    比如:root view是gone的,而include指定为visible,则布局是visible。
    具体原理可参考源码LayoutInflater#parseInclude

    <merge>

    merge标签在被引入布局中时会被忽略,也就是说相比于直接include普通布局文件,会少一层。
    但是merge标签中的子控件,会被直接放在布局中,用来替换include。

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
        <Button
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="btn"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>
    </merge>
    

    其中tools:parentTag可以让你在预览时查看它在父布局中显示样式。

    ViewStub

    ViewStub是一种延时加载的技术,只有在真正需要的时候才去加载布局文件。对于复杂且很少使用的布局来说,此种方式很有效,比如网络异常布局。

    <ViewStub
        android:id="@+id/stub_import"
        android:inflatedId="@+id/panel_import"
        android:layout="@layout/progress_overlay"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />
    

    通过inflate或者setVisibility可以加载ViewStub声明的布局。

    findViewById(R.id.stub_import).setVisibility(View.VISIBLE);
    // or
    View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
    

    一旦setVisibility或inflate调用,ViewStub会被inflated布局替换,并且inflatedId就是inflated布局文件root view的ID,它会覆盖布局文件中定义的root view ID。

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(0, 0);
        }
    
        @Override
        public void draw(Canvas canvas) {
        }
    

    从源码中看ViewStub的宽高为0,且不参与绘制过程。

        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");
            }
        }
    

    ViewStub必须在ViewGroup中使用。
    ViewStub的inflate方法只能被触发一次,否则会抛异常。
    因为ViewStub#inflate从ViewGroup中移除了,导致第二次inflate时ViewGroup为空。

        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;
        }
    

    可以看到,使用parent inflate布局之后会重新set id为inflatedId。

        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);
            }
        }
    

    使用 inflated view 替换ViewStub在parent view中的位置。

        public void setVisibility(int visibility) {
            if (mInflatedViewRef != null) {
                View view = mInflatedViewRef.get();
                if (view != null) {
                    view.setVisibility(visibility);
                } else {
                    throw new IllegalStateException("setVisibility called on un-referenced view");
                }
            } else {
                super.setVisibility(visibility);
                if (visibility == VISIBLE || visibility == INVISIBLE) {
                    inflate();
                }
            }
        }
    

    setVisibility如果设置为VISIBLE或INVISIBLE实际也是调用的inflate方法。

    在DataBinding中使用

    ViewStub viewStub = binding.viewStub.getViewStub();
    viewStub.inflate();
    

    相关文章

      网友评论

          本文标题:性能优化-布局

          本文链接:https://www.haomeiwen.com/subject/jdbesrtx.html