Android系统。 一起滚动2个列表视图

好。 我想要实现的是一个布局,与Excel中的冻结窗格的效果相同。 那是我想要一个水平滚动与主ListView和一个左侧ListView垂直滚动与主ListView的标题行。 在其他维度中滚动时,标题行和左侧列表视图应保持静止。

这里是xml布局:

<?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> 

我然后在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); } 

这应该会导致左侧的ListView在用户滚动右侧的ListView时滚动。 不幸的是,它不。

我有一个谷歌了一下,似乎setSelectionFromTop()函数不能在一个嵌套在多个布局内的ListView上工作。

如果是这种情况,任何人都可以提出一种方法让他们一起滚动或以不同的方式设置布局或完全不同的技术。

改写

将一个ListView中的滚动操作传递给另一个ListView并没有太大的好处。 所以我select了一个不同的方法:传递MotionEvent 。 这让每个ListView计算自己的平滑滚动,快速滚动,或其他任何。

首先,我们需要一些类variables:

 ListView listView; ListView listView2; View clickSource; View touchSource; int offset = 0; 

我添加到listView每个方法对于listView几乎都是相同的,唯一的区别是listView2会引用listView (而不是本身)。 我没有包含重复的listView2代码。

其次,我们从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; } }); 

为了防止循环逻辑: listView调用listView2调用listView调用…我用一个类variablestouchSource来确定何时应该传递一个touchSource 。 我假设你不想在listView中单击一行来点击listView2 ,所以我使用另一个类variablesclickSource来防止这种情况。

三, 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 } } }); 

第四,传递每一个触摸事件并不完美,因为偶尔会出现差异。 OnScrollListener是完美的消除这些:

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

(可选)最后,你提到你有麻烦,因为listViewlistView2开始在布局中的不同高度…我强烈build议修改您的布局来平衡ListViews,但我find了解决这个问题的方法。 不过这有点棘手。
你不能计算两个布局之间的高度差异,直到整个布局已经被渲染,但是这个时候没有callback…所以我使用一个简单的处理程序:

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

假设半秒延迟足够长的时间来呈现布局,并启动onResume()的计时器:

 handler.sendEmptyMessageDelayed(0, 500); 

如果你使用偏移我想清楚的是listView2的OnScroll方法减去偏移量而不是添加它:

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

希望有所帮助!

这个线程帮助我find了一个我一直在努力的类似问题的解决scheme。 它也基于拦截触摸事件。 在这种情况下,同步适用于多个列表视图,并且是完全对称的。

主要的挑战是阻止列表项点击传播到其他列表视图。 我只需要通过最初接收触摸事件的列表视图来发送点击和长时间点击,特别是当您触碰某个项目时,特别包括突出显示反馈(由于在调用层次结构中已经太迟了,拦截onClick事件是无用的)。

关键是两次拦截触摸事件。 首先,将初始触摸事件转发给其他列表视图。 相同的onTouch处理程序函数捕获这些函数并将其提供给GestureDetector。 在GestureDetectorcallbackGestureDetector ,静态触摸事件( onDown等)被消耗( return true ),而运动手势不是( return false ),这样它们可以被视图本身进一步调度并触发滚动。

onScroll使用setPositionFromTop不适合我,因为它使滚动行为非常缓慢。 当新的ListView被添加到Syncer时,OnScroll被用来alignment初始的滚动位置。

迄今为止唯一存在的问题是上面s1ni5t3r提出的问题。 如果你双击listView然后他们仍然断开连接。

 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) { } } 

编辑:双重问题可以通过使用状态variables来解决。

 private boolean scrolling; public boolean onDown(MotionEvent e) { return !scrolling; } public void onScrollStateChanged(AbsListView view, int scrollState) { scrolling = scrollState != SCROLL_STATE_IDLE; } 

好。 我现在有一个答案。 问题是.setSelectionFromTop()只会在列表视图在顶部布局(即不嵌套)。 除了一些头挠我意识到,我可以使我的布局RelativeLayout并获得相同的外观,但不必嵌套checkbox和列表视图的布局。 这是新的布局:

  <?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> 

这基本上是与布局的代码。

在onCreate()

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

和侦听器方法:

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

我通过将ListView更改为RecyclerView来解决它,使用RecyclerView .addOnScrollListener同步滚动事件。 而且没有差异,这是完美的!

 private void syncScrollEvent(RecyclerView leftList, RecyclerView rightList) { leftList.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return rightList.getScrollState() != RecyclerView.SCROLL_STATE_IDLE; } }); rightList.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return leftList.getScrollState() != RecyclerView.SCROLL_STATE_IDLE; } }); leftList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { rightList.scrollBy(dx, dy); } } }); rightList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { leftList.scrollBy(dx, dy); } } }); } 

根据Sam的指示,我开发了一个简单的应用程序,将两个列表视图一起滚动。 其实我的主要目的是创build一个水平滚动可扩展的列表视图与固定/冻结列,并在完成后,将尽快分享它https://github.com/huseyinDaVinci/Android/tree/master/FrozenListViews

尝试下面的解决scheme在我的情况下工作

 leftlist.setOnScrollListener(new SyncedScrollListener(rightlist)); rightlist.setOnScrollListener(new SyncedScrollListener(leftlist)); 

SyncedScrollListener.java

 package com.xorbix.util; import android.view.View; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; public class SyncedScrollListener implements OnScrollListener{ int offset; int oldVisibleItem = -1; int currentHeight; int prevHeight; private View mSyncedView; public SyncedScrollListener(View syncedView){ if(syncedView == null){ throw new IllegalArgumentException("syncedView is null"); } mSyncedView = syncedView; } public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int[] location = new int[2]; if(visibleItemCount == 0){ return; } if(oldVisibleItem != firstVisibleItem){ if(oldVisibleItem < firstVisibleItem){ prevHeight = currentHeight; currentHeight = view.getChildAt(0).getHeight(); offset += prevHeight; }else{ currentHeight = view.getChildAt(0).getHeight(); View prevView; if((prevView = view.getChildAt(firstVisibleItem - 1)) != null){ prevHeight = prevView.getHeight(); }else{ prevHeight = 0; } offset -= currentHeight; } oldVisibleItem = firstVisibleItem; } view.getLocationOnScreen(location); int listContainerPosition = location[1]; view.getChildAt(0).getLocationOnScreen(location); int currentLocation = location[1]; int blah = listContainerPosition - currentLocation + offset; mSyncedView.scrollTo(0, blah); } public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO Auto-generated method stub } }