Search code examples
androidlistviewontouchlistenerandroid-cardview

Android CardView with ListView inside - onTouchListener on CardView not working


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.


Solution

  • 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.