Hanbit the Developer
[Kotlin] Carousel RecyclerView With PagerSnapHelper 본문
> Layouts
- Add padding to recycler view so that you can see a part of the adjacent items.
- In the item, set width to match_parent
> Recycler view acting like view pager2
- Just use PagerSnapHelper
snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(YOUR_RECYCLER_VIEW)
> Add button in last index of recycler view
- Add 'add_button_item.xml' which contains:
- Divide view holder into two like:
class NormalItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
// views for normal item
}
class AddButtonViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
// views for add button
}
This is because there are two items(normal_item.xml, add_button_item.xml) so ViewHolder can be divided into two. Then you can handle each views for normal item and add button.
- Override getItemViewType(), getItemCount():
override fun getItemViewType(position: Int): Int {
return if(position == resultList.size) {
R.layout.add_button
} else {
R.layout.normal_item
}
}
override fun getItemCount() = recyclerViewItemList.size + 1
Add 1 to the index and the last one item is set to add_button by getItemViewType(). For example, If there is no item, getItemCount returns 1 and the only item will be add_button because (position == resultList.size) is true.
Be careful If you want to check if there is no item, use a condition (position == 1) rather than 0.
- onCreateViewHolder:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType){
R.layout.normal_item -> {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.normal_item, parent, false)
NormalItemViewHolder(itemView)
}
else -> {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.add_button, parent, false)
AddButtonViewHolder(itemView)
}
}
}
- onBindViewHolder:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(getItemViewType(position)){
R.layout.normal_item -> {
holder as NormalItemListViewHolder
// For normal_item
}
R.layout.add_button ->{
holder as AddButtonViewHolder
// For add_button
}
}
}
> Add dragger for 'drag and drop'
- Implement drag and drop using ItemTouchHelper.
- Restore scroll after dragging.
ItemTouchHelper doesn't handle scroll like PagerSnapHelper so that the scroll can be stop at any value, not at some specific values which reflect PagerSnapHelper. Therefore If dragging ends, the scroll must be restored.
Pass 'restoreScroll: () -> Unit' as a paramater of DragAdapter and the parameter will be:
snapHelper.findSnapView(layoutManager)?.let {
val position = layoutManager.getPosition(it)
binding.myPetListRecyclerView.smoothScrollToPosition(position)
}
It finds a current view and get position from the view and finally scroll to the position programatically.
And use the restoreScroll when scroll ends by overriding clearView of DragAdapter:
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
restoreScroll.invoke()
}
- Override canDropOver() of DragAdapter to prevent items from dropping over add button.
override fun canDropOver(
recyclerView: RecyclerView,
current: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return if(current.itemViewType == R.layout.add_button ||
target.itemViewType == R.layout.add_button) false
else super.canDropOver(recyclerView, current, target)
}
- Override interpolateOutOfBoundsScroll to set scroll speed properly.
override fun interpolateOutOfBoundsScroll(
recyclerView: RecyclerView,
viewSize: Int,
viewSizeOutOfBounds: Int,
totalSize: Int,
msSinceStartScroll: Long
): Int {
return if(super.interpolateOutOfBoundsScroll(
recyclerView,
viewSize,
viewSizeOutOfBounds,
totalSize,
msSinceStartScroll
) > 0) SCROLL_SENSITIVITY
else -SCROLL_SENSITIVITY
}
I set the returned value to a constant because if not, 'drag and drop' was very uncomfortable.
- Override onSelectedChanged for drag animation additionally.
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder is HistoryListViewHolder) {
val anim = ValueAnimator.ofFloat(0.5f, 1f)
anim.addUpdateListener { valueAnimator ->
val value = valueAnimator.animatedValue as Float
viewHolder.itemView.scaleX = value
viewHolder.itemView.scaleY = value
}
anim.duration = 100
anim.start()
}
}
super.onSelectedChanged(viewHolder, actionState)
}
> References
https://stackoverflow.com/questions/29106484/how-to-add-a-button-at-the-end-of-recyclerview
https://developer.android.com/reference/androidx/recyclerview/widget/PagerSnapHelper
https://developer.android.com/reference/androidx/recyclerview/widget/ItemTouchHelper.Callback
'Android' 카테고리의 다른 글
[Kotlin] Android DataBinding 예제(+ 양방향 데이터 바인딩, 클릭 이벤트, 삼항 연산자) (0) | 2022.02.10 |
---|---|
[Retrofit2] Video View와 HTTP Range Header에 대해 (0) | 2022.01.11 |
[Kotlin] Implementation Retrofit2 Callback Function (0) | 2021.10.01 |
[Kotlin] RecyclerView in SwipeRefreshLayout in NestedScrollView Implementation (0) | 2021.09.24 |
[Kotlin] Delete whole directory in android (0) | 2021.08.19 |