Vintage appMaker의 Tech Blog

RecyclerView 가로스크롤 자석효과 본문

Source code or Tip/Android(Java, Kotlin)

RecyclerView 가로스크롤 자석효과

VintageappMaker 2021. 7. 17. 08:26

Android 앱 중에는 가로스크롤 후, 좌측 끝부분을 특정 위치로 재조정하는 것들이 있다. 

종종 구현해야 할 필요가 있어 구글링을 해보니 다음링크가 교과서처럼 사용되고 있었다. 

 

https://stackoverflow.com/questions/26370289/snappy-scrolling-in-recyclerview/33774983

 

Snappy scrolling in RecyclerView

I am trying to use the new RecyclerView class for a scenario where I want the component to snap to a specific element when scrolling (The old Android Gallery comes to mind as an example of such a l...

stackoverflow.com

 


위의 코드를 kotlin으로 확장함수를 이용하여 다음과 같이 간편하게 정리했다. 

 

ViewExt.kt

fun RecyclerView.getScrollDistanceOfColumnClosestToLeft(): Int {
    val manager = layoutManager as LinearLayoutManager?
    val firstVisibleColumnViewHolder = findViewHolderForAdapterPosition(
        manager!!.findFirstVisibleItemPosition()
    ) ?: return 0
    val columnWidth = firstVisibleColumnViewHolder.itemView.measuredWidth
    val left = firstVisibleColumnViewHolder.itemView.left
    val absoluteLeft = Math.abs(left)
    return if (absoluteLeft <= columnWidth / 2) left else columnWidth - absoluteLeft
}

fun RecyclerView.setMagneticMove(nStart : Int = 0 ){
    addOnScrollListener(object:RecyclerView.OnScrollListener(){
        var oldMoveTo : Int = 0
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            val moveTo = getScrollDistanceOfColumnClosestToLeft() - nStart
            if(newState == RecyclerView.SCROLL_STATE_IDLE){
                // smoothScrollBy()를 사용했을 경우,
                // 양쪽 끝자리에서 움직이면 무한루핑이 됨.
                // 그래서 이전값과 비교함.
                if(moveTo !== oldMoveTo){
                    recyclerView.smoothScrollBy(moveTo, 0)
                    oldMoveTo = moveTo
                }
            }
        }
    })
}

사용법은 간단하다. 

리사이클뷰를 선언시,  setMagneticMove를 호출하기만 하면된다.

 

 

MainActivity.kt

...

val lst = mutableListOf<SimpleData>()
val colortable = listOf(Color.RED, Color.GRAY, Color.BLUE, Color.GREEN, Color.WHITE)
(0..30).forEach {
                    val item = Box(color = colortable.get( it % colortable.size), alpha = it * 0.1f % 1.0f )
                    lst.add( item as SimpleData )
}

// 좌측에서 이동
//recycler.setMagneticMove(dpToPx(60f) * -1)
recycler.setMagneticMove()

val manager = LinearLayoutManager(applicationContext, RecyclerView.HORIZONTAL, false)
recycler.layoutManager = manager
val adt = MagneticAdapter(lst, applicationContext)
recycler.adapter = adt

...

 

MagneticAdapter.kt

import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.test.psw.oftenutilbox.R
import kotlin.random.Random

sealed class SimpleData
data class Box(
    var color        : Int,
    var alpha        : Float
) : SimpleData()

class MagneticAdapter(val items : List<SimpleData>, val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var mItems : MutableList <SimpleData> = items.toMutableList()

    override fun getItemCount(): Int {
        return mItems.size
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType){
            TYPE_ONE   -> {boxViewHolder(LayoutInflater.from(context).inflate(R.layout.recycler_item, parent, false))}
            else -> {boxViewHolder(LayoutInflater.from(context).inflate(R.layout.recycler_item, parent, false))}
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

        when (holder.itemViewType){
            TYPE_ONE -> { (holder as boxViewHolder).apply {
                var item = mItems.get(position) as Box
                bind(context,  item)
            }}
        }

    }

    override fun getItemViewType(position: Int): Int {
        return when (mItems.get(position)){
            is Box -> {
                TYPE_ONE
            }
        }
    }

    companion object {
        private const val TYPE_ONE   = 0
    }
}

class boxViewHolder (view: View) : RecyclerView.ViewHolder(view) {
    val box = view.findViewById<View>(R.id.colorBox)
    val dimmed = view.findViewById<View>(R.id.dimmed)
    fun bind(context : Context, item : Box){
        box.setBackgroundColor(item.color)

        dimmed.setBackgroundColor(Color.BLACK)
        dimmed.alpha = item.alpha
    }
}

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="#000000"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </androidx.recyclerview.widget.RecyclerView>

</androidx.constraintlayout.widget.ConstraintLayout>

 

recycler_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:padding="20dp"
    android:orientation="vertical">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <View
            android:id="@+id/colorBox"
            android:layout_width="280dp"
            android:layout_height="300dp"/>

        <View
            android:id="@+id/dimmed"
            android:layout_width="280dp"
            android:layout_height="300dp"/>

    </FrameLayout>

</LinearLayout>

 

Comments