Pranoy C Pranoy C - 6 months ago 111
Android Question

Android CardView with ListView inside - onTouchListener on CardView not working

Here's a test project which shows the issue dropbox link:
https://www.dropbox.com/sh/8s3v9ydcj6jvpl8/AACZ2VRP2N9R1ec7pxrsAn0ga?dl=0

I have a cardview with a listview inside. I will need the click item in list view too and I want to be able to move the entire cardview using the ontouchlistener too.
I have set a onTouchListener on the cardview but it doesn't work.

Code:

Put this in build.gradle:

compile 'com.android.support:cardview-v7:22.0+'


MainActivity:

package com.XXXXXXXX.testontouch;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.CardView;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

private ListView mylistview;
private CardView mycardview;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mycardview = (CardView)findViewById(R.id.mycardview);
mylistview = (ListView) findViewById(R.id.myListView);

List<String> your_array_list = new ArrayList<String>();
your_array_list.add("foo");
your_array_list.add("bar");
your_array_list.add("foo");
your_array_list.add("bar");
your_array_list.add("foo");
your_array_list.add("bar");
your_array_list.add("foo");
your_array_list.add("bar");
your_array_list.add("foo");
your_array_list.add("bar");
your_array_list.add("foo");
your_array_list.add("bar");
your_array_list.add("foo");
your_array_list.add("bar");


ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
your_array_list );

mylistview.setAdapter(arrayAdapter);
mycardview.setCardElevation(20);
mycardview.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {

System.out.println("TOuchedddd");
return true;
}
});

}
}


The TOuchedddd never prints.
If I remove the ListView from the cardview, then it starts working.

So somehow the listView is consuming the touch events instead of the parent cardview first.

XML for MainActivity:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.pranapps.testontouch.MainActivity">



<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/mycardview"
card_view:cardUseCompatPadding="true">

<ListView
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="@+id/myListView"
android:dividerHeight="0.2dp"
android:overScrollMode="always"
android:smoothScrollbar="true"
android:groupIndicator="@null"
></ListView>


</android.support.v7.widget.CardView>

</RelativeLayout>


I have tried both true and false for the android:clickable, android:focusable and android:focusableInTouchMode. no luck.

Please help with suggestions to try!
Thanks!

EDIT: Before downvoting, please at least leave a comment on why you are downvoting.

Answer

In Android, touch events bubble up from child to parents as you would expect. However, a parent can choose to intercept all touch events targeted for one of its children and decide to veto dispatch of the event to the child. This is exactly what you want, if I understand correctly your touch event should be called whatever happen when you touch your cardview, and then you dispatch the touch event to your child listview if needed.

To intercept the touch event from the CardView, you need to create a custom view that subclass it and override the onInterceptTouchEvent method:

package com.pranapps.testontouch;

import android.content.Context;
import android.util.AttributeSet;
import android.support.v7.widget.CardView;
import android.view.MotionEvent;
import android.view.View;

public class CardViewTouchThief extends CardView {

    public CardViewTouchThief(Context context) {
        super(context);
    }
    public CardViewTouchThief(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public CardViewTouchThief(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override 
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /* 
        * This method determines whether we want to intercept the motion. 
        * If we return true, onTouchEvent will be called. 
        */ 
        return true; 
    } 

} 

Then you use CardViewTouchThief where you would normally use your CardView in your XML layout:

<com.pranapps.testontouch.CardViewTouchThief
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/mycardview"
card_view:cardUseCompatPadding="true">

    <ListView
    android:layout_width="match_parent"
    android:layout_height="fill_parent"
    android:id="@+id/myListView"
    android:dividerHeight="0.2dp"
    android:overScrollMode="always"
    android:smoothScrollbar="true"
    android:groupIndicator="@null"/>

</com.pranapps.testontouch.CardViewTouchThief>

And in your activity put your own logic to handle whenever you want the touch event to be dispatch to the listview.

mycardview.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            Log.d("MainActivity", "TOuchedddd");
            if(mylistview!=null){
                //Route all touch event to listview without logic
                mylistview.onTouchEvent(event);
            }
            return true;
        }
    });

Here is the fixed project source code.