我在Android中使用ScrollView
,其中ScrollView
的可见部分与Scrollview
中的一个单元格的大小相同。每个“细胞”都是相同的高度。因此,我想要做的是在滚动ScrollView
之后抓住位置。
目前,我正在检测用户何时触及ScrollView
以及何时开始滚动并从那里开始工作,但这是非常错误的。当用户只是轻弹它并滚动然后减速时,它也需要工作。
在iPhone上有一个类似didDecelerate
的功能,当ScrollView
完成滚动时,我可以执行任何我想要的代码。 Android有这样的东西吗?或者是否有一些我可以看到的代码来找出更好的方法呢?
我查看过Android文档但找不到类似内容。
答案 0 :(得分:92)
我最近不得不实现你描述的功能。我做的是让一个Runnable检查出ScrollView是否已经停止滚动,当首次触发onTouchEvent时,getScrollY()返回的值与变量 newCheck 定义的时间后返回的值相比较。
见下面的代码(工作解决方案):
public class MyScrollView extends ScrollView{
private Runnable scrollerTask;
private int initialPosition;
private int newCheck = 100;
private static final String TAG = "MyScrollView";
public interface OnScrollStoppedListener{
void onScrollStopped();
}
private OnScrollStoppedListener onScrollStoppedListener;
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
scrollerTask = new Runnable() {
public void run() {
int newPosition = getScrollY();
if(initialPosition - newPosition == 0){//has stopped
if(onScrollStoppedListener!=null){
onScrollStoppedListener.onScrollStopped();
}
}else{
initialPosition = getScrollY();
MyScrollView.this.postDelayed(scrollerTask, newCheck);
}
}
};
}
public void setOnScrollStoppedListener(MyScrollView.OnScrollStoppedListener listener){
onScrollStoppedListener = listener;
}
public void startScrollerTask(){
initialPosition = getScrollY();
MyScrollView.this.postDelayed(scrollerTask, newCheck);
}
}
然后我有:
scroll.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
scroll.startScrollerTask();
}
return false;
}
});
scroll.setOnScrollStoppedListener(new OnScrollStoppedListener() {
public void onScrollStopped() {
Log.i(TAG, "stopped");
}
});
BTW我在我的应用程序中使用了其他回复中的一些想法来做到这一点。 希望这可以帮助。有任何问题请随时询问我(们)。 欢呼声。
答案 1 :(得分:22)
这是对IMHO的另一个修复,在ScrollView中缺少OnEndScroll事件错误。
受hambonious回答的启发。 只需将此类放入您的项目中(更改包以匹配您自己的项目)并使用以下xml
package com.thecrag.components.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class ResponsiveScrollView extends ScrollView {
public interface OnEndScrollListener {
public void onEndScroll();
}
private boolean mIsFling;
private OnEndScrollListener mOnEndScrollListener;
public ResponsiveScrollView(Context context) {
this(context, null, 0);
}
public ResponsiveScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ResponsiveScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void fling(int velocityY) {
super.fling(velocityY);
mIsFling = true;
}
@Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (mIsFling) {
if (Math.abs(y - oldY) < 2 || y >= getMeasuredHeight() || y == 0) {
if (mOnEndScrollListener != null) {
mOnEndScrollListener.onEndScroll();
}
mIsFling = false;
}
}
}
public OnEndScrollListener getOnEndScrollListener() {
return mOnEndScrollListener;
}
public void setOnEndScrollListener(OnEndScrollListener mOnEndScrollListener) {
this.mOnEndScrollListener = mOnEndScrollListener;
}
}
再次更改包名称以匹配您的项目
<com.thecrag.components.ui.ResponsiveScrollView
android:id="@+id/welcome_scroller"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/welcome_scroll_command_help_container"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/welcome_header_text_thecrag"
android:layout_margin="6dp">
....
</com.thecrag.components.ui.ResponsiveScrollView>
答案 2 :(得分:9)
我将(水平)ScrollView子类化并做了类似的事情:
@Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
if (Math.abs(x - oldX) > SlowDownThreshold) {
currentlyScrolling = true;
} else {
currentlyScrolling = false;
if (!currentlyTouching) {
//scrolling stopped...handle here
}
}
super.onScrollChanged(x, y, oldX, oldY);
}
我对SlowDownThreshold使用了值1,因为它似乎总是与最后一次onScrollChanged事件的区别。
为了在慢慢拖动时使其表现正常,我必须这样做:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
currentlyTouching = true;
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
currentlyTouching = false;
if (!currentlyScrolling) {
//I handle the release from a drag here
return true;
}
}
return false;
}
答案 3 :(得分:7)
我的方法是每次调用onScrollChanged()时更改时间戳来确定滚动状态。 可以很容易地确定滚动的开始和结束时间。 您还可以更改阈值(我使用100毫秒)来修复灵敏度。
public class CustomScrollView extends ScrollView {
private long lastScrollUpdate = -1;
private class ScrollStateHandler implements Runnable {
@Override
public void run() {
long currentTime = System.currentTimeMillis();
if ((currentTime - lastScrollUpdate) > 100) {
lastScrollUpdate = -1;
onScrollEnd();
} else {
postDelayed(this, 100);
}
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (lastScrollUpdate == -1) {
onScrollStart();
postDelayed(new ScrollStateHandler(), 100);
}
lastScrollUpdate = System.currentTimeMillis();
}
private void onScrollStart() {
// do something
}
private void onScrollEnd() {
// do something
}
}
答案 4 :(得分:5)
这是另一种解决方案,在我看来非常简单和干净,自然受到上述答案的启发。基本上,一旦用户结束手势检查,getScrollY()是否仍在改变,经过短暂的延迟(此处为50ms)。
public class ScrollViewWithOnStopListener extends ScrollView {
OnScrollStopListener listener;
public interface OnScrollStopListener {
void onScrollStopped(int y);
}
public ScrollViewWithOnStopListener(Context context) {
super(context);
}
public ScrollViewWithOnStopListener(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
checkIfScrollStopped();
}
return super.onTouchEvent(ev);
}
int initialY = 0;
private void checkIfScrollStopped() {
initialY = getScrollY();
this.postDelayed(new Runnable() {
@Override
public void run() {
int updatedY = getScrollY();
if (updatedY == initialY) {
//we've stopped
if (listener != null) {
listener.onScrollStopped(getScrollY());
}
} else {
initialY = updatedY;
checkIfScrollStopped();
}
}
}, 50);
}
public void setOnScrollStoppedListener(OnScrollStopListener yListener) {
listener = yListener;
}
}
答案 5 :(得分:4)
我对此问题的处理方法是使用计时器检查以下2个“事件”。
1)onScrollChanged()停止被调用
2)用户的手指从滚动视图中抬起
public class CustomScrollView extends HorizontalScrollView {
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
Timer ntimer = new Timer();
MotionEvent event;
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
checkAgain();
super.onScrollChanged(l, t, oldl, oldt);
}
public void checkAgain(){
try{
ntimer.cancel();
ntimer.purge();
}
catch(Exception e){}
ntimer = new Timer();
ntimer.schedule(new TimerTask() {
@Override
public void run() {
if(event.getAction() == MotionEvent.ACTION_UP){
// ScrollView Stopped Scrolling and Finger is not on the ScrollView
}
else{
// ScrollView Stopped Scrolling But Finger is still on the ScrollView
checkAgain();
}
}
},100);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
this.event = event;
return super.onTouchEvent(event);
}
}
答案 6 :(得分:2)
尝试在StackOverflow上查看this question - 它与您的问题不完全相同,但它可以让您了解如何管理ScrollView
的滚动事件。
基本上,您需要通过展开CustomScrollView
并覆盖ScrollView
来创建自己的onScrollChanged(int x, int y, int oldx, int oldy)
。然后,您需要在布局文件中引用它,而不是ScrollView
之类的标准com.mypackage.CustomScrollView
。
答案 7 :(得分:2)
我认为这已经出现了。 AFAIK,你不能轻松地检测到它。我的建议是你看一下ScrollView.java
(这就是我们在Android的工作方式:),并弄清楚如何扩展类以提供你正在寻找的功能。这是我首先要尝试的:
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if (mScroller.isFinished()) {
// do something, for example call a listener
}
}
答案 8 :(得分:2)
对于您所描述的简单案例,您可以在自定义滚动视图中使用重写fling方法。每次用户从屏幕上抬起手指时,都会调用Fling方法执行“减速”。
所以你应该做的是这样的事情:
子类ScrollView。
public class MyScrollView extends ScrollView {
private Scroller scroller;
private Runnable scrollerTask;
//...
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(getContext()); //or OverScroller for 3.0+
scrollerTask = new Runnable() {
@Override
public void run() {
scroller.computeScrollOffset();
scrollTo(0, scroller.getCurrY());
if (!scroller.isFinished()) {
MyScrollView.this.post(this);
} else {
//deceleration ends here, do your code
}
}
};
//...
}
}
子类fling方法,不要调用超类实现。
@Override
public void fling(int velocityY) {
scroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0, container.getHeight());
post(scrollerTask);
//add any extra functions you need from android source code:
//show scroll bars
//change focus
//etc.
}
如果用户在抬起手指之前停止滚动(velocityY == 0),则不会触发投掷。如果您想拦截此类事件,请覆盖onTouchEvent。
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean eventConsumed = super.onTouchEvent(ev);
if (eventConsumed && ev.getAction() == MotionEvent.ACTION_UP) {
if (scroller.isFinished()) {
//do your code
}
}
return eventConsumed;
}
注意虽然这有效,但重写fling方法可能不是一个好主意。它是公开的,但它几乎没有为子类化而设计。现在它做了3件事 - 它启动私人mScroller的投掷,处理可能的焦点变化并显示滚动条。这可能会在未来的Android版本中发生变化例如,私有mScroller实例将其类从Scroller更改为OvershootScroller,介于2.3和3.0之间。你必须记住所有这些微小的差异。无论如何,要为未来不可预见的后果做好准备。
答案 9 :(得分:2)
这是我的解决方案,其中包括滚动跟踪和滚动结尾:
public class ObservableHorizontalScrollView extends HorizontalScrollView {
public interface OnScrollListener {
public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY);
public void onEndScroll(ObservableHorizontalScrollView scrollView);
}
private boolean mIsScrolling;
private boolean mIsTouching;
private Runnable mScrollingRunnable;
private OnScrollListener mOnScrollListener;
public ObservableHorizontalScrollView(Context context) {
this(context, null, 0);
}
public ObservableHorizontalScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ObservableHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_MOVE) {
mIsTouching = true;
mIsScrolling = true;
} else if (action == MotionEvent.ACTION_UP) {
if (mIsTouching && !mIsScrolling) {
if (mOnScrollListener != null) {
mOnScrollListener.onEndScroll(this);
}
}
mIsTouching = false;
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (Math.abs(oldX - x) > 0) {
if (mScrollingRunnable != null) {
removeCallbacks(mScrollingRunnable);
}
mScrollingRunnable = new Runnable() {
public void run() {
if (mIsScrolling && !mIsTouching) {
if (mOnScrollListener != null) {
mOnScrollListener.onEndScroll(ObservableHorizontalScrollView.this);
}
}
mIsScrolling = false;
mScrollingRunnable = null;
}
};
postDelayed(mScrollingRunnable, 200);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScrollChanged(this, x, y, oldX, oldY);
}
}
public OnScrollListener getOnScrollListener() {
return mOnScrollListener;
}
public void setOnScrollListener(OnScrollListener mOnEndScrollListener) {
this.mOnScrollListener = mOnEndScrollListener;
}
}
答案 10 :(得分:2)
这是一个旧线程,但是我想添加一个较短的解决方案:
buttonsScrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
//YOUR CODE TO BE EXECUTED HERE
},1000)
}
自然会有1000毫秒的延迟。如有需要,请进行调整。
答案 11 :(得分:1)
我对ZeroG的回答做了一些改进。主要取消多余的任务调用并将整个事件作为私有OnTouchListener实现,因此所有滚动检测代码都在一个地方。
将以下代码粘贴到您自己的ScrollView实现中:
private class ScrollFinishHandler implements OnTouchListener
{
private static final int SCROLL_TASK_INTERVAL = 100;
private Runnable mScrollerTask;
private int mInitialPosition = 0;
public ScrollFinishHandler()
{
mScrollerTask = new Runnable() {
public void run() {
int newPosition = getScrollY();
if(mInitialPosition - newPosition == 0)
{//has stopped
onScrollStopped(); // Implement this on your main ScrollView class
}else{
mInitialPosition = getScrollY();
ExpandingLinearLayout.this.postDelayed(mScrollerTask, SCROLL_TASK_INTERVAL);
}
}
};
}
@Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_UP)
{
startScrollerTask();
}
else
{
stopScrollerTask();
}
return false;
}
}
然后在ScrollView实现中:
setOnTouchListener( new ScrollFinishHandler() );
答案 12 :(得分:1)
这里有一些很好的答案,但我的代码可以检测滚动何时停止而无需扩展ScrollView类。 每个视图实例都可以调用getViewTreeObserver()。当持有ViewTreeObserver的这个实例时,你可以使用addOnScrollChangedListener()函数添加一个OnScrollChangedListener。
声明以下内容:
private ScrollView scrollListener;
private volatile long milesec;
private Handler scrollStopDetector;
private Thread scrollcalled = new Thread() {
@Override
public void run() {
if (System.currentTimeMillis() - milesec > 200) {
//scroll stopped - put your code here
}
}
};
并在你的onCreate(或其他地方)添加:
scrollListener = (ScrollView) findViewById(R.id.scroll);
scrollListener.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
@Override
public void onScrollChanged() {
milesec = System.currentTimeMillis();
scrollStopDetector.postDelayed(scrollcalled, 200);
}
});
你可能想要在这些检查之间花费更长或更慢的时间,但是当滚动时,这个列表器被快速调用,因此它将非常快速地工作。
答案 13 :(得分:-1)
this.getListView().setOnScrollListener(new OnScrollListener(){
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if( firstVisibleItem + visibleItemCount >= totalItemCount )
// Last item is shown...
}
希望片段有用:)