Подтвердить что ты не робот

Android. Прокрутка 2 списка вместе

OK. То, что я пытаюсь достичь, - это макет, который делает тот же эффект, что и замороженные панели в Excel. То есть я хочу, чтобы строка заголовка прокручивалась горизонтально с помощью главного ListView и левой ListView List, которая прокручивается по вертикали с помощью основного 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 будет прокручиваться, когда пользователь прокручивает правую руку. К сожалению, это не так.

У меня было немного о Google, и кажется, что функция setSelectionFromTop() не будет работать в ListView, который вложен внутри более чем одного макета.

Если это так, может кто-нибудь предложить способ заставить их прокручиваться вместе или по-другому настроить макет или другую технику вообще.

4b9b3361

Ответ 1

OK. У меня есть ответ. Проблема в том, что .setSelectionFromTop() будет работать, только если listview находится в верхнем макете (т.е. Не вложен). После некоторой царапины на голове я понял, что смогу сделать свой макет 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());
}

Ответ 2

Rewrite

Мне не повезло с передачей действий прокрутки в один 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 вызывает вызовы 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
        }
    }
});

В-четвертых,, проходящее каждое событие касания, не идеально, потому что появляются случайные расхождения. 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 начинаются с разных высот в вашем макете... Я высоко рекомендую модифицировать ваш макет, чтобы сбалансировать ListViews, но я нашел способ решить эту проблему. Однако это немного сложно.
Вы не можете рассчитать разницу в высоте между двумя макетами до тех пор, пока не будет отображен весь макет, но на этот момент нет обратного вызова... поэтому я использую простой обработчик:

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // Set listView x, y coordinates in loc[0], loc[1]
        int[] loc = new int[2];
        listView.getLocationInWindow(loc);

        // Save listView y and get listView2 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);

Надеюсь, что это поможет!

Ответ 3

Этот поток помог мне найти решение для аналогичной проблемы, с которой я боролся некоторое время. Он также основан на перехвате событий касания. В этом случае синхронизация работает для нескольких списков и полностью симметрична.

Основная проблема заключалась в том, чтобы запретить клики элемента списка распространяться в другие списки. Мне нужны клики и длинные клики, отправленные только списком, который первоначально получил событие касания, включая, в частности, обратную связь, когда вы просто касаетесь элемента (перехват события onClick бесполезен, поскольку он слишком поздно в вызывающей иерархии).

Ключом к этому является перехват события касания дважды. Во-первых, начальное событие касания передается другим спискам. Такая же функция обработчика onTouch захватывает их и передает их в GestureDetector. В обратных вызовах GestureDetector потребляются события статического касания (onDown и т.д.) (return true), тогда как жесты движения не являются (return false), так что они могут быть дополнительно отправлены самим представлением и запускают прокрутки.

Использование setPositionFromTop внутри onScroll не работало для меня, потому что это делает поведение прокрутки крайне вялым. Вместо этого OnScroll используется для выравнивания начальных позиций прокрутки, когда в Syncer добавляются новые ListView.

Единственная проблема, которая сохраняется до сих пор, - это поднятая выше 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) { }
}

edit: проблема с двойным щелчком может быть решена с помощью переменной состояния.

private boolean scrolling;

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

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

Ответ 4

Я разрешаю его, изменяя 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);
            }
        }
    });

}

Ответ 5

Согласно инструкциям Сэма, я разработал простое приложение, показывающее прокрутку двух списков вместе. На самом деле, моя главная цель - создать горизонтальный прокручиваемый расширяемый список с фиксированными/замороженными столбцами и работая над ним по завершении, скоро поделится им https://github.com/huseyinDaVinci/Android/tree/master/FrozenListViews

Ответ 6

Попробуйте решение ниже, оно работает в моем случае

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

    }
}