LinearLayout的layout_weight性能优化(PriorityLinearLayout替换LinearLayout方案)

一.layout_weight替换方案

项目中经常用到LinearLayout的layout_weight,用起来虽然方便,但是加了layout_weight会使LinearLayout measure2次。如果LinearLayout 中的子View很多的话会存在性能问题。

优化这个问题,可以用PriorityLinearLayout + measure_priority 替换LinearLayout + layout_weight用法。

一般LinearLayout  + layout_weight用法:




    
    

PriorityLinearLayout替换 + measure_priority 替换: 

只需3步

1.在project/build.gradle添加仓库

allprojects {
    repositories {
       ...
        maven { url 'https://jitpack.io' }
     
    }
}

2.在app/build.gradle增加依赖

dependencies {
    ...
    implementation 'com.github.ouxiaoyong:PriorityLinearLayout:v1.0.1'
}

3.在layout中替换 




    
    

3.1.将原布局的根LinearLayout替换成com.oxy.library.PriorityLinearLayout

3.2.将原来包含layout_weight属性的标签的layout_height(layout_width)=“match_parent”

3.3.在原来不包含layout_weight属性的标签增加一个属app:measure_priority="1"(正整数即可)

这种没使用layout_weight的方案只会measure一次,性能得到提升。

二.项目中会有以下场景

LinearLayout的layout_weight性能优化(PriorityLinearLayout替换LinearLayout方案)_第1张图片

页面中间是一个ListView,或者ScrollView之类的可以滚动的控件,下面是一个包含Add按钮的LinearLayout,每次点击Add按钮则在ListView增加一个子项,当ListView内容较少时,ListView不可以滚动,并且Add按钮紧跟其后;当ListView内容较多时(即页面中TextView+ListView+LinearLayout 大于Activity的高度),ListView可以滚动,Add按钮布局在最底部不动。换而言之就是:ListView的最大高度 maxHeight= Activity高度-TextView高度 - LinearLayout高度;

传统的实现方案

每次Add/Remove后,计算ListView的内容高度,如果小于maxHeight则设置成ListView的实际高度,如果大于maxHeight则设置成maxHeight;这里肯定要写一堆代码。

有没有一个优雅的实现方案呢?当然有!PriorityLinearLayout + measure_priority




    

    

    
        

只要将原布局的根LinearLayout替换成

com.oxy.library.PriorityLinearLayout,并且在包含Add按钮的LinearLayout增加一个属 

app:measure_priority="1"(正整数即可)

PriorityLinearLayout继承于LinearLayout,它会根据参考子View的measure_priority属性在原顺序上加以干预。measure_priority值大的优先measure,没有设置measure_priority(默认是0)值的子View还按原来的顺序measure(即addView的先后顺序)。

PriorityLinearLayout源码:https://github.com/ouxiaoyong/PriorityLinearLayout

原理分析

其实PriorityLinearLayout的原理很简单,只是根据measure_priority属性值的大小改变了Measure顺序而已。

LinearLayout的measure、layout、draw顺序都是按Add子View的先后顺序进行的。如果上面layout文件中的com.oxy.library.PriorityLinearLayout改成LinearLayout,会出现什么情况?

1.如果ListView子项较少则正常。

2.如果ListView子项较多,则底部的LinearLayout会“消失”。

我们来看LinerLayout的measure源码分析。

首先找到onMeasure方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

我们这里的mOrientation = VERTICAL,接着看

void measureVertical(int widthMeasureSpec, int heightMeasureSpec);
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
        ...
        final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
        measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);
        ...
    }
    ...
}

到这里我们就能分析出情况2(底部LinearLayout消失)出现的根本原因了usedHeight 就是前面的子View使用了多少高度,当前子View能得到的最大高度应该要减去这个usedHeight 。而ListView先measure已经将剩余高度全部用完,于是底部的LinearLayout没有可分配的高度,于是它的高度为0;

我们可以看到measure遍历子View的顺序关键是在

getVirtualChildAt()
@Nullable
View getVirtualChildAt(int index) {
     return getChildAt(index);
}

要改变measure顺序重写getVirtualChildAt的逻辑即可,可getVirtualChildAt方法的访问权限是缺省的,无法重写。

只能重写getChildAt方法了。

public View getChildAt(int index) {
    if (index < 0 || index >= mChildrenCount) {
        return null;
    }
    return mChildren[index];
}

核心逻辑分析完了,剩下的就是代码实现了。

全部代码也就100行左右

package com.example.ouxia.myapplication;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class PriorityLinearLayout extends LinearLayout {
    private List indexs = new ArrayList<>();
    private boolean isMeasureing = false;

    public PriorityLinearLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }
    public PriorityLinearLayout(Context context, @Nullable AttributeSet attrs, int         
           defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new PriorityLinearLayout.LayoutParams(getContext(), attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        refreshIndexs();
        isMeasureing = true;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        isMeasureing = false;
    }

    private void refreshIndexs() {
        indexs.clear();
        for (int i = 0; i < getChildCount(); i++) {
            ViewGroup.LayoutParams layoutParams = super.getChildAt(i).getLayoutParams();
            int priority = 0;
            if (layoutParams != null && layoutParams instanceof 
                PriorityLinearLayout.LayoutParams) {
                priority = ((LayoutParams) layoutParams).priority;
            }
            if (priority <= 0) {
                priority = 0;
            }
            indexs.add(new IndexInfo(i, priority));
        }

        Collections.sort(indexs, new Comparator() {
            @Override
            public int compare(IndexInfo o1, IndexInfo o2) {
                return o2.priority - o1.priority;
            }
        });
    }

    @Override
    public View getChildAt(int index) {
        if (isMeasureing) {
            return getChildWithPriority(index);
        } else {
            return super.getChildAt(index);
        }
    }

    private View getChildWithPriority(int index) {
        return super.getChildAt(indexs.get(index).index);
    }

    public static class IndexInfo {
        public int index;
        public int priority;

        public IndexInfo(int index, int priority) {
            this.index = index;
            this.priority = priority;
        }
    }

    public static class LayoutParams extends LinearLayout.LayoutParams {
        public int priority = 0;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            initPriority(c, attrs);
        }

        private void initPriority(Context context, AttributeSet attrs) {
            @SuppressLint("CustomViewStyleable")
            TypedArray typedArray = context.obtainStyledAttributes(attrs, 
                R.styleable.PriorityLinearLayout);
            if (typedArray != null) {
                priority = 
                typedArray.getInt(R.styleable.PriorityLinearLayout_measure_priority, 0);
                typedArray.recycle();
            }
        }
    }
}

 

你可能感兴趣的:(android)