10

抱歉标题混乱,我不能非常简洁地表达这个问题......

我有一个带有 ListView 的 Android 应用程序,它使用圆形/“无限”适配器,这基本上意味着我可以根据需要向上或向下滚动它,并且当它到达顶部或底部时,这些项目会环绕,使它看起来对用户来说,就好像他正在旋转一个无限长的(~100)重复项目列表。

此设置的目的是让用户选择一个随机项目,只需旋转/翻转列表视图并等待查看它停止的位置。我减少了 Listview 的摩擦力,因此它的弹跳速度更快,时间更长,这似乎真的很好用。最后,我在 ListView 顶部放置了一个部分透明的图像,以遮挡顶部和底部的项目(从透明到黑色的过渡),使用户看起来好像正在“选择”中间的项目,就好像他们在他们通过投掷控制的旋转“轮子”上。

有一个明显的问题:在抛出 ListView 后不会停在特定项目上,但它可以停止在两个项目之间悬停(第一个可见项目然后只显示部分)。我想避免这种情况,因为在这种情况下,哪个项目被“随机选择”并不明显。

长话短说:在 ListView 完成滚动后,我希望它停止在“整个”行上,而不是在部分可见的行上。

现在我通过检查滚动何时停止来实现此行为,然后选择第一个可见项目,如下所示:

    lv = this.getListView();
    
    lv.setFriction(0.005f);
    lv.setOnScrollListener(new OnScrollListener() {
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}

        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) 
            {
                if (isAutoScrolling) return;
                
                isAutoScrolling = true;
                int pos = lv.getFirstVisiblePosition();
                lv.setSelection(pos);
                isAutoScrolling = false;
            }
        }
    });

这工作得相当好,除了一个明显的问题......第一个可见项目可能只对一两个像素可见。在这种情况下,我希望 ListView 为这两个像素“向上”跳转,以便选择第二个可见项。相反,当然,第一个可见项被选中,这意味着 ListView 几乎“向下”跳了一整行(减去这两个像素)。

简而言之,我不想跳到第一个可见项目,而是希望它跳到最可见的项目。如果第一个可见项目不到一半可见,我希望它跳到第二个可见项目。

这是一个插图,希望能传达我的观点。最左边的 ListView (每对)显示了投掷停止后的状态(停止的地方),右边的 ListView 显示了它通过选择第一个可见项目进行“跳跃”后的样子。在左侧,我显示了当前(错误)的情况:项目 B 几乎不可见,但它仍然是第一个可见项目,因此 listView 跳转以选择该项目 - 这是不合逻辑的,因为它必须滚动几乎整个项目高度到那里。滚动到项目 C(如右图所示)会更合乎逻辑,因为它“更接近”。

图片
(来源:nickthissen.nl

我怎样才能实现这种行为?我能想到的唯一方法是以某种方式测量第一个可见项目有多少是可见的。如果超过 50%,那么我跳到那个位置。如果它小于 50%,我跳到那个位置 + 1。但是我不知道如何测量它......

有任何想法吗?

4

7 回答 7

6

getChildVisibleRect您可以使用该方法获取孩子的可见尺寸。当你有了这个,你得到了孩子的总高度,你可以滚动到适当的孩子。

在下面的示例中,我检查是否至少有一半的孩子是可见的:

View child = lv.getChildAt (0);    // first visible child
Rect r = new Rect (0, 0, child.getWidth(), child.getHeight());     // set this initially, as required by the docs
double height = child.getHeight () * 1.0;

lv.getChildVisibleRect (child, r, null);

if (Math.abs (r.height ()) < height / 2.0) {
    // show next child
}

else {
    // show this child
}
于 2013-03-11T13:00:24.323 回答
4

这是受 Shade 回答启发的最终代码。

一开始忘了补充"if(Math.abs(r.height())!=height)"。然后它在滚动到正确位置后只滚动两次,因为它总是大于 childView 的 height/2。希望能帮助到你。

listView.setOnScrollListener(new AbsListView.OnScrollListener(){

            @Override
            public void onScrollStateChanged(AbsListView view,int scrollState) {
                if (scrollState == SCROLL_STATE_IDLE){
                    View child = listView.getChildAt (0);    // first visible child
                    Rect r = new Rect (0, 0, child.getWidth(), child.getHeight());     // set this initially, as required by the docs
                    double height = child.getHeight () * 1.0;
                    listView.getChildVisibleRect (child, r, null);
                    if(Math.abs(r.height())!=height){//only smooth scroll when not scroll to correct position
                        if (Math.abs (r.height ()) < height / 2.0) {
                            listView.smoothScrollToPosition(listView.getLastVisiblePosition());
                        }
                        else if(Math.abs (r.height ()) > height / 2.0){
                            listView.smoothScrollToPosition(listView.getFirstVisiblePosition());
                        }
                        else{
                            listView.smoothScrollToPosition(listView.getFirstVisiblePosition());
                        }

                    }
                }
            }

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

            }});
于 2015-05-12T10:27:06.423 回答
3

按照这3个步骤,你就可以得到你想要的!!!!

1.初始化上下滚动的两个变量:

int scrollingUp=0,scrollingDown=0;

2.然后根据滚动增加变量的值:

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

        if(mLastFirstVisibleItem<firstVisibleItem)
                {
                    scrollingDown=1;
                }
                if(mLastFirstVisibleItem>firstVisibleItem)
                {
                    scrollingUp=1;
                }
                mLastFirstVisibleItem=firstVisibleItem;
    } 

3.然后在onScrollStateChanged()中做修改:

@Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            switch (scrollState) {
            case SCROLL_STATE_IDLE:

                if(scrollingUp==1)
                {
                    mainListView.post(new Runnable() {
                        public void run() {
                            View child = mainListView.getChildAt (0);    // first visible child
                            Rect r = new Rect (0, 0, child.getWidth(), child.getHeight());     // set this initially, as required by the docs
                            double height = child.getHeight () * 1.0;
                            mainListView.getChildVisibleRect (child, r, null);
                            int dpDistance=Math.abs (r.height());
                            double minusDistance=dpDistance-height;
                            if (Math.abs (r.height()) < height/2)
                            {
                                mainListView.smoothScrollBy(dpDistance, 1500);
                            }       
                            else
                            {
                                mainListView.smoothScrollBy((int)minusDistance, 1500);
                            }
                            scrollingUp=0;

                        }
                    });
                }
                if(scrollingDown==1)
                {
                    mainListView.post(new Runnable() {
                        public void run() {

                            View child = mainListView.getChildAt (0);    // first visible child
                            Rect r = new Rect (0, 0, child.getWidth(), child.getHeight());     // set this initially, as required by the docs
                            double height = child.getHeight () * 1.0;
                            mainListView.getChildVisibleRect (child, r, null);
                            int dpDistance=Math.abs (r.height());
                            double minusDistance=dpDistance-height;
                            if (Math.abs (r.height()) < height/2)
                            {
                                mainListView.smoothScrollBy(dpDistance, 1500);
                            }       
                            else
                            {
                                mainListView.smoothScrollBy((int)minusDistance, 1500);
                            }
                            scrollingDown=0;
                        } 
                    });
                }
            break;
            case SCROLL_STATE_TOUCH_SCROLL:

                break;
                }
            }
于 2014-11-12T07:24:22.023 回答
1

您可能解决了这个问题,但我认为这个解决方案应该有效

if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
    View firstChild = lv.getChildAt(0);
    int pos = lv.getFirstVisiblePosition();

    //if first visible item is higher than the half of its height
    if (-firstChild.getTop() > firstChild.getHeight()/2) {
        pos++;
    }

    lv.setSelection(pos);
}

getTop()对于第一个项目视图总是返回非正值,所以我不使用Math.abs(firstChild.getTop()),而只是-firstChild.getTop(). 即使这个值将 >0,那么这个条件仍然有效。

如果你想让这个更流畅,那么你可以尝试使用lv.smoothScrollToPosition(pos)并将上面的所有代码包含在

if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
    post(new Runnable() {
        @Override
        public void run() {
            //put above code here
            //and change lv.setSelection(pos) to lv.smoothScrollToPosition(pos)
        }
    });
}
于 2013-05-24T12:21:01.540 回答
0

如果可以得到需要滚动到的行的位置,可以使用方法:

平滑滚动到位置

所以像:

int pos = lv.getFirstVisiblePosition();
lv.smoothScrollToPosition(pos);

编辑
试试这个,抱歉我没有时间测试,我出去了。

ImageView iv = //Code to find the image view
Rect rect = new Rect(iv.getLeft(), iv.getTop(), iv.getRight(), iv.getBottom());
lv.requestChildRectangleOnScreen(lv, rect, false);
于 2013-03-11T12:40:04.100 回答
0

一旦你知道了第一个可见位置,你应该能够在下一个位置的视图上使用View.getLocationinWindow()or来获得一个可见高度。将其与的高度进行比较,并在适当的情况下滚动到下一个位置。View.getLocationOnScreen()View

您可能需要调整它以考虑填充,具体取决于您的行的样子。


我还没有尝试过上述方法,但它似乎应该可以工作。如果没有,这是另一个可能不太可靠的想法:

getLastVisiblePosition(). 如果你取 和 之间的差lastfirst你可以看到屏幕上有多少个位置是可见的。将其与首次填充列表时可见的位置数(滚动位置 0)进行比较。

如果相同数量的位置可见,只需滚动到第一个可见位置即可。如果还有一个可见,滚动到“first + 1”位置。

于 2013-03-11T13:00:32.980 回答
0

我的解决方案:

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)

{

    if (swipeLayout.isRefreshing()) {
        swipeLayout.setRefreshing(false);
    } else {
        int pos = firstVisibleItem;
        if (pos == 0 && lv_post_list.getAdapter().getCount()>0) {

            int topOfNext = lv_post_list.getChildAt(pos + 1).getTop();
            int heightOfFirst = lv_post_list.getChildAt(pos).getHeight();
            if (topOfNext > heightOfFirst) {
                swipeLayout.setEnabled(true);
            } else {
                swipeLayout.setEnabled(false);
            }
        }
        else
            swipeLayout.setEnabled(false);
    }
}
于 2015-10-04T13:23:21.350 回答