web-dev-qa-db-ja.com

アンドロイド。 2つのリストビューを一緒にスクロールする

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では機能しないようです。

これが事実である場合、誰もがそれらを一緒にスクロールさせる方法、またはレイアウトをセットアップする別の方法または別の方法を完全に提案することができます。

24
s1ni5t3r

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

書き換え

ある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);
_

お役に立てば幸いです。

34
Sam

このスレッドは、しばらくの間苦労してきた同様の問題の解決策を見つけるのに役立ちました。また、傍受タッチイベントに基づいています。この場合、同期は複数のリストビューで機能し、完全に対称です。

主な課題は、リストアイテムのクリックが他のリストビューに伝達されないようにすることでした。特にアイテムをタッチダウンしたときのハイライトフィードバックなど、最初にタッチイベントを受け取ったリストビューによってのみクリックとロングクリックがディスパッチされる必要があります(呼び出し階層で遅すぎるため、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;
}
5
Glemi

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

}
3
Peng Lin

サムの指示に従って、私は2つのリストビューを一緒にスクロールして表示するシンプルなアプリを開発しました。実際、私の主な目的は、固定/固定された列を使用して水平方向にスクロールする拡張可能なリストビューを作成し、終了時にそれを処理することです。すぐに共有されます https://github.com/huseyinDaVinci/Android/tree/master/FrozenListViews

1
huseyin

以下の解決策を試してくださいそれは私の場合に機能します

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

    }
}
0
Rhn Bhadani