Android. Scrolling 2 listviews together

Rewrite

I didn't have much luck with passing the scrolling actions in one ListView to another. So I chose a different method: passing the MotionEvent. This lets each ListView calculate their own smooth scroll, fast scroll, or anything else.

First, we'll need some class variables:

ListView listView;
ListView listView2;

View clickSource;
View touchSource;

int offset = 0;

Every method that I add to listView will be almost identical for listView2, the only difference is that listView2 will reference listView (not itself). I didn't include the repetitive listView2 code.

Second, let's start with the OnTouchListener:

listView = (ListView) findViewById(R.id.engNameList);
listView.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(touchSource == null)
            touchSource = v;

        if(v == touchSource) {
            listView2.dispatchTouchEvent(event);
            if(event.getAction() == MotionEvent.ACTION_UP) {
                clickSource = v;
                touchSource = null;
            }
        }

        return false;
    }
});

To prevent circular logic: listView calls listView2 calls listView calls... I used a class variable touchSource to determine when a MotionEvent should be passed. I assumed that you don't want a row click in listView to also click in listView2, so I used another class variable clickSource to prevent this.

Third, the OnItemClickListener:

listView.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if(parent == clickSource) {
            // Do something with the ListView was clicked
        }
    }
});

Fourth, passing every touch event isn't perfect because occasional discrepancies appear. The OnScrollListener is perfect for eliminating these:

listView.setOnScrollListener(new OnScrollListener() {
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if(view == clickSource) 
            listView2.setSelectionFromTop(firstVisibleItem, view.getChildAt(0).getTop() + offset);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {}
});

(Optional) Lastly, you mentioned that you have trouble since listView and listView2 begin at different heights in your layout... I highly recommend modifying your layout to balance the ListViews, but I found a way to address this. However it is a little tricky.
You cannot calculate the difference in height between the two layouts until after the entire layout have been rendered, but there is no callback for this moment... so I use a simple handler:

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // Set listView's x, y coordinates in loc[0], loc[1]
        int[] loc = new int[2];
        listView.getLocationInWindow(loc);

        // Save listView's y and get listView2's coordinates
        int firstY = loc[1];
        listView2.getLocationInWindow(loc);

        offset = firstY - loc[1];
        //Log.v("Example", "offset: " + offset + " = " + firstY + " + " + loc[1]);
    }
};

I assume that a half second delay is long enough to render the layout and start the timer in onResume():

handler.sendEmptyMessageDelayed(0, 500);

If you do use an offset I want to be clear that listView2's OnScroll method subtracts the offset rather than adds it:

listView2.setSelectionFromTop(firstVisibleItem, view.getChildAt(0).getTop() - offset);

Hope that helps!


OK. I have an answer now. The problem being that .setSelectionFromTop() would only work if the listview was in the top layout (ie. not nested). Afters some head scratching I realised that I could make my layout a RelativeLayout and get the same look but without having to nest layouts for the checkbox and listview. This is the new layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recordViewLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <CheckBox android:id="@+id/checkBoxTop"
        android:text="Check All"
        android:layout_width="160dp"
        android:layout_height="wrap_content"/>


    <ListView android:id="@+id/engNameList"
        android:layout_width="160dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/checkBoxTop"/>       

    <HorizontalScrollView  
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/checkBoxTop">

        <LinearLayout android:id="@+id/scroll"  
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <include layout="@layout/record_view_line" android:id="@+id/titleLine" />

            <ListView 
                android:id="@android:id/list"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"/>

        </LinearLayout>

    </HorizontalScrollView>

</RelativeLayout>

This basically is the code that goes with the layout.

In onCreate()

engListView=getListView();
engListView.setOnTouchListener(this);

recordListView=(ListView)findViewById(R.id.recordList);
recordListView.setOnScrollListener(this);

and the listener methods:

public boolean onTouch(View arg0, MotionEvent event) {
    recordListView.dispatchTouchEvent(event);
    return false;
}

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    View v=view.getChildAt(0);
    if(v != null)
        engListView.setSelectionFromTop(firstVisibleItem, v.getTop());
}