Android获取ListView中精确的滚动位置

40

我想要获取ListView滚动的精确像素位置。

不,我并不是指第一个可见的位置。

有没有办法实现这个功能呢?


你提供的方法解决了不同的问题 - 如何恢复ListView的位置,而我的问题是如何获取滚动位置的实际值。 - saarraz1
8个回答

81

好的,我找到了一个解决方法,使用以下代码:

View c = listview.getChildAt(0);
int scrolly = -c.getTop() + listview.getFirstVisiblePosition() * c.getHeight();
它的工作原理是它获取第一个可见列表项的实际偏移量,并计算其距视图顶部的距离,以确定我们滚动了多少进入视图,现在我们知道了这一点,就可以使用常规的getFirstVisiblePosition方法来计算剩余部分。

97
只有在您的列表项高度相同时,这个说法才是准确的。 - Paul Lammertsma
6
如果ListView有一个标题(header),该怎么办? - jonah_w
我相信它应该可以工作(如果标题与项目具有相同的高度)- 因为标题对于列表视图本身是透明的 - 它们是由列表视图在setAdapter时生成的包装器适配器创建的,并且随后像任何其他视图一样处理。 - saarraz1
非常有用的检查简单列表视图scrollY位置。 - speedynomads
我喜欢chet提供的链接/评论指向另一个问题,但我最喜欢的答案是这个:https://dev59.com/dXA75IYBdhLWcg3w8-HZ#3035521 - Josh
1
我需要知道相对滚动位置,以确定它是向上还是向下滚动,因此使用了 Math.max,代码如下:View firstView = view.getChildAt(0); maxHeight = Math.max(maxHeight, firstView.getHeight()); int y = -firstView.getTop() + view.getFirstVisiblePosition() * maxHeight; - ViliusK

29

Saarraz1的回答只适用于ListView中所有行的高度相同且没有标题(或标题与行高度相同)的情况。

请注意,一旦行在屏幕顶部消失,你将无法访问它们,也就是说,你将无法追踪它们的高度。这就是为什么需要保存这些高度(或者所有高度的累积值)。我的解决方案需要保持一个按索引分组的高度字典(假定在第一次显示列表时滚动到顶部)。

private Dictionary<Integer, Integer> listViewItemHeights = new Hashtable<Integer, Integer>();

private int getScroll() {
    View c = listView.getChildAt(0); //this is the first visible row
    int scrollY = -c.getTop();
    listViewItemHeights.put(listView.getFirstVisiblePosition(), c.getHeight());
    for (int i = 0; i < listView.getFirstVisiblePosition(); ++i) {
        if (listViewItemHeights.get(i) != null) // (this is a sanity check)
            scrollY += listViewItemHeights.get(i); //add all heights of the views that are gone
    }
    return scrollY;
}

2
当然,这仅适用于用户手动滚动所有列表项的情况。如果您以编程方式设置列表位置,则此方法不起作用。 - Tom anMoney
1
视图c始终返回null。 - Olkunmustafa
1
我建议使用SparseIntArray或Map(HashMap)代替Hashtable以获得更好的性能。在这种情况下,Hashtable的同步行为是无用的。 - Piovezan
请注意,如果选择这种方法,您需要在 onScroll 上调用 getScroll。 - Danedo
这会导致滚动时出现很多抖动。 - Shubham Chaudhary
显示剩余2条评论

20

我能想到最简单的想法是扩展ListView并公开默认情况下受保护的“computeVerticalScrollOffset”,然后在你的xml布局中使用“com.your.package.CustomListView”。

public class CustomListView extends ListView {

    public CustomListView(Context context) {
        super(context);
    }

    public CustomListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public int computeVerticalScrollOffset() {
        return super.computeVerticalScrollOffset();
    }
}

这是一个不错的技巧,但不幸的是结果以一些奇怪的单位表示,例如当标题部分可见时,computeVerticalScrollOffset() 报告此标题视图展开的百分比。当我滚动更多时,结果单调增长,但我不确定如何从(虚拟)第一个标题的顶部将其转换为像素。 - Alex Cohn
computeVerticalScrollOffset是相对于“可见”列表中的第一项计算的。您需要了解ListView计算它的方式。您还记得Adapter中的getView(int position, View convertView, ViewGroup parent)方法吗?从列表顶部弹出旧项目并转换为底部的项目,反之亦然。您可以使用getFirstVisiblePosition()方法。y=super.computeVerticalScrollOffset(); if(listView.getFirstVisiblePosition() > 0){ y += (listView.getFirstVisiblePosition()-1)*listItemHeight+headerHeight; } - El'
请提供需要翻译的内容。 - El'

12

首先声明一个整型变量来保存位置。

int position = 0;

然后将 scrollListener 添加到您的 ListView 中,

listView.setOnScrollListener(new OnScrollListener() {

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // TODO Auto-generated method stub

        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
             position = firstVisibleItem;

        }
    });

然后,当您获得新数据或数据发生任何更改时,您需要设置ListView的当前位置。

listView.setSelection(position);

我已经使用了适配器进行设置,对我来说运行良好。


1
这并没有回答 OP 的问题,但它是一个简单的解决方案,可以根据其先前的位置设置列表视图的滚动位置,而不受单元格大小的影响。 - Grux
简单而稳定的解决方案,用于管理ListView的滚动位置。在重新创建ListView后,我们会丢失其位置。使用此解决方案,我已经修复了这个问题。 - spellsleeper

9

我差不多要计算ListView的位置了。希望它能够正常工作,我明天会试一下。 - Olkunmustafa
1
这里是如何使用它的链接 - Sarasranglt

2

我知道我有点晚了,但我想分享一下我解决这个问题的方法。我有一个ListView,我试图找出我已经滚动了多少,以便相对于它滚动其他内容并引起视差效果。以下是我的解决方案:

public abstract class OnScrollPositionChangedListener implements AbsListView.OnScrollListener {
    int pos;
    int prevIndex;
    int prevViewPos;
    int prevViewHeight;
    @Override
    public void onScroll(AbsListView v, int i, int vi, int n) {
        try {
            View currView = v.getChildAt(0);
            int currViewPos = Math.round(currView.getTop());
            int diffViewPos = prevViewPos - currViewPos;
            int currViewHeight = currView.getHeight();
            pos += diffViewPos;
            if (i > prevIndex) {
                pos += prevViewHeight;
            } else if (i < prevIndex) {
                pos -= currViewHeight;
            }
            prevIndex = i;
            prevViewPos = currViewPos;
            prevViewHeight = currViewHeight;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            onScrollPositionChanged(pos);
        }
    }
    @Override public void onScrollStateChanged(AbsListView absListView, int i) {}
    public abstract void onScrollPositionChanged(int scrollYPosition);
}

我创建了自己的OnScrollListener,其中onScrollPositionChanged方法将在每次调用onScroll时被调用。但是这个方法将访问表示ListView已滚动量的计算值。
要使用这个类,您可以将setOnClickListener设置为新的OnScrollPositionChangedListener并覆盖onScrollPositionChanged方法。
如果您需要使用onScroll方法进行其他操作,则也可以覆盖该方法,但是您需要调用super.onScroll以使onScrollPositionChanged正常工作。
myListView.setOnScrollListener(
    new OnScrollPositionChangedListener() {
        @Override
        public void onScroll(AbsListView v, int i, int vi, int n) {
            super.onScroll(v, i, vi, n);
            //Do your onScroll stuff
        }
        @Override
        public void onScrollPositionChanged(int scrollYPosition) {
            //Enjoy having access to the amount the ListView has scrolled
        }
    }
);

谢谢!我有同样的想法,我复制并粘贴了你的示例。但对我来说,这并不完全有效,当我进行高速硬测试时会出现偏移。 - fingerup
谢谢!你能否在你的代码中添加注释来解释你所做的事情? - Badr At

2
除了@jaredpetker的答案之外,还有以下内容。 ListView在滚动时不能容纳所有的项目,因此您只需要操作“可见”部分的列表。当您向下滚动时,顶部项目会被移出并推送为新的项目视图。这就是转换视图的来源(它不是用于填充的空项,而是超出列表“可见”部分的移动项)。因此,您需要知道在可见部分之前有多少个项目,将它们与ListItemHeight相乘并加上headerHeight,这样就可以得到滚动中的实际绝对偏移量。如果没有头文件,则位置0将是listItem,因此您可以简化absoluteY += pos*listItemHeight;
public class CustomListView extends ListView {

private int listItemHeight = 140;
private int headerHeight = 200;

public CustomListView(Context context) {
    super(context);
}

public CustomListView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomListView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

@Override
public int computeVerticalScrollOffset() {
    final int y = super.computeVerticalScrollOffset();
    int absoluteY = y;
    int pos = getFirstVisiblePosition();
    if(pos > 0){ 
       absoluteY += (pos-1)*listItemHeight+header‌​Height;
    }
    //use absoluteY
    return y;
}

1
我是一位有用的助手,可以为您翻译文本。
我曾经遇到过类似的问题,即想要将垂直滑块放置在当前滚动值的ListView上。因此,我有自己的解决方案,如下所示。
首先创建一个类:
public abstract class OnScrollPositionChangedListener implements AbsListView.OnScrollListener {
    int pos;
    public  int viewHeight = 0;
    public  int listHeight = 0;

    @Override
    public void onScroll(AbsListView v, int i, int vi, int n) {
        try {
            if(viewHeight==0) {
                viewHeight = v.getChildAt(0).getHeight();
                listHeight = v.getHeight();

            }
            pos = viewHeight * i;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            onScrollPositionChanged(pos);
        }
    }
    @Override public void onScrollStateChanged(AbsListView absListView, int i) {}
    public abstract void onScrollPositionChanged(int scrollYPosition);
}

然后在主活动(Main Activity)中像这样使用它。
public class MainActivity extends AppCompatActivity {
    SeekBar seekBar;
    ListView listView;
    OnScrollPositionChangedListener onScrollPositionChangedListener = new OnScrollPositionChangedListener() {
        @Override
        public void onScroll(AbsListView v, int i, int vi, int n) {
            super.onScroll(v, i, vi, n);
            //Do your onScroll stuff
        }

        @Override
        public void onScrollPositionChanged(int scrollYPosition) {
            //Enjoy having access to the amount the ListView has scrolled

            seekBar.setProgress(scrollYPosition);


        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        seekBar = (SeekBar) findViewById(R.id.mySeekBar);
        listView = (ListView) findViewById(R.id.listView);
        final String[] values = new String[]{"Android List View",
                "Adapter implementation",
                "Simple List View In Android",
                "Create List View Android",
                "Android Example",

        };

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, android.R.id.text1, values);
        listView.setAdapter(adapter);
        listView.setOnScrollListener(onScrollPositionChangedListener);



        seekBar.postDelayed(new Runnable() {
            @Override
            public void run() {
                seekBar.setMax((onScrollPositionChangedListener.viewHeight * values.length) - onScrollPositionChangedListener.listHeight);
            }
        }, 1000);
        seekBar.setEnabled(false);

    }
}

在应用程序 Gradle 中。
compile 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:0.7.0'

在XML布局中
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.centsol.charexamples.MainActivity">


    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fadeScrollbars="false"


        android:scrollbars="none"

        android:layout_alignParentTop="true"

        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true">

    </ListView>
    <!-- This library requires pair of the VerticalSeekBar and VerticalSeekBarWrapper classes -->
    <com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper
        android:layout_width="wrap_content"

        android:layout_alignParentRight="true"
        android:layout_height="match_parent">
        <com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar
            android:id="@+id/mySeekBar"
            android:layout_width="0dp"
            android:progressDrawable="@drawable/progress"
            android:thumb="@drawable/thumb"
            android:layout_height="0dp"
            android:splitTrack="false"

            app:seekBarRotation="CW90" /> <!-- Rotation: CW90 or CW270 -->
    </com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper>
    <View
        android:layout_width="1dp"
        android:visibility="visible"
        android:layout_alignParentRight="true"
        android:layout_marginTop="16dp"
        android:layout_marginRight="2.5dp"
        android:layout_marginBottom="16dp"
        android:background="@android:color/black"
        android:layout_height="match_parent" />
</RelativeLayout>

背景 XML

    <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

    <solid android:color="@android:color/transparent"/>

</shape>

填充XML。
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

<solid android:color="@android:color/transparent" />


</shape>

进度 XML
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@android:id/background"
        android:drawable="@drawable/background"/>
    <item android:id="@android:id/progress">
        <clip android:drawable="@drawable/fill" />
    </item>

</layer-list>

thumb xml (缩略图 XML)
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval" >


<solid android:color="@android:color/holo_red_dark" />
    <size
        android:height="5dp"
        android:width="5dp" />

</shape>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接