HeightListView - 自适应TextView高度

Posted by phantomVK on August 27, 2017

使用Adapter可能需要在一个item里包含ListView控件动态展示多个TextView。普通的ListView会出现所有的TextView只显示第一行而不是多行内容的问题。

问题的根源是ListView没有正确计算出每个TextView文字需要的高度,仅显示一行文字的高度。

重写的的类中,把计算高度的onItemsChanged()通过DataSetObserver绑定到数据对应的adapter,且重写onMeasure(int, int)。数据改变时回调DataSetObserver,新数据对应的TextView高度得到计算。

DataSetObserver在初始化ListView的时候已经创建完成,在setAdapter(ListAdapter adapter)中绑定到adapter

经过上面的处理,令HeightListViewListView用法无异。

public final class HeightListView extends ListView {

    private DataSetObserver mDataSetObserver; // The custom data set observer.

    public HeightListView(Context context) {
        this(context, null);
    }

    public HeightListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        createObserver();
    }

    public HeightListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        createObserver();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int spec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, spec);
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (adapter == null) throw new NullPointerException();

        if (getAdapter() != null) {
            getAdapter().unregisterDataSetObserver(mDataSetObserver);
        }

        adapter.registerDataSetObserver(mDataSetObserver);
        super.setAdapter(adapter);
    }

    // Creates a custom data set change observer.
    private void createObserver() {
        if (mDataSetObserver != null) return;
        mDataSetObserver = new DataSetObserver() {
            @Override
            public void onChanged() {
                super.onChanged();
                onItemsChanged();
            }
        };
    }

    // Calculates the height of all sub-items, includes divider height of each item.
    private void onItemsChanged() {
        int count = getAdapter().getCount();
        if (count == 0) return;

        int height = 0;
        for (int i = 0; i < count; i++) {
            View v = getAdapter().getView(i, null, this);
            v.measure(0, 0);
            height += v.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = getLayoutParams();
        params.height = height + getDividerHeight() * (count - 1);
        setLayoutParams(params);
    }

    @Override
    public boolean isClickable() {
        return super.isClickable();
    }

    @Override
    public boolean isEnabled() {
        return super.isEnabled();
    }
}

最后还把isClickable()isEnabled()显式调用父类方法:itemListView展示的所有subitem都是可以点击的。要subitem把点击事件传递给item处理,则需要重写或设置上述两个方法的值为false