이번 글에서는 RecyclerVIew에서 현재 화면 가운데에 있는 아이템을 강조하는 법에 대해서 알아보도록 하자.
Adapter, ViewHolder, itemXml
일단 먼저 RecyclerView를 표시하기 위해서 Adapter와 ViewHolder, itemXml을 정의해주도록 하자.
class RecyclerAdapter(
private val itemList: ArrayList<String>,
private val onClickItem: (String) -> Unit
): Adapter<RecyclerViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val binding = ItemRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return RecyclerViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
val item = itemList[position]
holder.binding.run {
cardView.apply {
scaleX = 0.9f
scaleY = 0.9f
}
cardView.setOnClickListener {
onClickItem(item)
}
textView.text = item
}
}
override fun getItemCount(): Int = itemList.count()
}
먼저 Adapter를 만들어주도록 하자.
paramer로는 데이터 리스트와 아이템을 클릭 했을 때 호출할 함수를 받아주도록 한다.
그 다음으로 뷰의 기본 크기를 0.9배로 맞춰줘서 줌되는 아이템을 좀 더 강조하는 효과를 주도록 하고
아이템 클릭시 onClickItem함수에 아이템을 argument로 넣어줘서 호출하도록 하자.
class RecyclerViewHolder(val binding: ItemRecyclerBinding): ViewHolder(binding.root) {
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="300dp"
android:layout_height="400dp"
android:elevation="10dp"
app:cardCornerRadius="30dp">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="animal"
android:background="@color/teal_200"
android:textColor="@color/black"
android:textSize="50dp" />
</androidx.cardview.widget.CardView>
</FrameLayout>
ViewHolder와 itemXml은 매우 간단하게 작성해서 따로 설명할 부분은 없다.
MainActivity, RecyclerView
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_recycler"/>
</androidx.constraintlayout.widget.ConstraintLayout>
이번엔 MainActivity를 만들어주는데, 일단 간단하게 RecyclerView만 있게 작성해주자.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var recyclerAdapter: RecyclerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
}
private fun initView() {
binding.root.let {
it.viewTreeObserver.addOnGlobalLayoutListener(object:
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
it.viewTreeObserver.removeOnGlobalLayoutListener(this)
initRecyclerView()
}
})
}
}
private fun initRecyclerView() {
binding.recyclerView.apply {
recyclerAdapter = RecyclerAdapter(arrayListOf("호랑이", "사자", "곰", "고양이", "개", "기린")) {
Toast.makeText(this@MainActivity, it, Toast.LENGTH_SHORT).show()
}
adapter = recyclerAdapter
val snap = PagerSnapHelper()
snap.attachToRecyclerView(this)
val scrollListener = ScrollListener(layoutManager as LinearLayoutManager, binding.root.width)
addOnScrollListener(scrollListener)
addItemDecoration(Decoration(binding.root.width))
}
}
}
다음으로 RecyclerView를 설정해주는데, 부분별로 설명해보도록 하겠다.
setAdapter
recyclerAdapter = RecyclerAdapter(arrayListOf("호랑이", "사자", "곰", "고양이", "개", "기린")) {
Toast.makeText(this@MainActivity, it, Toast.LENGTH_SHORT).show()
}
adapter = recyclerAdapter
위에서 작성한 Adapter클래스에 여러 동물 텍스트를 arrayList로 넣어준 뒤, clickEvent로는 Toast를 띄워주도록 한다.
그 뒤 adapter를 recycerView에 연결시켜주기만 하면 된다.
GlobalLayoutListener
binding.root.let {
it.viewTreeObserver.addOnGlobalLayoutListener(object:
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
it.viewTreeObserver.removeOnGlobalLayoutListener(this)
initRecyclerView()
}
})
}
일단 이 부분은 rootView의 width를 얻기위한 부분인데, 자세한 설명은 다음에 기회가 되면 글로 작성해보도록 하겠다.
SnapHelper
val snap = PagerSnapHelper()
snap.attachToRecyclerView(this)
다음으로 RecyclerView에 snapHelper를 연결해주는데,
이걸 사용해서 RecyclerView에서 하나의 아이템만 선택할 수 있도록 한다.
ScrollListener
val scrollListener = ScrollListener(layoutManager as LinearLayoutManager, binding.root.width)
addOnScrollListener(scrollListener)
이 부분이 이 글에서 핵심인 아이템을 ZoomIn, ZoomOut하는 부분이다.
RecyclerView에 ScrollListener를 만들어서 연결해주는데, 아래에서 어떻게 만들었는지 보도록 하자.
class ScrollListener(
private val layoutManager: LinearLayoutManager,
private val screenWidth: Int,
): OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
setFocus(layoutManager)
}
private fun setFocus(manager: LinearLayoutManager) {
manager.let {
val firstPos = it.findFirstVisibleItemPosition()
val lastPos = it.findLastVisibleItemPosition()
val completePos = it.findFirstCompletelyVisibleItemPosition()
val firstView = it.findViewByPosition(firstPos)
val lastView = it.findViewByPosition(lastPos)
if (completePos != RecyclerView.NO_POSITION) {
val completeView = it.findViewByPosition(completePos)
if (lastPos == completePos) firstView?.let { view -> zoomOutView(view) }
else if (firstPos == completePos) lastView?.let { view -> zoomOutView(view)}
zoomInView(completeView)
} else {
if (firstView != null && lastView != null) {
if (lastView.x <= screenWidth / 2) {
zoomOutView(firstView)
zoomInView(lastView)
} else {
zoomOutView(lastView)
zoomInView(firstView)
}
}
}
}
}
private fun zoomInView(inView: View?) {
inView?.run {
if (!isSelected) {
val anim = AnimationUtils.loadAnimation(context, R.anim.anim_focus_in)
startAnimation(anim)
isSelected = true
}
}
}
private fun zoomOutView(outView: View?) {
outView?.run {
if (isSelected){
val anim = AnimationUtils.loadAnimation(context, R.anim.anim_focus_out)
startAnimation(anim)
isSelected = false
}
}
}
우리가 Listener에서 사용할 함수는 onScrolled이다.
onScrolled함수가 실행되면 setFocus함수를 실행하는데, 어떻게 동작하는지 설명해보도록 하겠다.
- 현재 화면에서 좌, 우, 중앙에 있는 View들을 각각 구한다. 이 때 사용하는 함수로는
1) findFirstVisibleItemPosition - 현재 화면에 보이는 View중 맨 처음(좌 or 상)에 있는 아이템의 위치를 구한다.
2) findLastVisibleItemPosition - 현재 화면에 보이는 View중 맨 마지막(우 or 하)에 있는 아이템의 위치를 구한다.
3) findFirstCompletelyVisibleItemPosition - 현재 화면에 완전히 보이는 VIew중 맨 처음에 있는 아이템의 위치를 구한다. - 다음으로 조건이 나뉘는데, CompleteView가 있을 경우
firstView와 lastVIew중 CompleteView가 아닌 View를 ZoomOut하고, CompleteView를 ZoomIn한다. - CompleteView가 없을 경우 firstView와 lastVIew중
현재 화면에서 더 많은 공간을 차지하고 있는 View를 ZoomIn하고, 그렇지 않은 View를 ZoomOut한다.
이 부분을 통해 아래와 같은 동작이 가능하다. - ZoomIn, ZoomOut함수에서 animation을 사용해 View를 조작해준다.
Decoration
마지막으로 RecyclerView에 Decoration까지 추가해보도록 하자.
class Decoration(
private val screenWidth: Int
): ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val pos = parent.getChildAdapterPosition(view)
val count = state.itemCount
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
val offset = (screenWidth / 2) - (view.measuredWidth / 2)
if (pos == 0) {
outRect.left = offset
} else if (pos == count - 1) {
outRect.right = offset
}
}
}
Decoration에선 첫 번째 아이템과 마지막 아이템을 중앙에 배치하기 위해서
화면 사이즈의 1/2 - 아이템 사이즈의 1/2값을 offset으로 주도록 한다.
'안드로이드 > 개발관련(Kotlin)' 카테고리의 다른 글
안드로이드 데이터베이스(Database) Room 사용하기 (0) | 2023.04.02 |
---|---|
안드로이드 리사이클러뷰(RecyclerView) 사용하기 (0) | 2023.04.01 |
안드로이드 Circle Progress (로딩 애니메이션) 만들기 (0) | 2023.03.30 |
안드로이드 뒤로 가기 두 번 눌러서 종료 (onBackPressed, onBackPressedDispater) (0) | 2023.03.29 |
안드로이드 ViewPager2 + TabLayout를 사용한 스와이프 메뉴 (0) | 2023.03.28 |