Android ListView - 停止在“整个”行位置滚动

时间:2013-03-11 12:24:49

标签: android listview scroll

对于令人困惑的标题,我不能非常简洁地表达问题...

我有一个带有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(右图所示)会更合乎逻辑,因为它更“接近”。

Image http://nickthissen.nl/Images/lv.jpg

我该如何实现这种行为?我能想到的唯一方法是以某种方式测量第一个可见项目的可见数量。如果超过50%,那么我跳到那个位置。如果它低于50%,我跳到那个位置+ 1.然而我不知道如何衡量......

有什么想法吗?

7 个答案:

答案 0 :(得分: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
}

答案 1 :(得分: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;
                }
            }

答案 2 :(得分:3)

这是我的最终代码,受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) {

            }});

答案 3 :(得分:0)

如果您可以获取需要滚动到的行的位置,则可以使用以下方法:

smoothScrollToPosition

类似于:

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

答案 4 :(得分:0)

知道第一个可见位置后,您应该能够在下一个位置的视图中使用View.getLocationinWindow()View.getLocationOnScreen()来获得的可见高度第一即可。将其与View的高度进行比较,如果合适,滚动到下一个位置。

您可能需要对其进行调整以考虑填充,具体取决于行的外观。


我没有尝试过上述内容,但似乎应该可行。如果没有,这是另一个可能不那么强大的想法:

getLastVisiblePosition()。如果您采用lastfirst之间的差异,则可以查看屏幕上可见的位置数。将其与首次填充列表时可见的位置数相比较(滚动位置0)。

如果可以看到相同数量的位置,只需滚动到您正在进行的第一个可见位置。如果还有一个可见,请滚动到“第一个+ 1”位置。

答案 5 :(得分:0)

你可能已经解决了这个问题,但我认为这个解决方案应该可行

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

答案 6 :(得分: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);
    }
}