Android Custom ListView Example to create Non Scrollable ListView

Filed Under: Android

In this tutorial we’ll override the ListView class to suit it according to our requirements in the android application.

Android Non Scrollable ListView Requirement

A ListView comes up with its own default scrolling methods. Now if we wish to create a layout which contains a ListView along with some other views inside a parent ScrollView then you’ll notice that the scrolling gestures of the ListView don’t work as desired.

The reason for this is that scrolling gestures received by the layout, they are all handled by the parent layout only. One workaround is to add the other views as the headers and footers of the ListView and avoid using ListView and ScrollView. But a more robust option is to create custom ListView class to suit it to our needs by making it non scrollable.

In this tutorial we’ll develop a custom ListView class and use it in a ScrollView with other child views. We’ll use Butterknife to bind the views.

Project Structure

The project consists of a MainActivity and a subclass of ListView named NonScrollListView.

android-nonscroll-listview-project

Code

The NonScrollListView class is given below:


public class NonScrollListView extends ListView {

    public NonScrollListView(Context context) {
        super(context);
    }
    public NonScrollListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
                Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
        ViewGroup.LayoutParams params = getLayoutParams();
        params.height = getMeasuredHeight();
    }
}

onMeasure() allows us to specify how big you want your custom view to be with respect to the layout constraints of the parent class. AT_MOST typically means the layout_width or layout_height value was set to match_parent or wrap_content where a maximum size is needed (this is layout dependent in the framework), and the size of the parent dimension is the value.

The layout of the MainActivity is given below:
activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:fillViewport="true">

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/header_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_alignParentLeft="true"
            android:layout_alignParentRight="true"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:background="#36D2AA"
            android:padding="@dimen/activity_horizontal_margin"
            android:text="Header Button" />

        <com.journaldev.nonscrollablelistview.NonScrollListView
            android:id="@+id/listView"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@+id/header_button"></com.journaldev.nonscrollablelistview.NonScrollListView>

        <Button
            android:id="@+id/button3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@+id/button2"
            android:padding="@dimen/activity_horizontal_margin"
            android:text="Footer Button 3" />

        <Button
            android:id="@+id/button2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@+id/button1"
            android:padding="@dimen/activity_horizontal_margin"
            android:text="Footer Button 2" />

        <Button
            android:id="@+id/button1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@+id/listView"
            android:text="Footer Button 1" />

    </RelativeLayout>
</ScrollView>

The package name of the custom ListView class is set as the tag. The layout consists of four buttons inside a RelativeLayout that’s the child of the a ScrollView.

The MainActivity.java is given below:


package com.journaldev.nonscrollablelistview;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

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

import butterknife.ButterKnife;
import butterknife.InjectView;
import butterknife.OnClick;
import butterknife.OnItemClick;
import butterknife.OnItemSelected;

public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.listView)
    NonScrollListView nonScrollListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);

        final List<String[]> values = new LinkedList<String[]>();
        values.add(new String[]{"Title 1", "Subtitle 1"});
        values.add(new String[]{"Title 2", "Subtitle 2"});
        values.add(new String[]{"Title 3", "Subtitle 3"});
        values.add(new String[]{"Title 4", "Subtitle 4"});
        values.add(new String[]{"Title 5", "Subtitle 5"});
        values.add(new String[]{"Title 6", "Subtitle 6"});
        values.add(new String[]{"Title 7", "Subtitle 7"});
        values.add(new String[]{"Title 8", "Subtitle 8"});

        nonScrollListView.setAdapter(new ArrayAdapter<String[]>(MainActivity.this, android.R.layout.simple_expandable_list_item_2, android.R.id.text1, values) {

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View view = super.getView(position, convertView, parent);

                String[] entry = values.get(position);
                TextView text1 = (TextView) view.findViewById(android.R.id.text1);
                TextView text2 = (TextView) view.findViewById(android.R.id.text2);
                text1.setText(entry[0]);
                text2.setText(entry[1]);

                return view;
            }
        });
    }

    @Nullable
    @OnClick({ R.id.header_button, R.id.button1,R.id.button2 , R.id.button3})
    public void commonMethod() {
        Toast.makeText(getApplicationContext(),"Button is clicked",Toast.LENGTH_SHORT).show();
    }

    @OnItemClick(R.id.listView)
    void onItemClick(int position) {
        Toast.makeText(getApplicationContext(),"Position "+position+" is clicked",Toast.LENGTH_SHORT).show();
    }

}

We’ve used a new default list layout android.R.layout.simple_expandable_list_item_2. It consists of two textViews that can be only invoked using ids : android.R.id.text1 and android.R.id.text2. The ListView is populated using a LinkedList of String arrays.

The output of the application in action is given below:

android-nonscroll-listview-output

Note: Replace the NonScrollListView with a normal ListView in the xml and the code. The following output is returned.

android-nonscroll-listview-output-2

As you can see the ListView default scrolls conflict with the ScrollView’s hence the gestures are not handled properly.

Alternate Way

A workaround to the above problem without creating a custom widget is given below:


listView.setOnTouchListener(new ListView.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction();
            switch (action) {
            case MotionEvent.ACTION_DOWN:
                // Disallow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(true);
                break;

            case MotionEvent.ACTION_UP:
                // Allow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }

            // Handle ListView touch events.
            v.onTouchEvent(event);
            return true;
        }
    });

This brings an end to this tutorial. You can download the final Android Custom Non Scrollable ListView Project from the link below.

Comments

  1. Chanpreet Singh says:

    can we use search option in this ..??

  2. STLer says:

    Great, elegant solution for the ListView+ScrollView phenomena.

    Thank you sir!

  3. Jonathan says:

    Nice article

    1. Anupam says:

      Thanks, Jonathan.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages