具有动态项目高度的横向RecyclerView

14

我正在尝试使用水平滚动的RecyclerView,所以我正在使用具有水平方向的LinearLayoutManager。问题在于我正在使用2种不同高度的项来填充RecyclerView。这是我用于该项的布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp">

<LinearLayout
    android:id="@+id/document_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:background="@drawable/ic_rounded"
    android:backgroundTint="@color/ms_black_ms_gray"
    android:gravity="center"
    android:layout_gravity="bottom"
    android:padding="5dp"
    android:paddingStart="15dp">

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:textSize="13sp"
        android:singleLine="true"
        android:maxWidth="80dp"
        tools:text="example_form"/>

    <TextView
        android:id="@+id/format"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:textSize="13sp" /></LinearLayout>

<android.support.v7.widget.CardView
    android:id="@+id/image_view"
    android:layout_width="120dp"
    android:layout_height="80dp"
    android:layout_gravity="bottom"
    app:cardCornerRadius="25dp"
    app:cardElevation="0dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/preview_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"/></RelativeLayout>
</android.support.v7.widget.CardView>

这是包含 RecyclerView 的布局,大致如下:

图片描述

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingStart="14dp"
        android:paddingEnd="14dp">

        <ImageView
            android:id="@+id/attach"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:layout_gravity="bottom"
            android:layout_marginBottom="19dp"
            android:visibility="visible"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dp"
            android:layout_marginBottom="10dp"
            android:layout_marginTop="5dp"
            android:padding="3dp"
            android:foreground="@drawable/ic_rounded_stroke"
            android:foregroundTint="@color/white">
            <android.support.constraint.ConstraintLayout
                android:id="@+id/chatEdit"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/ic_rounded"
                android:foreground="@drawable/ic_rounded_stroke"
                android:padding="6dp"
                android:visibility="visible">

                <EditText
                    android:id="@+id/editText"
                    android:textSize="17sp"
                    android:textColor="#121212"
                    android:letterSpacing="-0.02"
                    android:lineSpacingExtra="0sp"
                    android:padding="10dp"
                    android:paddingStart="15dp"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:maxLines="5"
                    android:hint="@string/chat_hint"
                    android:inputType="textCapSentences|textMultiLine"
                    android:maxLength="2500"
                    android:background="@null"
                    app:layout_constraintRight_toLeftOf="@id/buttonsContainer"
                    app:layout_constraintLeft_toLeftOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />


                    <TextView
                        android:id="@+id/send"
                        android:layout_gravity="bottom"
                        android:visibility="visible"
                        android:paddingLeft="15dp"
                        android:paddingRight="15dp"
                        android:paddingBottom="10dp"
                        android:paddingTop="8dp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textSize="18sp"
                        android:textColor="#ffffff"
                        android:letterSpacing="-0.02"
                        android:gravity="center_horizontal"
                        android:text="@string/send"
                        app:layout_constraintRight_toRightOf="parent"
                        app:layout_constraintBottom_toBottomOf="parent"
                        />

                <android.support.v7.widget.RecyclerView
                    android:id="@+id/filesList"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_marginTop="10dp"
                    android:paddingTop="5dp"
                    android:paddingEnd="5dp"
                    android:visibility="gone"
                    app:layout_constraintRight_toLeftOf="@id/send"
                    app:layout_constraintLeft_toLeftOf="parent"
                    app:layout_constraintTop_toBottomOf="@id/editText"
                    app:layout_constraintBottom_toBottomOf="parent"/>
            </android.support.constraint.ConstraintLayout>
        </LinearLayout>
    </LinearLayout>

我正在使用一个单一的ViewHolder,只是改变了2个子视图的可见性。

我的期望结果是这样的:

enter image description here

但我得到的结果是这样的; CardView被分成两半,使用第二种类型的项目的高度:

enter image description here

我看到了这篇文章,与我的问题类似。它建议使用Google的Flexbox。因此,我尝试实现FlexboxLayoutManager:

FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(getContext());
layoutManager.setFlexDirection(FlexDirection.ROW);
layoutManager.setFlexWrap(FlexWrap.NOWRAP);

我正在使用row方向,如果一行无法容纳所有项,则会显示在下一行。因此,我还添加了No_wrap。现在,它将所有项都显示在一行中,但不提供滚动条。在这种情况下,它尝试通过减小项的宽度来使所有项适合单行。

我也尝试了弹性框布局示例应用程序,但是我没有得到想要的结果。

是否有一种方法可以利用与RecyclerView集成的Flexbox实现水平滚动?或者我应该使用不同的方法?

谢谢

编辑

谢谢您的提示和建议,但它并没有解决我的问题。因此,我将代码剥离到最少以重现此问题。

MainActivity:

public class MainActivity extends AppCompatActivity {

private static final int REQUEST_CODE = 1;

private RecyclerView recyclerView;
private FilesAdapter filesAdapter;
private List<File> filesList = new ArrayList<>();

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

    recyclerView = findViewById(R.id.recyclerView);
    LinearLayoutManager filesLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
    recyclerView.setLayoutManager(filesLayoutManager);
    filesAdapter = new FilesAdapter(filesList);
    recyclerView.setAdapter(filesAdapter);

    ImageView attach = findViewById(R.id.attach);
    attach.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent();
            intent.setType("*/*");
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setAction(Intent.ACTION_GET_CONTENT);
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
            startActivityForResult(Intent.createChooser(intent,"Select Files"), REQUEST_CODE);
        }
    });
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
        try {
            if (data != null) {
                List<File> uriList = new ArrayList<>();
                if (data.getClipData() != null) { // Multiple files
                    for (int i = 0; i < data.getClipData().getItemCount(); i++) {
                        Uri uri = data.getClipData().getItemAt(i).getUri();
                        Pair<Boolean, File> isValid = isFileValid(uri);
                        if (isValid.first) {
                            uriList.add(isValid.second);
                        }
                    }
                } else { // Single file
                    Uri uri = data.getData();
                    Pair<Boolean, File> isValid = isFileValid(uri);
                    if (isValid.first) {
                        uriList.add(isValid.second);
                    }
                }

                if (uriList.size() > 0) {
                    for (File file : uriList) {
                        filesList.add(filesList.size(), file);
                        filesAdapter.notifyItemInserted(filesList.size());
                    }
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

private Pair<Boolean, File> isFileValid(Uri uri) throws NullPointerException {
    Pair<Boolean, File> defaultResponse = Pair.create(false, null);
    Cursor c = getContentResolver().query(uri, null, null, null, null);
    if (c != null) {
        c.moveToFirst();

        String filename = c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));

        if (isSupported(filename)) {
            c.close();
            return Pair.create(true, new File(StringUtils.endsWithIgnoreCase(filename, ".pdf") ? DOCUMENT : IMAGE));
        } else {
            Toast.makeText(this, "File format not supported", Toast.LENGTH_SHORT).show();
            c.close();
            return defaultResponse;
        }
    }
    return defaultResponse;
}

private boolean isSupported(String filename) {
    String[] supportedFormats = { ".pdf", ".jpg", ".gif", ".png" };

    for (String format : supportedFormats) {
        if (StringUtils.endsWithIgnoreCase(filename, format)) {
            return true;
        }
    }
    return false;
}
}

主活动布局:

<?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:gravity="bottom"
android:orientation="vertical"
tools:context=".MainActivity">

   <androidx.recyclerview.widget.RecyclerView
   android:id="@+id/recyclerView"
   android:layout_width="match_parent"
   android:layout_height="wrap_content" />

<ImageView
    android:id="@+id/attach"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|center"
    android:layout_marginBottom="19dp"
    android:padding="10dp"
    android:src="@drawable/ic_attach" />
</LinearLayout>

文件:

public class File {

public enum Type {
    DOCUMENT,
    IMAGE
}

private Type type;

public File(Type type) {
    this.type = type;
}

public Type getType() {
    return type;
}
}

文件适配器:

public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.BaseViewHolder> {

private List<File> files;

public FilesAdapter(List<File> files) {
    this.files = files;
}

@NonNull
@Override
public FilesAdapter.BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(viewType == 0 ? R.layout.document_item : R.layout.image_item, parent, false);
    if (viewType == 0) {
        return new DocumentViewHolder(view);
    } else {
        return new ImageViewHolder(view);
    }
}

@Override
public void onBindViewHolder(@NonNull FilesAdapter.BaseViewHolder viewHolder, int position) {
    viewHolder.bind(files.get(position));
}

@Override
public int getItemViewType(int position) {
    if (files.get(position).getType() == File.Type.DOCUMENT) {
        return 0;
    } else {
        return 1;
    }
}

@Override
public int getItemCount() {
    return files.size();
}

abstract static class BaseViewHolder extends RecyclerView.ViewHolder {
    public BaseViewHolder(@NonNull View itemView) {
        super(itemView);
    }
    abstract void bind(File file);
}

static class ImageViewHolder extends BaseViewHolder {

    public ImageViewHolder(@NonNull View itemView) {
        super(itemView);
    }

    @Override
    void bind(File file) { }
}

static class DocumentViewHolder extends BaseViewHolder {

    public DocumentViewHolder(@NonNull View itemView) {
        super(itemView);
    }

    public void bind(File file) { }
}
}

文档项:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="150dp"
android:layout_height="40dp"
android:background="@drawable/ic_rounded"
android:backgroundTint="#888888"
android:layout_margin="5dp">

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:gravity="center"
    android:text="PDF"
    android:textColor="@android:color/white"/>

</LinearLayout>

图片项:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="120dp"
android:layout_height="80dp"
android:layout_margin="5dp"
app:cardBackgroundColor="#000000"
app:cardCornerRadius="10dp"
app:cardElevation="0dp">

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:gravity="center"
    android:text="IMAGE"
    android:textColor="@android:color/white"/>

</androidx.cardview.widget.CardView>

如果我先选择一张图片,然后再选择几个pdf文件,就没有问题:

enter image description here

但是如果我先选择了三个pdf文件,然后再选择一张图片,就会出现这种情况:

enter image description here

有什么解决办法吗?


你可以使用GridView与RecyclerView:请查看此链接 https://stackoverflow.com/questions/31398992/gridview-gridview-with-different-cells-sizes-and-layout - B.mansouri
你为什么使用单个viewholder,有特定的原因吗?使用两个viewholder来处理两个视图,这应该可以解决问题。 - Darshan Miskin
已经尝试过了,没有用。它不是切掉了上半部分,而是切掉了下半部分。这是唯一的区别。 - ILovemyPoncho
问题很可能出在包含RecyclerView的布局上。你能否分享完整的xml文件,以便我们可以看到约束视图之外还有什么,以及里面还有什么? - MehranB
我又更新了一下,请你检查一下。 - ILovemyPoncho
显示剩余2条评论
6个回答

24

我在另一个项目中遇到了类似的问题,通过使用Google库FlexboxLayoutManager解决了它。

  1. Get the latest FlexboxLayoutManager Library (https://github.com/google/flexbox-layout) and add it into your grandle dependencies (implementation 'com.google.android:flexbox:2.0.1')
  2. In your Activity add the below lines of code:
    FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(this); 
     layoutManager.setFlexDirection(FlexDirection.ROW);
     layoutManager.setFlexWrap(FlexWrap.NOWRAP);
     recyclerView.setLayoutManager(layoutManager);
  3. To make FlexboxLayoutManager work with horizontal scroll add the below code in your adapter (FilesAdapter) in BaseViewHolder class:
    abstract static class BaseViewHolder extends RecyclerView.ViewHolder {
         public BaseViewHolder(@NonNull View itemView) {
             super(itemView);
             ViewGroup.LayoutParams lp = itemView.getLayoutParams();
             if (lp instanceof FlexboxLayoutManager.LayoutParams) {
                 FlexboxLayoutManager.LayoutParams flexboxLp = (FlexboxLayoutManager.LayoutParams) lp;
                 flexboxLp.setFlexShrink(0.0f);
                 flexboxLp.setAlignSelf(AlignItems.FLEX_START); //this will align each itemView on Top or use AlignItems.FLEX_END to align it at Bottom
             }
         }
         abstract void bind(File file);
     }

太棒了,正是我所寻找的! - ILovemyPoncho
优雅的解决方案 - Mina Farid
说实话,我无法给这个点赞次数够多!真的是一个非常优雅的解决方案。我尝试过各种方法,如滚动监听器、发布可运行文件来计算高度等等。但这个解决方案可以直接开箱即用。 - dev2505
解决方案有效。但是当我尝试删除一个项目时,所有的列表项都会消失(如果我删除第一个或最后一个项目则没有问题)。还有其他人遇到同样的问题吗? - Suneesh Ambatt
以上解决方案在项目视图中有EditText时无法正常工作。因为在RecyclerView上每次按键都会不必要地滚动。 - Shikhar
显示剩余2条评论

5
如果对其他人有帮助的话,这是 MariosP's answer 的 Kotlin 版本,下面进行了一些小的重构,但是100%归功于 @MariosP。他的答案为我们解决了问题!
RecyclerView 设置(这是从一个片段中调用的 onViewCreated):
private fun setupRecyclerView() {
    val flexBoxLayoutManager = FlexboxLayoutManager(requireContext(), FlexDirection.ROW, FlexWrap.NOWRAP)
    with(recycler_view) {
        layoutManager = flexBoxLayoutManager
        adapter = myAdapter
    }
}

适配器设置:

var items : List<Item>

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    holder.bindItem(items[position])
}

在ViewHolder中:
class MyViewHolder(private val itemView: View): RecyclerView.ViewHolder(itemView) {

    fun bindItem(item: Item) {
        // Do things with item
        updateLayoutParamsToAllowHorizontalScrolling()
    }

    private fun updateLayoutParamsToAllowHorizontalScrolling() {
        (itemView.layoutParams as? FlexboxLayoutManager.LayoutParams)?.let {
            it.flexShrink = 0.0f
            it.alignSelf = AlignItems.FLEX_START
        }
    }

}

0

针对您的RecyclerView,请尝试以下代码:

android:layout_height="wrap_content"

由于包含RecyclerView的XML文件不完整,我不能肯定。但是如果您的RecyclerView在另一个限制它的父视图中,那么我猜使用wrap_content作为RecyclerView高度加上一些调整应该可以解决问题。

此外,请注意您将RecyclerView限制在“editText”顶部以下,这可能会防止RecyclerView扩展。


0

你需要做的就是将RecyclerView的高度设置为最大项的高度,对于你的情况来说,就是图像项。

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="wrap_content"
android:layout_height="80dp" />

2
如果项目的高度可能是动态的,则无法正常工作。 - Jeff Padgett

0

如果您首先选择pdf文件,则图像被裁剪的原因是recycleView的高度为40dp,这是pdf项目的高度。当您尝试添加新项目而不修改现有项目时,recycleView的高度保持不变,即40dp。为了强制实施minHeight的最小高度为80dp(这是当前图像布局的高度),我们可以使用以下方法:

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="80dp"
        tools:listitem="@layout/document_item"
        />

您还可以修改PDF项目布局,使PDF与图像项目center_vertically对齐,方法如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:layout_margin="5dp">

    <TextView
        android:layout_width="150dp"
        android:layout_height="40dp"
        android:layout_gravity="center"
        android:gravity="center"
        android:background="@drawable/ic_round"
        android:backgroundTint="#888888"
        android:text="PDF"
        android:textColor="@android:color/white"/>

</LinearLayout>

祝福你成功 :)


-1

首先,我认为你的主要布局有点过于复杂了。你可以在单个ConstraintLayout中完成整个布局(如果你需要特定项目周围的框架背景,则建议使用纯粹的View实例,使用BarrierGuideline进行布局 - 参见https://medium.com/better-programming/essential-components-of-constraintlayout-7f4026a1eb87)。

另一个添加和/或改进的方法是不使用右/左约束,而是使用开始/结束。这将为您的布局准备RTL显示。

此外,我强烈建议为RecyclerView中的不同项目使用单独的布局文件和ViewHolder

正如其他人在评论中指出的那样,您的RecyclerView使用match_parent进行布局,这可能会裁剪您的视图。您可能希望将其设置为wrap_content

同时,您可能还希望更新依赖项以使用Android Jetpack并放弃支持库。


我更新了问题,请您检查一下。 - ILovemyPoncho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接