Last active
October 15, 2021 07:02
-
-
Save ed-george/637af23583dff9c206b57bfcd2285096 to your computer and use it in GitHub Desktop.
A RecyclerView that can display a 'masked' selection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.himumsaiddad.example.util | |
import android.content.res.Resources | |
val Int.dp: Int | |
get() = (this * Resources.getSystem().displayMetrics.density).toInt() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@ed-george How can I make first item selected at first time?