RecyclerView - Continuous Columns Layout

Viewed 271

I am trying to create a layout where items would follow one another in columns (see image below) but I am not getting there yet. I have tried GridLayoutManager and StaggeredGridLayoutManager - the problem with both neither provides the feature of item flowing into another column and following each other this way. With my current attempt I am trying FlexboxLayoutManager but the result I am getting is always columns with single items instead of the items flowing one after another.

The desired behavior is that the items are located one after another and when the high of the recycler doesn't allow for the full item view it should be broken down to the next column.

Here is what I am trying right now:

mBinding?.activeRecycler?.layoutManager = FlexboxLayoutManager(context).apply {
            flexDirection = FlexDirection.COLUMN
            flexWrap = FlexWrap.WRAP
            alignItems = AlignItems.STRETCH
        }

And this is getting me one item per column.

Trying to achieve this:

enter image description here

3 Answers

I highly doubt this is possible. The RecyclerView, its adapters and its layout managers all are not designed to alter the fundamental form of a view.

Meaning that "splitting" one would not be possible. The RecyclerView is designed to understand how many views are in sight at the same time, create that many views only and then bind the underlying objects to the views respectively.

Meaning the RecyclerView doesn't "Cut a View in half and displays its halves in different places".

The only way in which a constellation like yours would be possible, was if the layout manager is specifically designed to display one item in multiple views and thereby multiple positions. Which would then allow it to be displayed as you described. However, as I said, that would mean the view 3 in the middle and the view 3 in the last column would be two views being bound to the same object or a copy of it. (Or someone went completely crazy and actually split the view, which I doubt).

I don't believe that any of the standard layout managers are capable of it and I doubt that you can even achieve this without also altering the adapter accordingly, at the very least. Because the adapter basically does the binding so without its help the standard layout managers wouldn't be able to do the double binding as described above.

That being said, this is just a very good guess, going by the principles of the view and its components. I have not read the source code or full description of every layout manager.

The way I understand your problem is like this: You have your current list of data that contains the text fields and you want to show them on the normal way, one list item one view item in recycler view.

But based on your design requirements this is not possible.

My idea to achieve that is like this: You have to create a new list which will separate one item of the previous list into 2,3 or more items to fit in your columns.

private fun demo() {

    val originalList = listOf<String>()
    val newScreenSpecificList = mutableListOf<String>()

    val columnHeight = 3//example number of lines
    val columnWidth = 10//example number of chars
    var columnsIndex = 0//index of column
    var currentColumnHeight = 0 // current column filled height

    originalList.forEach {
        if (currentColumnHeight + getTextHeight(it, columnWidth) <= columnHeight) {
            newScreenSpecificList.add(it)
            currentColumnHeight = currentColumnHeight + getTextHeight(it, columnWidth)
        } else {
            //here is the part where your text is bigger then your column height so you need to divide it
            val textForSpaceLeft = getTextForSpaceLeft(it, columnHeight - currentColumnHeight)
            newScreenSpecificList.add(textForSpaceLeft)
            currentColumnHeight = currentColumnHeight + getTextHeight(textForSpaceLeft, columnWidth)

            if (currentColumnHeight >= columnHeight) {
                columnsIndex++
            }

            if (getTextForNewSpaceLeft(it, columnHeight - currentColumnHeight)){
                //continue to repeat logic for new column
                //...
            }

        }

        if (currentColumnHeight >= columnHeight) {
            columnsIndex++
        }
    }

}

private fun getTextForSpaceLeft(it: String, spaceLeft: Int): String {
    return "it"// return text for the available space
}

private fun getTextForNewSpaceLeft(it: String, spaceLeft: Int): String {
    return "new column also"// return text left for the new available space
}

private fun getTextHeight(text: String, columnWidth: Int): Int {
    return 2//todo your logic to convert text length to number of lines needed for a specific width of the column
}

Now you need to continue this logic it is not complete, I hope it helps you.

I guess your problem is with the LayoutParams of items which are being created in your adapter. probably the height is set to match_parent in items. You can try to change the LayoutParams of itemViews in your adapter's onCreateViewHolder/onBindViewHolder. Or if the items' heights are kinda tricky to calculate, you can create a customView and try calculate the height in onMeasure and set the height to wrap_content

try to set items' height to wrap_content or if you want to do it in code, something like this:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FlexItemViewHolder {
    val infatedView = ...
    
    infatedView.layoutParams = FlexboxLayoutManager.LayoutParams(FlexboxLayoutManager.LayoutParams.WRAP_CONTENT,     FlexboxLayoutManager.LayoutParams.WRAP_CONTENT)
    infatedView.addView(textView)
    return FlexItemViewHolder(f)
}
Related