将RecyclerView的宽高属性设置为“wrap_content”

本文探讨了RecyclerView设置为wrap_content时却表现为match_parent的问题,并提供了一种解决方案,通过重写LayoutManager的onMeasure方法来确保正确的高度测量。

前言

最近,在使用RecyclerView时遇到一个问题,就是将RecyclerView的高度设置为“wrap_content”时,控件实际测量高度为”match_parent”,为什么会出现这种问题呢?RecyclerView设置如下:

<android.support.v7.widget.RecyclerView
    android:id="@+id/sv_rv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/darker_gray">
</android.support.v7.widget.RecyclerView>
recyclerView = (RecyclerView)findViewById(R.id.sv_rv);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
recyclerView.setAdapter(new RvAdapter());

而实际显示效果如下:
这里写图片描述

正文

一般来说,出现这种问题是因为高度测量问题,所以先查看RecyclerView的onMeasure方法,如下:

@Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mAdapterUpdateDuringMeasure) {
            eatRequestLayout();
            processAdapterUpdatesAndSetAnimationFlags();

            if (mState.mRunPredictiveAnimations) {
                // TODO: try to provide a better approach.
                // When RV decides to run predictive animations, we need to measure in pre-layout
                // state so that pre-layout pass results in correct layout.
                // On the other hand, this will prevent the layout manager from resizing properly.
                mState.mInPreLayout = true;
            } else {
                // consume remaining updates to provide a consistent state with the layout pass.
                mAdapterHelper.consumeUpdatesInOnePass();
                mState.mInPreLayout = false;
            }
            mAdapterUpdateDuringMeasure = false;
            resumeRequestLayout(false);
        }

        if (mAdapter != null) {
            mState.mItemCount = mAdapter.getItemCount();
        } else {
            mState.mItemCount = 0;
        }
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
        } else {
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        }

        mState.mInPreLayout = false; // clear
    }

重要代码:

if (mLayout == null) {
    defaultOnMeasure(widthSpec, heightSpec);
    } else {
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    }
}

可以发现当RecyclerView设置LayoutManager时,RecyclerView的测量高度由所选择的LayoutManager决定。由于上文所选择的LayoutManager为LinearLayoutManager,故查看其源码的onMeasure函数,然而并没找到,那就查看其父类的onMeasure函数。

public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
    mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}

然后查看defaultOnMeasure函数,可以发现其并未对子item进行高度测量,所以导致其实测高度异常。故而要纠正此问题,需要重写LayoutManager的onMeasure函数。
如下,是我自己写的一个简单的LayoutManager子类,继承自LinearLayoutManager,仅作抛砖引玉之用:

package how.th.ridelib.RvLayoutManager;

import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;


public class RecyclerLayoutManager extends LinearLayoutManager {

    public RecyclerLayoutManager(Context context){
        super(context);
    }

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        int itemCount = state.getItemCount();
        int measuredWidth = 0;
        int measuredHeight = 0;
        int heightSize = View.MeasureSpec.getSize(heightSpec);
        for(int i = 0; i < itemCount; i ++){
            View view = recycler.getViewForPosition(i);

            if(view != null){
                RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams)view.getLayoutParams();
                int margin = layoutParams.bottomMargin + layoutParams.topMargin;
                measuredWidth = View.MeasureSpec.getSize(widthSpec);
                view.measure(widthSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
                switch (layoutParams.height){
                    case RecyclerView.LayoutParams.WRAP_CONTENT:
                        measuredHeight += (getDecoratedMeasuredHeight(view) + margin);
                        break;
                    case RecyclerView.LayoutParams.MATCH_PARENT:
                    default:
                        measuredHeight += (layoutParams.height + margin);
                        break;
                }
            }
        }
        if(measuredHeight > heightSize)
            super.onMeasure(recycler, state, widthSpec, heightSpec);
        else
            setMeasuredDimension(measuredWidth, measuredHeight);
    }
}

实际使用:

recyclerView = (RecyclerView)findViewById(R.id.sv_rv);
recyclerView.setLayoutManager(new RecyclerLayoutManager(getApplicationContext()));
recyclerView.setAdapter(new RvAdapter());

实际效果:
这里写图片描述

<?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:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:ignore="ExtraText"> <!--图片按钮--> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_server"/> <!--开关按钮--> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content"/> <!--选择按钮--> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content"/> <!--下拉列表--> <Spinner android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:backgroundTint="@color/black"/> <!--引用自定义形状--> <!-- <ImageView android:src="@drawable/sb" 这个属性的作用是让视图能够响应点击事件。 android:clickable="true" --> 这个属性的功能是将视图的点击事件和 Activity 里的方法关联起来。 android:onClick="loginWithQQ" --> 使用 CardView 实现圆角和阴影效果 每个按钮使用CardView作为容器 通过app:cardCornerRadius="12dp"设置圆角 通过app:cardElevation="4dp"添加阴影 设置app:cardBackgroundColor替代原有的backgroundTint <!--<权重分配> android:layout_weight="1" android:layout_weight="2" android:layout_weight="3" </权重分配>--> <!--垂直下滑格式--> <!--垂直下滑容器--> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0000000000" android:textSize="20sp" android:textColor="@color/black"/> <ImageView android:layout_width="match_parent" android:layout_height="180dp" android:src="@drawable/hutao" android:scaleType="centerCrop"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0000000000" android:textSize="20sp" android:textColor="@color/black"/> <ImageView android:layout_width="match_parent" android:layout_height="180dp" android:src="@drawable/hutao" android:scaleType="centerCrop"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0000000000" android:textSize="20sp" android:textColor="@color/black" /> <ImageView android:layout_width="match_parent" android:layout_height="180dp" android:src="@drawable/hutao" android:scaleType="centerCrop"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0000000000" android:textSize="20sp" android:textColor="@color/black"/> <ImageView android:layout_width="match_parent" android:layout_height="180dp" android:src="@drawable/hutao" android:scaleType="centerCrop"/> </LinearLayout> </ScrollView> <!--垂直下滑容器--> </LinearLayout>错误分析
08-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值