在适配器中检测滚动方向(上/下)

20

我正在尝试在我的项目中模仿Google Plus应用,因为现在它似乎是参考。

当滚动时,列表视图的效果非常好,我想做类似的东西。

我已经开始使用LayoutAnimationController http://android-er.blogspot.be/2009/10/listview-and-listactivity-layout.html

LayoutAnimationController controller 
   = AnimationUtils.loadLayoutAnimation(
     this, R.anim.list_layout_controller);
  getListView().setLayoutAnimation(controller);

这似乎不太好,因为并非所有的元素都被动画化了:

因此,最终我使用了适配器的getView方法,并使用了以下代码:

        AnimationSet set = new AnimationSet(true);

        Animation animation = new AlphaAnimation(0.0f, 1.0f);
        animation.setDuration(800);
        set.addAnimation(animation);

        animation = new TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0.0f,Animation.RELATIVE_TO_SELF, 0.0f,
            Animation.RELATIVE_TO_SELF, 1.0f,Animation.RELATIVE_TO_SELF, 0.0f
        );
        animation.setDuration(600);
        set.addAnimation(animation);

        row.startAnimation(set);

结果非常棒,我真的很满意!

不幸的是,只有当我从列表顶部向底部滚动时才有效!

我想让它在从另一侧滚动时也能正常工作,我需要稍微改变TranslateAnimation。

所以我的问题是,在我的适配器中有没有一种方法可以检测我是向上还是向下滚动?


你的例子中,“row”变量是什么? - Bostone
您可以在此帖子中查看完整解决方案:https://dev59.com/L2Qn5IYBdhLWcg3we3PD#20091066 - thanhlcm
另一个可行的解决方案 https://dev59.com/5Gkw5IYBdhLWcg3wzdzZ#34788591 - Muhammad Babar
11个回答

23

将一个 OnScrollListener 分配给你的 ListView。创建一个指示用户向上或向下滚动的标志。通过检查当前 first 可见项目位置是否等于先前 first 可见项位置来设置适当的值到该标志。将该检查放置在 onScrollStateChanged() 中。

示例代码:

private int mLastFirstVisibleItem;
private boolean mIsScrollingUp;

public void onScrollStateChanged(AbsListView view, int scrollState) {
    final ListView lw = getListView();

    if (view.getId() == lw.getId()) {
        final int currentFirstVisibleItem = lw.getFirstVisiblePosition();

        if (currentFirstVisibleItem > mLastFirstVisibleItem) {
            mIsScrollingUp = false;
        } else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
            mIsScrollingUp = true;
        }

        mLastFirstVisibleItem = currentFirstVisibleItem;
    } 
}

getView()中检查mIsScrollingUp是否为true或false,并相应地分配动画。


我写了我的解决方案,但你的看起来也很正确。我想知道哪一个更好,我认为我需要更少的代码,但也许使用位置更“不干净”。 - Waza_Be
@Waza_Be:嗯,除了我的回答增加了更多代码之外,实际上并没有太大的区别。但是无论如何,如果您例如要扩展并且希望在用户将ListView拖离屏幕时跳过动画,那么最好在OnScrollListener中进行控制。 :-) - Wroclai
11
这并没有明确告诉你当前是在向上还是向下滚动,只能告诉你是否已经向上或向下滚动了,因为第一个可见的项目在不可见之前是不会改变的。在我的情况下,我实际上需要在列表正在滚动时获得持续的回调。 - Christopher Perry
补充一下Christopher的评论。这里有一个简单的方法可以更快地获得有关滚动位置的反馈。您可以根据ListView的第一个子项的位置来实现它。它也适用于GridView。listView.getChildAt(0).getTop() - Nelson Ramirez
不错。虽然如果列表项的高度很大,这可能不是最优雅的解决方案。 - Aditya Kushwaha
@Christopher Perry:关于滚动方向,请看我在这里的回复:https://dev59.com/5Gkw5IYBdhLWcg3wzdzZ#24781757 - Santacrab

19

我最终做了这个:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    Log.i("",position+" - "+lastposition);

    if (position >= lastposition)
        animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF,
                0.0f, Animation.RELATIVE_TO_SELF, 0.0f,
                Animation.RELATIVE_TO_SELF, 1.0f,
                Animation.RELATIVE_TO_SELF, 0.0f);
    else
        animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF,
                0.0f, Animation.RELATIVE_TO_SELF, 0.0f,
                Animation.RELATIVE_TO_SELF, -1.0f,
                Animation.RELATIVE_TO_SELF, 0.0f);

    animation.setDuration(600);
    set.addAnimation(animation);

    row.startAnimation(set);

    lastposition = position;

}

甜。这是一个更好的解决方案。 - Amanni
1
lastposition 是从哪里来的? - Zon

6

这是我遇到的最简单、最易懂的方法。而且它非常有效。

view.addOnScrollListener(new View.OnScrollListener() {
                @Override
                public void onScrolled(@NonNull View view, int dx, int dy) {
                    if (dy > 0) {
                        //Scrolling down
                    } else if (dy < 0) {
                        //Scrolling up
                    }
                }
            });

5

接受的答案并不能真正“检测”向上或向下滚动。如果当前可见项非常大,它将无法工作。使用onTouchListener是正确的方法。

这是我使用的代码片段:

listView.setOnTouchListener(new View.OnTouchListener() {
    float initialY, finalY;
    boolean isScrollingUp;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = MotionEventCompat.getActionMasked(event);

        switch(action) {
            case (MotionEvent.ACTION_DOWN):
                initialY = event.getY();
            case (MotionEvent.ACTION_UP):
                finalY = event.getY();

                if (initialY < finalY) {
                    Log.d(TAG, "Scrolling up");
                    isScrollingUp = true;
                } else if (initialY > finalY) {
                    Log.d(TAG, "Scrolling down");
                    isScrollingUp = false;
                }
            default:
        }

        if (isScrollingUp) {
            // do animation for scrolling up
        } else {
            // do animation for scrolling down
        }

        return false; // has to be false, or it will freeze the listView
    }
});

想象一下用户在不将其“工具”与屏幕分离的情况下上下滚动,这种情况下,初始值不会被更新,你将会得到错误的 isScrollingUp 标识结果。你应该始终检查 currentprevious 点。而不是 finalinitial 点。 - Farid

5

更复杂的解决方案(适用于listview中长条目高度的情况)

  1. Create custom listview

    public class ScrollDetectingListView extends ListView {
        public ScrollDetectingListView(Context context) {
            super(context);
        }
    
        public ScrollDetectingListView(Context context, AttributeSet attrs) {
            super(context,attrs);
        } 
    
        public ScrollDetectingListView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        //we need this protected method for scroll detection
        public int getVerticalScrollOffset() {
            return computeVerticalScrollOffset();
        }
    }
    
  2. Override onScroll

        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
    
        private int mInitialScroll = 0;
    
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
    
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            int scrolledOffset = listView.getVerticalScrollOffset();
            if (scrolledOffset!=mInitialScroll) {
                //if scroll position changed
                boolean scrollUp = (scrolledOffset - mInitialScroll) < 0;
                mInitialScroll = scrolledOffset;
            }
        }
        });
    

迄今为止最好的答案。 这解决了当Listview回收其视图时,使“listview.getChildAt(0).getTop()”不可靠的问题。 - SalicBlu3
listView.getVerticalScrollOffset() 没有找到。 - sujith s

4
尝试这个。我希望它能帮助你。这是来自@Gal Rom回答的逻辑。
lv.setOnScrollListener(new OnScrollListener() {
        private int mLastFirstVisibleItem;

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {

        }

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

            if(mLastFirstVisibleItem<firstVisibleItem)
            {
                Log.i("SCROLLING DOWN","TRUE");
            }
            if(mLastFirstVisibleItem>firstVisibleItem)
            {
                Log.i("SCROLLING UP","TRUE");
            }
            mLastFirstVisibleItem=firstVisibleItem;

        }
    });

2

这是我的方法:它可以让您更快地了解您已经滚动了多少:

OnScroll时,您只需获取列表中第一个项目的顶部位置即可。这是一种相当可靠的方法,可以立即获取实际的滚动位置信息。

listView.getChildAt(0).getTop()

在适配器的 getView 方法中,您可以将 parent 参数作为 listView 找到。 - Zon

0
list.setOnScrollListener(new OnScrollListener() {
        int last_item;
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {

        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            if(last_item<firstVisibleItem+visibleItemCount-1){
                System.out.println("List is scrolling upwards");
            }
            else if(last_item>firstVisibleItem+visibleItemCount-1){
                System.out.println("List is scrolling downwards");
            }
             last_item = firstVisibleItem+visibleItemCount-1;
        }
    });

根据最后一个可见项的位置,我决定Listview是向上还是向下滚动。


0
我使用了这个更简单的解决方案:
public class ScrollDetectingListView extends ListView

...

setOnScrollListener( new OnScrollListener() 
{

    private int mInitialScroll = 0;

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

        boolean scrollUp = scrolledOffset > mInitialScroll;
        mInitialScroll = scrolledOffset;
    }


    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {


    }

}

computeVerticalScroll()是什么? - Jared Rummler
http://developer.android.com/reference/android/widget/AbsListView.html 中的一个受保护的方法。 - Vaiden

0

一种不依赖于视图位置等的通用解决方案。只需检查垂直滚动偏移量并将其与先前的滚动偏移量进行比较。如果新值大于旧值,用户正在向下滚动;反之亦然。

// [START check vertical scroll direction]
int oldScrollOffset = 0;

listView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
        @Override
        public void onScrollChange(View view, int i, int i1, int i2, int i3) {

            Boolean scrollDirectionDown;

            int newScrollOffset = listView.computeVerticalScrollOffset();

            if (newScrollOffset > oldScrollOffset) {
                scrollDirectionDown = true;
            } else {
                scrollDirectionDown = false;
            }

            oldScrollOffset = newScrollOffset;

            if (scrollDirectionDown) {
                // Update accordingly for scrolling down
                Log.d(TAG, "scrolling down");
            } else {
                // Update accordingly for scrolling up
                Log.d(TAG, "scrolling up");
            }

    });
// [END check vertical scroll direction]

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