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

s1n*_*t3r 23 android listview scroll

好.我想要实现的是一种与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>
Run Code Online (Sandbox Code Playgroud)

然后我在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);      
}
Run Code Online (Sandbox Code Playgroud)

这应该导致左手ListView在用户滚动右手时滚动.不幸的是它没有.

我有一点关于google的事情,似乎setSelectionFromTop()函数不能在嵌套在多个布局中的ListView上运行.

如果是这种情况,任何人都可以建议一种方法来让它们一起滚动或以不同的方式来设置布局或完全不同的技术.

Sam*_*Sam 34

改写

将一个ListView中的滚动操作传递给另一个ListView时,我没有太多运气.所以我选择了另一种方法:传递MotionEvent.这让每个人都可以ListView计算出自己的平滑滚动,快速滚动或其他任何内容.

首先,我们需要一些类变量:

ListView listView;
ListView listView2;

View clickSource;
View touchSource;

int offset = 0;
Run Code Online (Sandbox Code Playgroud)

我添加的每个方法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;
    }
});
Run Code Online (Sandbox Code Playgroud)

为了防止循环逻辑: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
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

第四,通过每一次触摸事件并不完美,因为偶尔会出现差异.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) {}
});
Run Code Online (Sandbox Code Playgroud)

(可选)最后,你提到你有问题,listView并且listView2在你的布局中以不同的高度开始......我强烈建议修改你的布局以平衡ListViews,但我找到了解决这个问题的方法.然而,它有点棘手.
在渲染整个布局之前,你无法计算两个布局之间的高度差异,但此刻没有回调...所以我使用一个简单的处理程序:

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]);
    }
};
Run Code Online (Sandbox Code Playgroud)

假设半秒延迟足以渲染布局并启动计时器onResume():

handler.sendEmptyMessageDelayed(0, 500);
Run Code Online (Sandbox Code Playgroud)

如果你确实使用了一个偏移量我想明确listView2的是OnScroll方法减去偏移而不是添加它:

listView2.setSelectionFromTop(firstVisibleItem, view.getChildAt(0).getTop() - offset);
Run Code Online (Sandbox Code Playgroud)

希望有所帮助!


s1n*_*t3r 8

好.我现在有一个答案.问题是.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>
Run Code Online (Sandbox Code Playgroud)

这基本上是与布局一起使用的代码.

在onCreate()

engListView=getListView();
engListView.setOnTouchListener(this);

recordListView=(ListView)findViewById(R.id.recordList);
recordListView.setOnScrollListener(this);
Run Code Online (Sandbox Code Playgroud)

和监听方法:

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());
}
Run Code Online (Sandbox Code Playgroud)


Gle*_*emi 5

这个帖子帮助我找到了一个类似问题的解决方案,我一直在努力解决这个问题.它还基于拦截触摸事件.在这种情况下,同步适用于多个列表视图,并且完全对称.

主要挑战是阻止列表项单击传播到其他列表视图.我需要仅通过最初接收触摸事件的列表视图调度的点击和长按点击,特别是当您触及项目时突出显示反馈(拦截onClick事件是没有用的,因为它在调用层次结构中为时已晚).

关键是拦截触摸事件两次.首先,初始触摸事件被转发到其他列表视图.相同的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) { }
}
Run Code Online (Sandbox Code Playgroud)

编辑:可以使用状态变量解决双重问题.

private boolean scrolling;

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

public void onScrollStateChanged(AbsListView view, int scrollState) 
{
    scrolling = scrollState != SCROLL_STATE_IDLE;
}
Run Code Online (Sandbox Code Playgroud)