Skip to content

Instantly share code, notes, and snippets.

@ed-george
Last active October 15, 2021 07:02
Show Gist options
  • Save ed-george/637af23583dff9c206b57bfcd2285096 to your computer and use it in GitHub Desktop.
Save ed-george/637af23583dff9c206b57bfcd2285096 to your computer and use it in GitHub Desktop.
A RecyclerView that can display a 'masked' selection
package com.himumsaiddad.example.util
import android.content.res.Resources
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
package com.himumsaiddad.example.ui.widget
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.himumsaiddad.example.R
import com.himumsaiddad.example.util.dp
class MaskedSelectorRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
private val filter = LightingColorFilter(Color.WHITE, Color.WHITE)
private val paint = Paint()
private val rectf = RectF()
private val rect by lazy {
// Hard coded values to be replaced with correct vals
Rect(width/2 - 44.dp, 0, width/2 + 44.dp, 32.dp)
}
override fun onDraw(c: Canvas?) {
super.onDraw(c)
rectf.set(rect)
paint.apply {
color = ContextCompat.getColor(context, R.color.purple)
isAntiAlias = true
colorFilter = null
}
// Hard coded values to be replaced with relevant dimens
c?.drawRoundRect(rectf, 32.dp.toFloat(), 32.dp.toFloat(), paint)
}
override fun dispatchDraw(canvas: Canvas?) {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvasCopy = Canvas(bitmap)
// draw them to that temporary bitmap
super.dispatchDraw(canvasCopy)
// copy the temporary bitmap to screen without the filter
paint.colorFilter = null
canvas?.drawBitmap(bitmap, 0f, 0f, paint)
// copy the inverted rectangle
paint.colorFilter = filter
canvas?.drawBitmap(bitmap, rect, rect, paint)
}
}
package com.himumsaiddad.example.ui.widget
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSnapHelper
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
import kotlin.math.sqrt
// Adapted from @nbtk123 blog on sliding recyclerview layouts
// Read it here - https://link.medium.com/wP4Dy4KxM0
class SliderLayoutManager(context: Context?, private val callback: (Int) -> Unit = {}) : LinearLayoutManager(context) {
init {
orientation = HORIZONTAL
reverseLayout = false
}
private lateinit var recyclerView: RecyclerView
var selectedIndex = -1
override fun onAttachedToWindow(view: RecyclerView?) {
super.onAttachedToWindow(view)
recyclerView = view!!
// Smart snapping (try PagerSnapHelper)
LinearSnapHelper().attachToRecyclerView(recyclerView)
}
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State) {
super.onLayoutChildren(recycler, state)
scaleDownView()
}
override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State?): Int {
return if (orientation == HORIZONTAL) {
val scrolled = super.scrollHorizontallyBy(dx, recycler, state)
scaleDownView()
scrolled
} else {
0
}
}
private fun scaleDownView() {
val mid = width / 2.0f
for (i in 0 until childCount) {
// Calculating the distance of the child from the center
val child = getChildAt(i) ?: continue
val childMid = (getDecoratedLeft(child) + getDecoratedRight(child)) / 2.0f
val distanceFromCenter = abs(mid - childMid)
// The scaling formula
val scale = 1- sqrt((distanceFromCenter/width).toDouble()).toFloat()*0.66f
// Set scale to view
child.scaleX = scale
child.scaleY = scale
}
}
override fun onScrollStateChanged(state: Int) {
super.onScrollStateChanged(state)
// When scroll stops we notify on the selected item
if (state == RecyclerView.SCROLL_STATE_IDLE) {
// Find the closest child to the recyclerView center --> this is the selected item.
val recyclerViewCenterX = getRecyclerViewCenterX()
var minDistance = recyclerView.width
for (i in 0 until recyclerView.childCount) {
val child = recyclerView.getChildAt(i)
val childCenterX = getDecoratedLeft(child) + (getDecoratedRight(child) - getDecoratedLeft(child)) / 2
val newDistance = abs(childCenterX - recyclerViewCenterX)
if (newDistance < minDistance) {
minDistance = newDistance
selectedIndex = recyclerView.getChildLayoutPosition(child)
}
}
// Notify on item selection
callback.invoke(selectedIndex)
}
}
private fun getRecyclerViewCenterX() : Int {
return (recyclerView.right - recyclerView.left)/2 + recyclerView.left
}
@viroth-ty
Copy link

viroth-ty commented Oct 15, 2021

@ed-george How can I make first item selected at first time?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment