-1

I'm developing a music collection app, and I'm struggling with my current goal :Designing a scrollable list of 3 textviews - track number, track title and track duration - which will represent songs in an album. I want the list's layout to be composed of 3 columns with fixed width,aligned as one would expect, without the title textview for a particular track, for example, stretching over above the duration textview of the track below.

I have considered the following options:

  1. Using one RecyclerView with its ViewHolder layout composed of a horizontal LinearLayout of 3 textviews. The problem here is that I don't know how to keep the columns aligned this way. Should I set fixed widths for the textviews using definite dps? How then can I set the entire row to fill the device's entire width?

  2. Using 3 RecyclerViews for each column. I've tried this idea halfway, and the columns are aligned nicely, but scrolling one RV doesn't scroll the others, obviously, and to fix that - as I've seen in another question here - I'll need to mess with the scrolling mechanism, and it seems it can still be very prone to errors with the RVs still might manage to get out of sync or the app crashing.

I know there is GridLayout (which I'm not familiar with and don't know if it can solve my problem), but since it's now considered legacy, I would rather refrain from using it.

Above all, I would like to know what way is considered "best practice", assuming there is one, to tackling such a problem.

Thanks!

tfreifeld
  • 183
  • 11

3 Answers3

1

I tried to create a scrollable list of 3 TextViews for a row.

Like you have mentioned, we can use a RecyclerView as the scrollable list.

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>

For a list item to have 3 TextViews in a row, I used a GridLayout as follows.

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>

For each TextView a layout_columnWeight value is set to specify the relative proportion of horizontal space that should be allocated for it. Each TextView's layout_width value is set to 0dp for it to take full width of the allocated space.

But instead of GridLayout, we can use a ConstraintLayout and achieve the same result. As mentioned above in the question, GridLayout is marked as legacy. It better to use 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>

For each TextView a layout_constraintHorizontal_weight value is set to specify the relative proportion of horizontal space that should be allocated for it. Each TextView's layout_width value is set to 0dp for it to take full width of the allocated space.

This is just a Kotlin class to store track information.

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

This is the Adapter class and ViewHolder class for the RecyclerView.

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
}

This is the 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)
    }
}

Result :

Here is a screenshot of the result

I hope this answers the first part of your question.

You have mentioned about 'stretching over above the duration textview, when there's a particular track without a title'.

For that, children of a GridLayout can be configured to span multiple cells using android:layout_columnSpan attribute.

But to handle trackTitle null cases and set properties like layout_columnSpan of TextViews accordingly, I think it would be better to write a custom view class for list item.

Lakindu
  • 1,010
  • 8
  • 14
  • Thank you very much. This is indeed what I was looking for in terms of result, though I was looking for a solve without using GridLayout as it is considered legacy (I wrote in the question GridView by mistake, I meant GridLayout). – tfreifeld May 01 '20 at 14:48
  • Meanwhile, I found a solution without GridLayout, I will post it shortly. – tfreifeld May 01 '20 at 14:50
  • You are correct. GridLayout is also marked as legacy. Earlier I didn't know what 'legacy' meant. Now I got the answer from [here](https://stackoverflow.com/questions/50079026/why-are-some-views-inside-the-legacy-tab-in-android-studio-3-1-and-what-replaces). It's good that you found a solution without GridLayout. Yes, please post it. – Lakindu May 01 '20 at 17:22
  • 1
    I was able to get rid of GridLayout by using ConstraintLayout and achieve the same result. The change was only in list_item.xml. I'll keep current list_item.xml as it is, in case someone would still want to use a GridLayout instead of ConstraintLayout, and edit answer to add the new list_item.xml. – Lakindu May 01 '20 at 18:38
  • 1
    Sorry, I saw your [post](https://stackoverflow.com/a/61545011/2443657) after I edited this answer. I think you have found exactly what you wanted. That's good. You encouraged me to improve my answer and thank you for that. – Lakindu May 01 '20 at 19:04
1

This is an alternative solution inspired by Farshad Tahmasbi's answer I found here.

In this screenshot you can see the result. (The duration column is empty right now).

    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);
tfreifeld
  • 183
  • 11
0

the best solution to this is using RecyclerView.

1 - you can set the width of the TextViews to match_parent if you want them to fill the width of the screen.

2 - you need only one recycler view that shows a list of a view.

if you can show me a picture of what you want to achieve

Remon Shehatta
  • 1,324
  • 1
  • 10
  • 21