Горизонтальное RecyclerView с элементами с динамической высотой

Мне нужен горизонтальный RecyclerView. Однако элементы в них имеют динамическую высоту, основанную на том, что было возвращено из бэкэнд.

RecyclerView и его элементы имеют высоту "wrap_content"

Проблема в том, что первые видимые элементы в RecyclerView малы, а затем, когда пользователь прокручивается к более крупным элементам, они выглядят обрезанными.

В идеале мы хотим, чтобы RecyclerView был достаточно большим, чтобы удерживать наибольший элемент в группе (но только самый большой существующий элемент... не гипотетически самый большой элемент). RecyclerView, динамически изменяющий его высоту, также является приемлемым.

Я написал пример приложения, чтобы проиллюстрировать проблему. В этом приложении у нас есть горизонтальный список. Первые 33 элемента должны отображать 1 строку текста, а следующая должна показывать 3 строки текста, а последние 34 должны отображаться 2 строки. Тем не менее, все они показывают 1 строку, потому что высота RecyclerView не изменяется.

Я попытался вызвать requestLayout() в RecyclerView (и в TextView) из onBindViewHolder(), но он ничего не делает.

MainActivity

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private RecyclerView.Adapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAdapter = createAdapter();

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        mRecyclerView.setAdapter(mAdapter);
    }

    private RecyclerView.Adapter<ViewHolder> createAdapter() {
        RecyclerView.Adapter adapter = new RecyclerView.Adapter<ViewHolder>() {
            @Override
            public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
                return new ViewHolder(v);
            }

            @Override
            public void onBindViewHolder(ViewHolder holder, int position) {
                if (position < 33) {
                    holder.mText.setText("1 line");
                } else if (position > 66) {
                    holder.mText.setText("2 lines\n2 lines");
                } else {
                    holder.mText.setText("3 lines\n3 lines\n3 lines");
                }
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        };
        return adapter;
    }

    private static class ViewHolder extends RecyclerView.ViewHolder {
        TextView mText;
        public ViewHolder(View itemView) {
            super(itemView);
            mText = (TextView) itemView.findViewById(R.id.text);
        }
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#888888">
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hi"
        android:textSize="30sp" />

</LinearLayout>

Ответ 1

Я написал класс, который может установить максимальную высоту просмотра в зависимости от ваших предметов. Попробуйте этот класс:

public class DynamicRecyclerViewHeight extends RecyclerView {

    private int mMaxHeight;

    public DynamicRecyclerViewHeight(@NonNull Context context) {
        super(context);
    }

    public DynamicRecyclerViewHeight(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initialize(context, attrs);
    }

    public DynamicRecyclerViewHeight(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize(context, attrs);
    }

    private void initialize(Context context, AttributeSet attrs) {
        TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.CustomRecyclerView);
        mMaxHeight = arr.getLayoutDimension(R.styleable.CustomRecyclerView_maxHeight, mMaxHeight);
        arr.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mMaxHeight > 0) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    public void setmMaxHeight(int mMaxHeight) {
        this.mMaxHeight = mMaxHeight;
        invalidate();
    }
}

Вы должны добавить эти строки в файл attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DynamicRecyclerViewHeight">
        <attr name="maxHeight" format="dimension" />
    </declare-styleable>
</resources>

Затем вы можете использовать этот метод для установки maxHeight toyour recyclerView:

yourRecyclerView.setMaxHeight(yourHeight);

Удачи.

Ответ 2

Просто вызовите itemAdapter.notifyDataSetChanged();