1

To ground this question, lets assume I have 10 TextViews with text numbered between 1 and 10. At any given time, I would like exactly three of the TextViews to be visible on the screen (no partial). For example, 1,2,3 might be visible or 5,6,7. I want to be able to scroll through this list of items (forwards and backwards) such that the first and last number take up 25% of the container they are in and the middle takes up 50%.

For example, when I start my activity I will see the following

1      <25% of container, TextView scaled to fill all 25%>
2      <50% of container, TextView scaled to fill all 50%>
3      <25% of container, TextView scaled to fill all 25%>

If the user scrolls down one item the following would be seen

2      <25% of container, TextView scaled to fill all 25%>
3      <50% of container, TextView scaled to fill all 50%>
4      <25% of container, TextView scaled to fill all 25%>

How could I (easily) do this? Something like a RecyclerView would be ideal but I don't see how I could get it to work. The best I can see is to use multiple view holder types and statically assign a type to each object in my list(like here and here). In other words if I start with this

1      <25% of container>
2      <50% of container>
3      <25% of container>

and scroll by one I will get this

2      <50% of container>
3      <25% of container>
4      <25% of container>

This isn't what I want though because TextView 2 maintains 50% of the container when it should be reduce to 25% when the user scrolled down. Another way would be to have only three TextViews and manually update the text of each whenever a scroll event occurs (via a listener), but that seems really hacky. This is such a simple thing I have seen appear in multiple apps so there must be a better solution than that.

What is the correct way of doing this and what ViewGroup/Layout/etc should I use?

Also, as an aside, if you give an example, please do so in Java not Kotlin.

HXSP1947
  • 1,311
  • 1
  • 16
  • 39
  • 2
    It sounds like you want a carousel. Search for "motionlayout carousel" and "recyclerview carousel" to take a look at some examples. I think that you could modify one of these to do what you want. – Cheticamp Feb 20 '22 at 21:47
  • 1
    This looks like another example of UI/UX Android designer who uses an iPhone. – Tahir Ferli Feb 23 '22 at 23:42

1 Answers1

0

This was interesting so I made an example with RecyclerView.Adapter the main point is on onScrollStateChanged there, you scroll to fill items in recyclerView and set 50% heght only to the middle item:

Make sure to set the layoutManager as LinearLayoutManager

class RecyclerAdapter(val items: Int) : RecyclerView.Adapter<RecyclerAdapter.TextViewHolder>() {

private var listHeight: Int = 0
private var scrollDirectionY = 0
private var visibleItemPosition = 0

private var largetItemPosition = 1 // View at Index 1 have 50%, others 25%

private lateinit var layoutManager: LinearLayoutManager

private val scrollListener = object : RecyclerView.OnScrollListener() {

    var autoFit = false
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)
        // Stopped scrolling...
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            if (autoFit) {
                autoFit = false
                return
            }


            autoFit = true
            // Is Scrolling Down
            if (scrollDirectionY > 0) {
                visibleItemPosition = layoutManager.findLastVisibleItemPosition()
                val lastView = layoutManager.findViewByPosition(visibleItemPosition) ?: return
                val lastAmount = lastView.bottom - recyclerView.bottom
                // set to display last view fully
                recyclerView.smoothScrollBy(0, lastAmount)


                val prevPos = largetItemPosition
                largetItemPosition = visibleItemPosition - 1
                notifyItemChanged(prevPos)
                notifyItemChanged(largetItemPosition)

            }
            // Is Scrolling Up
            else if (scrollDirectionY < 0) {
                visibleItemPosition = layoutManager.findFirstVisibleItemPosition()
                val firstView = layoutManager.findViewByPosition(visibleItemPosition) ?: return
                val firstAmount = firstView.top - recyclerView.top
                // set to display first view fully
                recyclerView.smoothScrollBy(0, firstAmount)


                val prevPos = largetItemPosition
                largetItemPosition = visibleItemPosition + 1
                notifyItemChanged(prevPos)
                notifyItemChanged(largetItemPosition)
            }
        }
    }

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        scrollDirectionY = dy
    }

}

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
    super.onAttachedToRecyclerView(recyclerView)

    recyclerView.viewTreeObserver.addOnGlobalLayoutListener(object :
        ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            recyclerView.viewTreeObserver.removeOnGlobalLayoutListener(this)
            listHeight = recyclerView.measuredHeight
            // In this example, needs to refresh after finding height
            notifyDataSetChanged()
        }
    })

    // Make sure is LinearLayoutManager
    layoutManager = recyclerView.layoutManager as LinearLayoutManager

    recyclerView.addOnScrollListener(scrollListener)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextViewHolder {
    // You can have a simple TextView in xml and just reference here
    val textView = TextView(parent.context).apply {
        // text style stuff
        setTextColor(Color.BLACK)
        textSize = 50f
        gravity = Gravity.CENTER
        //end
        layoutParams = RecyclerView.LayoutParams(
            RecyclerView.LayoutParams.MATCH_PARENT,
            RecyclerView.LayoutParams.WRAP_CONTENT
        )
    }

    return TextViewHolder(textView)
}

override fun onBindViewHolder(holder: TextViewHolder, position: Int) = holder.bind()

override fun getItemCount(): Int = items

inner class TextViewHolder(val view: View) : RecyclerView.ViewHolder(view) {

    // Initial bind, from adapter bindViewHolder
    fun bind() {
        val percent = if(adapterPosition == largetItemPosition) 0.5 else 0.25
        
        (view as TextView).apply {
            // just a simple text
            text = "${adapterPosition + 1}"
            // set exact height in pixels, since we get listHeight in pixels
            height = (listHeight * percent).toInt()
        }
    }
}

}

dardan.g
  • 689
  • 7
  • 17