24

わかった。私が達成しようとしているのは、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 では機能しないようです。

この場合、それらを一緒にスクロールさせる方法、レイアウトを設定する別の方法、または別の手法を一緒に提案することができます。

4

6 に答える 6

34

リライト

ある ListView のスクロール アクションを別の ListView に渡すのはうまくいきませんでした。そこで、別の方法を選択しましたMotionEvent。これにより、それぞれListViewが独自のスムーズ スクロール、高速スクロールなどを計算できます。

まず、いくつかのクラス変数が必要です:

ListView listView;
ListView listView2;

View clickSource;
View touchSource;

int offset = 0;

私が追加するすべてのメソッドは、listViewほとんど同じですlistView2。唯一の違いは、(それ自体ではなく)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ます。行のクリックでもクリックしたくないと思ったので、これを防ぐために別のクラス変数を使用しました。listViewtouchSourceMotionEventlistViewlistView2clickSource

第三に、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) {}
});

(オプション) 最後に、レイアウトのさまざまな高さから開始してから問題が発生したとおっしゃいました... ListViews のバランスを取るためにレイアウトを変更することをlistView強くお勧めしますが、これに対処する方法を見つけましたただし、少しトリッキーです。 レイアウト全体がレンダリングされるまで、2 つのレイアウトの高さの差を計算することはできませんが、現時点ではコールバックがありません...そのため、単純なハンドラーを使用します。listView2

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

0.5 秒の遅延は、レイアウトをレンダリングしてタイマーを開始するのに十分な長さであると想定onResume()しています。

handler.sendEmptyMessageDelayed(0, 500);

listView2オフセットを使用する場合は、 OnScroll メソッドがオフセットを追加するのではなく、オフセットを減算することを明確にしたいと思います。

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

それが役立つことを願っています!

于 2012-09-09T20:47:28.503 に答える
8

わかった。私は今答えを持っています。.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());
}
于 2012-09-14T16:08:09.890 に答える
5

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

主な課題は、リストアイテムのクリックが他のリストビューに伝播するのを防ぐことでした。タッチイベントを最初に受信したリストビューによってのみディスパッチされるクリックとロングクリックが必要です。これには、特にアイテムをタッチダウンしたときのハイライトフィードバックが含まれます(onClickイベントのインターセプトは、呼び出し階層では遅すぎるため使用できません)。

これの鍵は、タッチイベントを2回インターセプトすることです。まず、最初のタッチイベントが他のリストビューに中継されます。同じonTouchハンドラー関数がこれらをキャッチし、GestureDetectorにフィードします。コールバックではGestureDetector、静的タッチイベント(onDownなど)が消費されますが(return true)、モーションジェスチャは消費されないため(return false)、ビュー自体によってさらにディスパッチされ、スクロールがトリガーされます。

setPositionFromTop内部を使用onScrollすると、スクロール動作が非常に遅くなるため、うまくいきませんでした。代わりに、新しいListViewがSyncerに追加されたときに、最初のスクロール位置を揃えるためにOnScrollが使用されます。

これまでに続く唯一の問題は、上記の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) { }
}

編集:状態変数を使用することで、ダブルフリングの問題を解決できます。

private boolean scrolling;

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

public void onScrollStateChanged(AbsListView view, int scrollState) 
{
    scrolling = scrollState != SCROLL_STATE_IDLE;
}
于 2013-02-16T13:16:47.727 に答える
3

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

}
于 2016-09-06T09:16:02.257 に答える