OK. What I'm trying to achieve is a layout that does the same effect as frozen panes in Excel. That is I want a header row that scrolls horizontally with the main ListView and a left hand ListView that scrolls vertically with the main ListView. The header row and the left hand listview should remain stationary when scrolling in the other dimension.

Here is the xml layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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">
    <LinearLayout android:layout_width="160dp"
        android:layout_height="match_parent"
        android:orientation="vertical">

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

<HorizontalScrollView  
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

<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>
</LinearLayout>

I'm then using this code in the ListActivity

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    View v = recordsListView.getChildAt(0);
    int top = (v == null) ? 0 : v.getTop();

((ListView)findViewById(R.id.engNameList)).setSelectionFromTop(firstVisibleItem, top);      
}

This should cause the left hand ListView to scroll when the right hand one is scrolled by the user. Unfortunately it doesn't.

I've had a bit of a google about and it seems the setSelectionFromTop() function will not work on a ListView that is nested inside more than one layout.

If this is the case can anyone suggest a way to get them to scroll together or a different way to set up the layout or a different technique altogether.
android listview scroll
share|improve this question
    
asked Sep 9 '12 at 19:54
s1ni5t3r
5827
    
       
    
Have you tried smootHSCrollToPosition: developer.android.com/reference/android/widget/… –  Waza_Be Sep 9 '12 at 20:05
       
    
Thanks for the quick reply. However this isn't the solution. I am looking to get the listviews to scroll smoothly together. –  s1ni5t3r Sep 9 '12 at 20:34
add comment
3 Answers
active oldest votes
up vote 14 down vote

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!
share|improve this answer
    
edited Dec 28 '12 at 7:04

answered Sep 9 '12 at 20:47
Sam
42.9k73971
    
       
    
Not sure if I'm doing something wrong but although the dispatchTouchEvent is called it doesn't seem to send any touch event to the other listview. OnTouchEvent is not called for the other listview anyway. –  s1ni5t3r Sep 10 '12 at 10:58
       
    
I noticed that isFocused() didn't behave the way I first thought so I switched tactics. Hope this helps. –  Sam Sep 10 '12 at 18:38
       
    
Magic. This is the solution. Works perfectly. Thanks Sam. –  s1ni5t3r Sep 10 '12 at 19:28
       
    
Oh Dear. I spoke too soon. This method works well but if you try to scoot the list (that is you give it another push while it's still in motion) the two lists become disconnected. –  s1ni5t3r Sep 14 '12 at 14:51
1     
    
Thanks Sam, I was expecting a simple ugly solution to get around the initial problem but have come away with an elegant solution that performs perfectly. I'm afraid I don't have the rep to vote your answer up though. –  s1ni5t3r Sep 18 '12 at 17:47
show 4 more comments
up vote 2 down vote

This thread has helped me find a solution for a similar problem I've been struggling with for a while. It is also based on intercepting touch events. In this case the syncing works for multiple listviews and is entirely symmetric.

The major challenge was to prevent list item clicks to propagate to the other listviews. I need clicks and long clicks dispatched only by the listview that initially received the touch event, including in particular the highlight feedback when you just touch down on an item (intercepting onClick event is no use since it's too late in the calling hierarchy).

The key to this is intercepting the touch event twice. First, the initial touch event is relayed to the other listviews. The same onTouch handler function catches these and feeds them to a GestureDetector. In the GestureDetector callbacks the static touch events (onDown etc.) are consumed (return true) whereas the motion gestures aren't (return false) such that they can be further dispatched by the view itself and trigger the scrolling.

Using setPositionFromTop inside onScroll didn't work for me because it makes the scrolling behavior extremely sluggish. OnScroll is used instead to align initial scroll positions as new ListViews are added to the Syncer.

The only problem that persists so far is the one brought up by s1ni5t3r above. If you double-fling the listView then they still become disconnected.

public class ListScrollSyncer
    implements AbsListView.OnScrollListener, OnTouchListener, OnGestureListener
{
    private GestureDetector gestureDetector;
    private Set<ListView>   listSet = new HashSet<ListView>();
    private ListView currentTouchSource;

private int currentOffset = 0;
    private int currentPosition = 0;

public void addList(ListView list)
    {
        listSet.add(list);
        list.setOnTouchListener(this);
        list.setSelectionFromTop(currentPosition, currentOffset);

if (gestureDetector == null)
            gestureDetector = new GestureDetector(list.getContext(), this);
    }

public void removeList(ListView list)
    {
        listSet.remove(list);
    }

public boolean onTouch(View view, MotionEvent event)
    {
        ListView list = (ListView) view;

if (currentTouchSource != null)
        {
            list.setOnScrollListener(null);
            return gestureDetector.onTouchEvent(event);
        }
        else
        {
            list.setOnScrollListener(this);
            currentTouchSource = list;

for (ListView list : listSet)
                if (list != currentTouchSource)
                    list.dispatchTouchEvent(event);

currentTouchSource = null;
            return false;
        }
    }

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
    {
        if (view.getChildCount() > 0)
        {
            currentPosition = view.getFirstVisiblePosition();
            currentOffset   = view.getChildAt(0).getTop();
        }
    }

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

// GestureDetector callbacks
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; }
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; }
    public boolean onSingleTapUp(MotionEvent e) { return true; }
    public boolean onDown(MotionEvent e) { return true; }
    public void onLongPress(MotionEvent e) { }
    public void onShowPress(MotionEvent e) { }
}

edit: the double-fling issue can be resolved by using a state variable.

private boolean scrolling;

public boolean onDown(MotionEvent e) { return !scrolling; }

public void onScrollStateChanged(AbsListView view, int scrollState)
{
    scrolling = scrollState != SCROLL_STATE_IDLE;
}

share|improve this answer
    
edited Jun 10 '13 at 21:36

answered Feb 16 '13 at 13:16
Glemi
506
    
       
    
I'm going to take a shot at implementing this tonight. I'll let you know if it works out. Hopefully you can help me if it doesn't? :) –  Kgrover Apr 1 '13 at 1:54
       
    
Hm, this doesn't seem to work at all :( They get unsynced almost immediately. –  Kgrover Apr 1 '13 at 4:02
       
    
Sorry to hear this Kgrover. Does it get unsynced even if you do not "double-fling" on the list? If not then the edit in the answer above might be a solution - with this the listviews in my app are now very stable. Perhpas it also depends on how fast your phone is (Mine is an S3 and I haven't tested it on any other phone). –  Glemi Jun 10 '13 at 21:41
add comment
up vote 0 down vote accepted

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());
}

Android. Scrolling 2 listviews together的更多相关文章

  1. wesome-android

    awesome-android Introduction android libs from github System requirements Android Notice If the lib ...

  2. 手势识别官方教程(4)在挑划或拖动手势后view的滚动用ScrollView和 HorizontalScrollView,自定义用Scroller或OverScroller

    简单滚动用ScrollView和 HorizontalScrollView就够.自定义view时可能要自定义滚动效果,可以使用 Scroller或 OverScroller Animating a S ...

  3. Android Lint Checks

    Android Lint Checks Here are the current list of checks that lint performs as of Android Studio 2.3 ...

  4. Android ViewPager打造3D画廊

    本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发. 网上有很多关于使用Gallery来打造3D画廊的博客,但是在关于Gallery的官方说法中表明: This cl ...

  5. Android Weekly Notes Issue #221

    Android Weekly Issue #221 September 4th, 2016 Android Weekly Issue #221 ARTICLES & TUTORIALS And ...

  6. Android事件分发机制浅谈(三)--源码分析(View篇)

    写事件分发源码分析的时候很纠结,网上的许多博文都是先分析的View,后分析ViewGroup.因为我一开始理解的时候是按我的流程图往下走的,感觉方向很对,单是具体分析的时候总是磕磕绊绊的,老要跳到Vi ...

  7. [译]:Xamarin.Android开发入门——Hello,Android Multiscreen深入理解

    原文链接:Hello, Android Multiscreen_DeepDive. 译文链接:Xamarin.Android开发入门--Hello,Android Multiscreen深入理解. 本 ...

  8. 讲讲Android事件拦截机制

    简介 什么是触摸事件?顾名思义,触摸事件就是捕获触摸屏幕后产生的事件.当点击一个按钮时,通常会产生两个或者三个事件--按钮按下,这是事件一,如果滑动几下,这是事件二,当手抬起,这是事件三.所以在And ...

  9. Android Material Design 兼容库的使用

    Android Material Design 兼容库的使用 mecury 前言:近来学习了Android Material Design 兼容库,为了把这个弄懂,才有了这篇博客,这里先推荐两篇博客: ...

随机推荐

  1. poj3067

    求交点的个数: 容易发现,对于两条航线(xi,yi)和(xj,yj),设xi<xj 只有yi>yj时两条航线存在交点: 于是我们考虑以x为第一关键字减序,y为第二关键字为减序排序: 则对于 ...

  2. angularJS $resource与后台restapi的对应关系

    REST(表征性状态传输,Representational State Transfer)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格.RESTful风格的设计不仅 ...

  3. python auto send email

    /*************************************************************************** * python auto send emai ...

  4. html的视频插件 (转)

         1)jMedia Element是一个基于jQuery/jQuery UI实现的HTML5音频/视频开发工具包.提供非常多的功能来控制页面中的音频和视频内容.当旧的浏览器不兼容HTML5时, ...

  5. HDU 1495 非常可乐 BFS搜索

    题意:有个为三个杯子(杯子没有刻度),体积为s,n,m,s=m+n, 刚开始只有体积为s的杯子装满可乐,可以互相倒,问你最少的次数使可乐均分,如果没有结果,输出-1; 分析:直接互相倒就完了,BFS模 ...

  6. POJ3414 Pots BFS搜素

    题意:通过题目给出的三种操作,让任意一个杯子中的水到达一定量 分析:两个杯子最大容量是100,所以开个100*100的数组记录状态,最多1w个状态,所以复杂度很低,然后记录一下路径就好 注:代码写残了 ...

  7. mysql数据库基础的简单操作指南

    最近在学习mysql,本文是做的关于mysql学习的笔记,跟大家分享一下,希望对大家学习mysql知识有所助益.mysql现在几乎已经成了网站建设的主流数据库,很多php网站系统都采用了mysql数据 ...

  8. FL2440移植Linux2.6.33.7内核

    kernel version:2.6.33.7 /linux-2.6.33.7 OS:CentOS 6.4 cross-compilation chain:arm-linux-4.3.2 /usr/l ...

  9. bzoj 1834 [ZJOI2010]network 网络扩容(MCMF)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1834 [题意] 给定一个有向图,每条边有容量C,扩容费用W,问最大流和使容量增加K的最 ...

  10. zoj 3537 Cake 区间DP (好题)

    题意:切一个凸边行,如果不是凸包直接输出.然后输出最小代价的切割费用,把凸包都切割成三角形. 先判断是否是凸包,然后用三角形优化. dp[i][j]=min(dp[i][j],dp[i][k]+dp[ ...