I have a nested RecyclerView. I'll call them as ParentRecyclerView and ChildRecyclerView.
My goal is kind of complicated.
TouchEvent
)TouchEvent
like Number 1)TouchEvent
. ChildRecyclerView gets the TouchEvent
.) TouchEvent
to make it work the way I wanted it to and made a custom ConstraintLayout
. Here is the code of custom ConstraintLayout.
class TouchThroughConstraintLayout(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) {
private var mLastMotionX = 0f
private var mLastMotionY = 0f
private lateinit var childViewGroup: ViewGroup
fun setChildView(childViewGroup: ViewGroup) { // in this case, the childViewGroup is ChildRecyclerView
this.childViewGroup = childViewGroup
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
mLastMotionX = ev.x
mLastMotionY = ev.y
return childViewGroup.isViewInBounds(ev.rawX.toInt(), ev.rawY.toInt())
}
MotionEvent.ACTION_MOVE -> {
return false
}
}
return false
}
private fun View.isViewInBounds(x : Int, y :Int): Boolean{
val outRect = Rect()
val location = IntArray(2)
getDrawingRect(outRect)
getLocationOnScreen(location)
outRect.offset(location[0], location[1])
return outRect.contains(x, y)
}
}
but the code doesn't work because When i touch the childRecyclerView, isViewInBounds()
always returns true
. So, the ParentRecyclerView gets the TouchEvent
.
How can i solve it?? Thanks in advance. :)
Made some modifications. Please check.
class TouchThroughConstraintLayout(context: Context, attrs: AttributeSet?)
: ConstraintLayout(context, attrs) {
private var mLastMotionX = 0f
private var mLastMotionY = 0f
private lateinit var childViewGroup: ViewGroup
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
fun setChildView(childViewGroup: ViewGroup) {
this.childViewGroup = childViewGroup
}
private var childViewIsTouched = false
private var childViewIsSwipedHorinzontally = false
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
mLastMotionX = ev.x
mLastMotionY = ev.y
// Check if the touch is within the bounds of the child view group
childViewIsTouched = childViewGroup.isViewInBounds(ev.rawX.toInt(), ev.rawY.toInt())
// Always intercept the touch event to handle it in onTouchEvent
return true
}
}
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
childViewIsSwipedHorinzontally = false
//Pass the event to the child view if child is touched
if (childViewIsTouched) {
childViewGroup.dispatchTouchEvent(event)
}
}
MotionEvent.ACTION_MOVE -> {
//Pass the event to the child view if childView is touched Touched and swiped
if (childViewIsTouched && isHorizontalSwipe(event)) {
childViewIsSwipedHorinzontally = true
childViewGroup.dispatchTouchEvent(event)
}
}
//If child view is not swiped consume the event
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
return if (!childViewIsSwipedHorinzontally) {
super.onTouchEvent(event)
}else{
false
}
}
}
return super.onTouchEvent(event)
}
private fun isHorizontalSwipe(event: MotionEvent): Boolean {
val adx = abs(event.x - mLastMotionX)
return adx > touchSlop
}
private fun View.isViewInBounds(x : Int, y :Int): Boolean{
val outRect = Rect()
val location = IntArray(2)
getDrawingRect(outRect)
getLocationOnScreen(location)
outRect.offset(location[0], location[1])
return outRect.contains(x, y)
}
}
Edit: Also you have to remove android:state_pressed="true"
portion of your sbg_white.xml
.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_enabled="false">
<shape android:shape="rectangle">
<solid android:color="#EAEAEA" />
</shape>
</item>
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="#FFF1F1F1"/>
</shape>
</item>
<!-- <item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#FFF1F1F1"/>
</shape>
</item>-->
<item>
<shape android:shape="rectangle">
<solid android:color="#FFF"/>
</shape>
</item>
</selector>
Add logic handling the selection state of parent RV item:
class ParentAdapter: ListAdapter<ParentItem, ParentAdapter.ParentViewHolder>(diffUtil) {
private var selectedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParentViewHolder {
val binding = ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ParentViewHolder(binding).apply{
itemView.setOnClickListener{
val previousSelectedPosition = selectedPosition
selectedPosition = bindingAdapterPosition
notifyItemChanged(previousSelectedPosition)
notifyItemChanged(selectedPosition)
true
}
}
}
override fun onBindViewHolder(holder: ParentViewHolder, position: Int) {
//update selected state accordingly
holder.itemView.isSelected = position == selectedPosition
holder.bind()
}
//rest of your codes
}
If it's necessary to retain the other states of the rv item like the scroll position, you can do partial bind using change payload:
class ParentAdapter: ListAdapter<ParentItem, ParentAdapter.ParentViewHolder>(diffUtil) {
companion object{
private const val PAYLOAD_SELECTED_STATE = "selected_state"
}
private var selectedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParentViewHolder {
val binding = ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ParentViewHolder(binding).apply{
itemView.setOnClickListener{
val previousSelectedPosition = selectedPosition
selectedPosition = bindingAdapterPosition
notifyItemChanged(previousSelectedPosition, PAYLOAD_SELECTED_STATE)
notifyItemChanged(selectedPosition, PAYLOAD_SELECTED_STATE )
true
}
}
}
override fun onBindViewHolder(
holder: NewsViewHolder,
position: Int,
payloads: MutableList<Any>
) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
}else {
for (payload in payloads) {
if (payload == PAYLOAD_SELECTED_STATE) {
//update selected state accordingly
holder.itemView.isSelected = position == selectedPosition
}
}
}
}
override fun onBindViewHolder(holder: ParentViewHolder, position: Int) {
//update selected state accordingly
holder.itemView.isSelected = position == selectedPosition
holder.bind()
}
//rest of your codes
}