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用法:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="0dp"/>
    <Button
        android:text="提交"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

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中替换 

<?xml version="1.0" encoding="utf-8"?>
<com.oxy.library.PriorityLinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <Button
        android:text="提交"
        app:measure_priority="1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</com.oxy.library.PriorityLinearLayout>

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一次,性能得到提升。

二.项目中会有以下场景

页面中间是一个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

<?xml version="1.0" encoding="utf-8"?>
<com.oxy.library.PriorityLinearLayout         
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
        app:measure_priority="1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">
        <Button
            android:text="Add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Button
            android:text="Remove"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</com.oxy.library.PriorityLinearLayout>

只要将原布局的根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<IndexInfo> 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<IndexInfo>() {
            @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();
            }
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值