OK。私が達成しようとしているのは、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);
}
これにより、ユーザーが右側のリストビューをスクロールすると、左側のリストビューがスクロールします。残念ながらそうではありません。
私は少しグーグルのことをしてきましたが、setSelectionFromTop()関数は複数のレイアウト内にネストされているListViewでは機能しないようです。
これが事実である場合、誰もがそれらを一緒にスクロールさせる方法、またはレイアウトをセットアップする別の方法または別の方法を完全に提案することができます。
OK。今答えがあります。 .setSelectionFromTop()が機能するのは、リストビューがトップレイアウト(ネストされていない)である場合のみです。頭をひっかいた後、レイアウトをRelativeLayoutにして同じ外観にすることができることに気付きましたが、チェックボックスとリストビューのレイアウトをネストする必要はありません。これは新しいレイアウトです:
<?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のスクロールアクションを別のListViewに渡すことには、あまり運がありませんでした。そこで、MotionEvent
を渡すという別の方法を選択しました。これにより、各ListView
は、独自のスムーズスクロール、高速スクロール、またはその他のものを計算できます。
まず、いくつかのクラス変数が必要です。
_ListView listView;
ListView listView2;
View clickSource;
View touchSource;
int offset = 0;
_
listView
に追加するすべてのメソッドは、_listView2
_でもほぼ同じです。唯一の違いは、_listView2
_がlistView
(それ自体ではない)を参照することです。繰り返しの_listView2
_コードは含めませんでした。
2番目 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
呼び出し...クラス変数touchSource
を使用して、MotionEvent
がいつ渡される。 listView
の行クリックで_listView2
_もクリックしたくないと思ったので、これを防ぐために別のクラス変数clickSource
を使用しました。
番目 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
}
}
});
_
4番目タッチイベントをすべて渡すことは、時折矛盾が生じるため、完璧ではありません。 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) {}
});
_
(オプション)最後にlistView
と_listView2
_がレイアウトの異なる高さで始まるので問題があると述べました... I highlyレイアウトを変更してリストビューのバランスをとることをお勧めしますが、これに対処する方法を見つけました。ただし、少し注意が必要です。
レイアウト全体がレンダリングされるまで、2つのレイアウト間の高さの差を計算することはできませんが、この瞬間のコールバックはありません...したがって、簡単なハンドラーを使用します:
_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]);
}
};
_
私assumeは、レイアウトをレンダリングしてタイマーを開始するのに十分な0.5秒の遅延がonResume()
であると想定しています:
_handler.sendEmptyMessageDelayed(0, 500);
_
オフセットを使用する場合、_listView2
_のOnScrollメソッドがオフセットを追加するのではなく減算することを明確にしたいと思います。
_listView2.setSelectionFromTop(firstVisibleItem, view.getChildAt(0).getTop() - offset);
_
お役に立てば幸いです。
このスレッドは、しばらくの間苦労してきた同様の問題の解決策を見つけるのに役立ちました。また、傍受タッチイベントに基づいています。この場合、同期は複数のリストビューで機能し、完全に対称です。
主な課題は、リストアイテムのクリックが他のリストビューに伝達されないようにすることでした。特にアイテムをタッチダウンしたときのハイライトフィードバックなど、最初にタッチイベントを受け取ったリストビューによってのみクリックとロングクリックがディスパッチされる必要があります(呼び出し階層で遅すぎるため、onClickイベントのインターセプトは使用できません)。
これの鍵は、タッチイベントを2回インターセプトすることです。まず、最初のタッチイベントが他のリストビューに中継されます。同じonTouchハンドラー関数がこれらをキャッチし、GestureDetectorにフィードします。 GestureDetector
コールバックでは、静的タッチイベント(onDown
など)が消費されます(return true
)に対して、モーションジェスチャーは(return false
)ビュー自体によってさらにディスパッチされ、スクロールをトリガーできるようにします。
setPositionFromTop
の内部でonScroll
を使用しても、スクロール動作が非常に遅くなるため、うまくいきませんでした。代わりにOnScrollを使用して、新しいListViewがSyncerに追加されるときに初期スクロール位置を揃えます。
これまでに残っている唯一の問題は、上記のs1ni5t3rによって引き起こされた問題です。 listViewを2回たたくと、接続が解除されます。
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) { }
}
編集:二重の問題は状態変数を使用して解決できます。
private boolean scrolling;
public boolean onDown(MotionEvent e) { return !scrolling; }
public void onScrollStateChanged(AbsListView view, int scrollState)
{
scrolling = scrollState != SCROLL_STATE_IDLE;
}
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);
}
}
});
}
サムの指示に従って、私は2つのリストビューを一緒にスクロールして表示するシンプルなアプリを開発しました。実際、私の主な目的は、固定/固定された列を使用して水平方向にスクロールする拡張可能なリストビューを作成し、終了時にそれを処理することです。すぐに共有されます https://github.com/huseyinDaVinci/Android/tree/master/FrozenListViews
以下の解決策を試してくださいそれは私の場合に機能します
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
}
}