设计每行多个文本视图的可滚动列表

Design a scrollable list of multiple textviews per line

我正在开发一个音乐 collection 应用程序,我正在努力实现我当前的目标:设计一个包含 3 个文本视图的可滚动列表 - 曲目编号、曲目标题和曲目持续时间 - 将代表歌曲一张专辑。我希望列表的布局由 3 列组成,宽度固定,按预期对齐,没有特定轨道的标题文本视图,例如,延伸到下面轨道的持续时间文本视图之上。

我考虑了以下选项:

  1. 使用一个 RecyclerView,其 ViewHolder 布局由 3 个文本视图的水平 LinearLayout 组成。这里的问题是我不知道如何以这种方式保持列对齐。我应该使用确定的 dps 为文本视图设置固定宽度吗?那么如何设置整行以填充设备的整个宽度?

  2. 每列使用 3 个 RecyclerView。我已经中途尝试了这个想法,并且列对齐得很好,但是滚动一个 RV 显然不会滚动其他 RV,并且要解决这个问题 - 正如我在另一个问题中看到的那样 - 我需要搞砸滚动机制,它似乎仍然很容易出错,RV 仍然可能设法不同步或应用程序崩溃。

我知道有 GridLayout(我不熟悉,不知道它是否能解决我的问题),但由于它现在被认为是遗留的,我宁愿避免使用它。

最重要的是,我想知道 "best practice" 假设有一种方法可以解决这样的问题。

谢谢!

对此的最佳解决方案是使用 RecyclerView。

1 - 您可以将 TextView 的宽度设置为 match_parent 如果您希望它们填满屏幕的宽度。

2 - 您只需要一个显示视图列表的回收器视图。

如果你能给我一张你想要达到的目标的图片

我试图为一行创建一个包含 3 个 TextView 的可滚动列表。

正如您提到的,我们可以使用 RecyclerView 作为可滚动列表。

activity_main.xml

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

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

</ConstraintLayout>

对于一个连续有 3 个 TextView 的列表项,我使用了如下的 GridLayout。

list_item.xml

<GridLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:columnCount="3"
    android:rowCount="1" >

    <TextView
        android:id="@+id/trackNumber"
        android:layout_columnWeight="1"
        android:layout_width="0dp"
        android:background="@android:color/holo_red_light" />

    <TextView
        android:id="@+id/trackTitle"
        android:layout_columnWeight="6"
        android:layout_width="0dp"
        android:ellipsize="end"
        android:background="@android:color/holo_green_light" />

    <TextView
        android:id="@+id/trackDuration"
        android:layout_columnWeight="1"
        android:layout_width="0dp"
        android:background="@android:color/holo_blue_light" />

</GridLayout>

为每个 TextView 设置一个 layout_columnWeight 值以指定应为其分配的水平 space 的相对比例。 每个 TextView 的 layout_width 值都设置为 0dp 以占据分配的 space.

的完整宽度

但是我们可以使用 ConstraintLayout 而不是 GridLayout 并获得相同的结果。 正如上面问题中提到的,GridLayout 被标记为旧版。 最好使用 ConstraintLayout。

list_item.xml

<ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/trackNumber"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintHorizontal_weight="1"
        android:background="@android:color/holo_red_light"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/trackTitle"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/trackTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintHorizontal_weight="6"
        android:ellipsize="end"
        android:background="@android:color/holo_green_light"
        app:layout_constraintStart_toEndOf="@id/trackNumber"
        app:layout_constraintEnd_toStartOf="@id/trackDuration"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/trackDuration"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintHorizontal_weight="1"
        android:background="@android:color/holo_blue_light"
        app:layout_constraintStart_toEndOf="@id/trackTitle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</ConstraintLayout>

为每个 TextView 设置一个 layout_constraintHorizontal_weight 值以指定应为其分配的水平 space 的相对比例。 每个 TextView 的 layout_width 值都设置为 0dp 以占据分配的 space.

的完整宽度

这只是一个 Kotlin class 来存储曲目信息。

class TrackInfo(
    var trackNumber: Int,
    var trackTitle: String?,
    var durationMinutes: Short,
    var durationSeconds: Short
)

这是 RecyclerView 的适配器 class 和 ViewHolder class。

class TrackInfoAdapter(private val items: List<TrackInfo>, private val mContext: Context) :
    RecyclerView.Adapter<ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false)
        )
    }

    override fun getItemCount() = items.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val trackInfo = items[position]
        holder.trackNumber.text = trackInfo.trackNumber.toString()
        holder.trackTitle.text = trackInfo.trackTitle
        // Use String.format() to add leading zero for single digit durationSeconds value
        val formattedDurationSeconds = "%02d".format(trackInfo.durationSeconds)
        holder.trackDuration.text = "${trackInfo.durationMinutes}:$formattedDurationSeconds"
    }

}

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val trackNumber = view.trackNumber
    val trackTitle = view.trackTitle
    val trackDuration = view.trackDuration
}

这是 MainActivity。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Prepare list of track information
        val trackInfoList = mutableListOf<TrackInfo>().apply {
            // track 1
            add(
                TrackInfo(1, "Lady GaGa - Poker Face",
                    4, 4)
            )

            // track 2
            add(
                TrackInfo(2, "T.I. featuring Rihanna - Live Your Life",
                    4, 1)
            )

            // track 3
            add(
                TrackInfo(3, "Kanye West - Stronger",
                    5, 11)
            )

            // track 4
            add(
                TrackInfo(4, "Leon Haywood - I Wanna Do Something Freaky To You",
                    6, 0)
            )

            // track 5
            add(
                TrackInfo(5, "Hilary Duff - Reach Out",
                    4, 16)
            )
        }

        recyclerView.layoutManager = LinearLayoutManager(baseContext)
        // Set track information list to Adapter of RecyclerView
        recyclerView.adapter = TrackInfoAdapter(trackInfoList, baseContext)
    }
}

结果:

我希望这能回答您问题的第一部分。

您提到过“当有一个没有标题的特定曲目时,在持续时间文本视图之上拉伸”。

为此,可以使用 android:layout_columnSpan 属性将 GridLayout 的 children 配置为跨越多个单元格。

但是要处理 trackTitle null 情况并相应地设置 TextView 的 layout_columnSpan 等属性,我认为最好为列表项编写一个自定义视图 class。

这是受 Farshad Tahmasbi's answer I found 启发的替代解决方案。

在这个screenshot中你可以看到结果。 (持续时间列现在为空)。

    mTracksRecyclerView.setAdapter(new TrackListAdapter(getContext(), musicItem.getTracks()));
    GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), 12);
    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            int type = position % 3 ;
            if (type == 0) {
                // Position
                return 2;
            } else if (type == 1) {
                // Title
                return 8;

            } else {
                // Duration
                return 2;
            }
        }
    });
    mTracksRecyclerView.setLayoutManager(gridLayoutManager);