Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to peek RecyclerView + PagerSnapHelper

I'm using a horizontal RecyclerView with PagerSnapHelper to make it look like a ViewPager. It's displaying CardViews.

My question is, how can I make it show a small peek of the edge of the next and previous card? The user needs to see a little of those cards so they understand intuitively that they need to swipe horizontally so they can view other cards.

I'll also note that the current card always needs to be centered, even for the first one, which would not have a previous card peeking on the left. Also, the design requirements are out of my control; I need to peek, I can't use dot indicators or anything else.

I could use a LinearSnapHelper and make the width of the cards smaller, but then 1) the first item will be left-aligned instead of centered, since there's no card peeking on the left side and 2) how much of each card displays would vary based on the width of the phone.

This seems like it should be a common and simple task, so I hope I'm missing something obvious to make it happen.

like image 863
Chad Schultz Avatar asked Jun 16 '18 03:06

Chad Schultz


1 Answers

After some trial and error, I found a solution. Fortunately, it wasn't as complicated as I thought, although not as clean as I hoped.

So start with a RecyclerView and its Adapter, use a LinearLayoutManager with a Horizontal orientation, add in a PagerSnapHelper and so on... then to fix the specific issue in this question I made some adjustments to the adapter:

private var orientation: Int? = null

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
    super.onAttachedToRecyclerView(recyclerView)
    orientation = (recyclerView.layoutManager as LinearLayoutManager).orientation
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    // Kludge to adjust margins for horizontal, ViewPager style RecyclerView
    if (orientation != LinearLayout.VERTICAL) {
        holder.itemView.layoutParams = (holder.itemView.layoutParams as RecyclerView.LayoutParams).apply {
            val displayMetrics = DisplayMetrics()
            baseActivity.windowManager.defaultDisplay.getMetrics(displayMetrics)
            // To show the edge of the next/previous card on the screen, we'll adjust the width of our MATCH_PARENT card to make
            // it just slightly smaller than the screen. That way, no matter the size of the screen, the card will fill most of
            // it and show a hint of the next cards.
            val widthSubtraction = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40f, displayMetrics).toInt()
            width = displayMetrics.widthPixels - widthSubtraction
            // We always want the spot card centered. But the RecyclerView will left-align the first card and right-align the
            // last card, since there's no card peeking on that size. We'll adjust the margins in those two places to pad it out
            // so those cards appear centered.
            // Theoretically we SHOULD be able to just use half of the amount we shrank the card by, but for some reason that's
            // not quite right, so I'm adding a fudge factor developed via trial and error to make it look better.
            val fudgeFactor = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6f, displayMetrics).toInt()
            val endAdjustment = (widthSubtraction / 2) - fudgeFactor
            marginStart = if (position == 0) endAdjustment else 0
            marginEnd = if (position == (itemCount - 1)) endAdjustment else 0
        }
    }
}

Of course, you'll want to change 40f and 6f to the appropriate values for your use case.

In case you're wondering, I have the orientation check because in my case, the same adapter is used for two different RecyclerViews. One is a simple vertical list. The other is horizontal and functions like a ViewPager, which greatly increased the complexity.

like image 152
Chad Schultz Avatar answered Oct 05 '22 09:10

Chad Schultz