Android:如何检测滚动结束


67

我正在使用GestureDetector.SimpleOnGestureListener的onScroll方法在画布上滚动大位图。滚动结束后,我想重新绘制位图,以防用户想进一步滚动...离开位图的边缘,但是我看不到如何检测滚动结束的时间(用户抬起手指)从屏幕上)。

e2.getAction()似乎总是返回值2,所以没有帮助。e2.getPressure似乎返回相当恒定的值(约0.25),直到最后的onScroll调用时压力似乎下降到约0.13。我想我可以检测到压力的降低,但这远非万无一失。

必须有更好的方法:请问有人可以帮忙吗?


我已经添加if (e2.getPressure() < 0.15)到onScroll()方法。正如预期的那样,有时会检测到滚动的结束,但并非总是如此。当然,必须有更好的方法!
prepbgg 2010年

1
我现在放弃了getPressure方法。太不可靠了。相反,我要检查滚动的累积距离,并在超过定义的限制时重绘位图。这是更可预测的,但仍远非理想。如果有人对更好的方法有任何建议,我会很高兴听到他们的建议!
prepbgg 2010年

在对我最有用的那些线索中,那些答案对我来说最有用的是Akos Cz,Aron Cederholm和Mike的答案。
prepbgg 2010年

Answers:


69

这是我解决问题的方法。希望这可以帮助。

// declare class member variables
private GestureDetector mGestureDetector;
private OnTouchListener mGestureListener;
private boolean mIsScrolling = false;


public void initGestureDetection() {
        // Gesture detection
    mGestureDetector = new GestureDetector(new SimpleOnGestureListener() {
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            handleDoubleTap(e);
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            handleSingleTap(e);
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // i'm only scrolling along the X axis
            mIsScrolling = true;                
            handleScroll(Math.round((e2.getX() - e1.getX())));
            return true;
        }

        @Override
        /**
         * Don't know why but we need to intercept this guy and return true so that the other gestures are handled.
         * https://code.google.com/p/android/issues/detail?id=8233
         */
        public boolean onDown(MotionEvent e) {
            Log.d("GestureDetector --> onDown");
            return true;
        }
    });

    mGestureListener = new View.OnTouchListener() {
        public boolean onTouch(View v, MotionEvent event) {

            if (mGestureDetector.onTouchEvent(event)) {
                return true;
            }

            if(event.getAction() == MotionEvent.ACTION_UP) {
                if(mIsScrolling ) {
                    Log.d("OnTouchListener --> onTouch ACTION_UP");
                    mIsScrolling  = false;
                    handleScrollFinished();
                };
            }

            return false;
        }
    };

    // attach the OnTouchListener to the image view
    mImageView.setOnTouchListener(mGestureListener);
}

谢谢你的帮助。从您的链接所引用的Google Code Android上的线程来看,对于返回“ true”或“ false”的后果似乎有些困惑。我将在onDown方法中尝试“返回true”,并进行报告。
prepbgg

1
确实非常感谢您提供此代码。这似乎是我的问题的答案。我最初以为您代码中的关键行是onDown方法中的“ return true”,但实际上这似乎对我的代码没有影响。关键点似乎是,据我所知,从未将ACTION_UP事件通知给GestureDetector,因此(您已正确识别)我必须在onTouchListener.onTouch()方法中包含代码以对此进行查找。滚动结束时发生的事件。再次感谢!
prepbgg

2
有趣...非常有帮助。但是在快速测试中,我似乎在猛攻之前就得到了一次修饰事件。如果发生猛冲或只是停止滚动,我想做其他事情。当我检测到润色时,如何知道是否要进行猛击事件,以致无法执行正常的滚动结束功能?
Garret Wilson

1
@GarretWilson可以使用VelocityTracker来检测运动事件的速度,然后可以使用该速度来确定猛击是否会发生。见developer.android.com/reference/android/view/... 和/或以下后stackoverflow.com/questions/10155907/...
阿科什锆石

捕捉到ACTION_UPonTouchEvent,确保onFling()返回FALSE。
haiwuxing

4

您应该看看http://developer.android.com/reference/android/widget/Scroller.html。特别是这可能会有所帮助(按相关性排序):

isFinished();
computeScrollOffset();
getFinalY(); getFinalX(); and getCurrY() getCurrX()
getDuration()

这意味着您必须创建一个Scroller。

如果要使用触摸,也可以使用GestureDetector并定义自己的画布滚动。以下示例创建了ScrollableImageView,并且要使用它,您必须定义图像的尺寸。您可以定义自己的滚动范围,并在完成滚动后重新绘制图像。

http://www.anddev.org/viewtopic.php?p=31487#31487

根据您的代码,您应该考虑invalidate(int l,int t,int r,int b); 无效。


感谢您的建议。滚动条看起来像是设计用来响应Fling手势开始滚动并继续滚动由程序员确定的距离(有点像HTC手机的主屏幕)。但是,我希望屏幕图像随着用户的触摸而移动。我在我的应用程序的anddev线程中遵循了一些想法。但是,据我所知,此示例仅绘制一次缓冲区图像。我的应用程序有可能无限大小的图像,其需要重画时,应用程序处于空闲状态(后一个滚动已结束,前另一个开始。)
prepbgg

3
SimpleOnGestureListener.onFling() 

它似乎发生在滚动结束(即用户放开手指)时,这就是我正在使用的功能,对我来说非常有用。


感谢您的建议。恐怕几天后我将没有时间尝试它,但是我很期待看到它是否对我有用。如果这样做,它将非常整洁!
prepbgg

4
可悲的是,似乎只是在快速手势结束时才调用onFling。我不仅需要重新绘制,而且还需要在缓慢的精确滚动结束时重新绘制。
prepbgg

似乎onFling确实即使经过一小段滚动也确实会被调用,但并非每次都调用,例如,如果缓慢释放它不会。在onFling内部,您需要检测滚动和其他内容的速度。

2

几个月后回到这个问题,我现在采取了另一种方法:使用处理程序(如Android Snake示例中的示例)每隔125毫秒向应用发送一条消息,提示其检查Scroll是否已启动,以及自上次滚动事件以来是否已超过100毫秒。

这似乎工作得很好,但是如果有人能看到任何缺点或可能的改进,我将不胜感激。

相关的代码在MyView类中:

public class MyView extends android.view.View {

...

private long timeCheckInterval = 125; // milliseconds
private long scrollEndInterval = 100;
public long latestScrollEventTime;
public boolean scrollInProgress = false;

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

private timeCheckHandler mTimeCheckHandler = new timeCheckHandler();

class timeCheckHandler extends Handler{

        @Override
        public void handleMessage(Message msg) {
        long now = System.currentTimeMillis();
        if (scrollInProgress && (now>latestScrollEventTime+scrollEndInterval)) {
                    scrollInProgress = false;

//滚动已结束,因此请在此处插入代码

//调用doDrawing()方法

//重画位图,重新居中滚动结束的位置

                    [ layout or view ].invalidate();
        }
        this.sleep(timeCheckInterval);
        }

        public void sleep(long delayMillis) {
            this.removeMessages(0);
            sendMessageDelayed(obtainMessage(0), delayMillis);
            }
    }
}

@Override protected void onDraw(Canvas canvas){
        super.onDraw(canvas);

//将大缓冲区位图绘制到视图画布上的代码//定位为考虑到正在进行的任何滚动

}

public void doDrawing() {

//在大缓冲区位图上进行详细(且费时)绘制的代码//

//以下指令重设时间检查时钟// //当应用启动时主要活动调用此方法时,时钟首先启动

        mTimeCheckHandler.sleep(timeCheckInterval);
}

// MyView类的其余部分

}

并在MyGestureDetector类中

public class MyGestureDetector extends SimpleOnGestureListener {

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
        float distanceY) {

    [MyView].scrollInProgress = true;
        long now = System.currentTimeMillis();  
    [MyView].latestScrollEventTime =now;

    [MyView].scrollX += (int) distanceX;
    [MyView].scrollY += (int) distanceY;

//下一条指令将导致调用View的onDraw方法//将缓冲区位图绘制到屏幕上//被转移以考虑滚动

    [MyView].invalidate();

}

// MyGestureDetector类的其余部分

}


尽管这样可以正常工作,但Akos Cz在2010年9月29日提供的答案(请参见上文)更加整洁。代码不仅简单得多,而且还避免了等待时间检查的任何不必要的延迟。使用Akos的代码,滚动的结束立即被检测到。
prepbgg 2010年

1

我一直在研究同样的问题。我看到Akos Cz回答了您的问题。我创建了类似的东西,但是在我的版本中,我注意到它仅适用于常规滚动-意味着不会产生猛冲。但是,如果确实生成了fling-不管我是否处理了fling,那么它都不会在“ onTouchEvent”中检测到“ ACTION_UP”。现在也许这只是我的实现,但是如果是这样,我就不知道为什么。

经过进一步调查,我注意到在逃跑期间,每次将“ ACTION_UP”传递到“ e2”中的“ onFling”中。因此,我认为这一定是为什么在这些实例中的“ onTouchEvent”中未对其进行处理的原因。

为了使它对我有用,我只需要调用一个方法来处理“ onFling”中的“ ACTION_UP”,然后对两种类型的滚动都起作用。以下是我在应用中实施的确切步骤:

-在构造函数中将“ gestureScrolling”布尔值初始化为“ false”。

-我在“ onScroll”中将其设置为“ true”

-创建了一种用于处理“ ACTION_UP”事件的方法。在该事件内,我将“ gestureSCrolling”重置为false,然后完成了我需要做的其余处理。

-在“ onTouchEvent”中,如果检测到“ ACTION_UP”并且“ gestureScrolling” = true,则我调用了处理“ ACTION_UP”的方法

-我所做的那部分与众不同的是:我还调用了我的方法来处理“ onFling”内部的“ ACTION_UP”。


感谢您提供的非常清晰的摘要。我最终得到的代码与您的代码非常相似,除了我没有费心在onFling内测试ACTION_UP……我只是将每个onFling调用(无论其ACTION类型如何)都视为滚动的结尾(假设当我调用onFling时,滚动标记为true),就像我对待在onTouchListener的onTouch调用中检测到的ACTION_UP事件一样。
prepbgg 2010年

我相信每次快速滚动和ACTION_UP都会导致每次猛击,所以我认为您是对的,不需要测试ACTION_UP是正确的,但始终假设每次猛击都是滚动的结束。使用相同的逻辑,您是否真的需要测试以查看onFling中的滚动标志是否为真,因为如果我没有记错的话,没有滚动就无法进行Fling,所以我认为您不会需要检查您的滚动标志是否已设置。
亚历克斯(Alex)2010年

是的,我认为您是对的!但是,我认为我仍然需要滚动标志才能检测(在onTouch中)滚动太慢的滚动结束,以至于不能将其视为甩尾,因此需要将语句保留在onFling(以及onTouch中)将标志设置为false。
prepbgg 2010年

1

我确定对您来说太迟了,但是,似乎我已经找到了解决您原始问题的正确方法,而没有必要这样做。

如果使用Scroller / OverScroller对象滚动,则应检查以下函数的返回值。

public boolean computeScrollOffset() 

请享用

收割者


1
感谢您的建议。正如我上面提到的,Scroller看起来像是旨在响应Fling手势开始滚动并继续滚动由程序员确定的距离(有点像HTC手机的主屏幕)。但是,我希望屏幕图像随着用户的触摸而移动。
prepbgg 2012年

1

我认为这会按需工作

protected class SnappingGestureDetectorListener extends SimpleOnGestureListener{

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
        boolean result = super.onScroll(e1, e2, distanceX, distanceY);

        if(!result){
            //Do what you need to do when the scrolling stop here
        }

        return result;
    }

}

从理论上讲,也许是可行的,但实际上却无法按预期进行:即使在滚动过程中,您也会收到错误消息。
Deepscorn 2014年

SimpleOnGestureListener确实总是返回false
GaRRaPeTa

1

这对我有用。

我使用onFingerUp()方法丰富了现有的GestureDetector.OnGestureListener。该侦听器作为内置的GestureDetector进行所有操作,它还可以侦听手指向上事件(不是onFling(),因为只有在手指快速滑动并抬起手指时才会调用此事件)。

import android.content.Context;
import android.os.Handler;
import android.view.GestureDetector;
import android.view.MotionEvent;

public class FingerUpGestureDetector extends GestureDetector {
    FingerUpGestureDetector.OnGestureListener fListener;
    public FingerUpGestureDetector(Context context, OnGestureListener listener) {
        super(context, listener);
        fListener = listener;
    }

    public FingerUpGestureDetector(Context context, GestureDetector.OnGestureListener listener, OnGestureListener fListener) {
        super(context, listener);
        this.fListener = fListener;
    }

    public FingerUpGestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler, OnGestureListener fListener) {
        super(context, listener, handler);
        this.fListener = fListener;
    }

    public FingerUpGestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler, boolean unused, OnGestureListener fListener) {
        super(context, listener, handler, unused);
        this.fListener = fListener;
    }

    public interface OnGestureListener extends GestureDetector.OnGestureListener {
        boolean onFingerUp(MotionEvent e);
    }

    public static class SimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements FingerUpGestureDetector.OnGestureListener {
        @Override
        public boolean onFingerUp(MotionEvent e) {
            return false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (super.onTouchEvent(ev)) return true;
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            return fListener.onFingerUp(ev);
        }
        return false;
    }
}

0

我自己还没有做这件事,但是看着onTouch(),您总是得到一个序列0 <2> 1,所以末端必须为1才能松开手指。


对于一个ListView类似的问题: stackoverflow.com/questions/1768391/...
罗布肯特

谢谢你的建议。我已在View类的定义中添加了“ implements OnTouchListener”,因此整行内容为“公共类MyView扩展了android.view.View实现了OnTouchListener {”。但是,当我点击或滚动时,似乎从未调用过onTouch()方法。我有一个单独的GestureDetector类“扩展了SimpleOnGestureListener”这一事实是否意味着OnTouchListener被绕过了?
prepbgg

罗伯·肯特,我才刚刚看到您的评论。据我所知,OnScrollListener仅可用于ListView。我正在“扩展android.view.View”的View上使用绘图Canvas。您是否知道是否可以使用此方法(除了我已经在使用的GestureListener之外)使用OnScroll方法?
prepbgg

0

我不知道Android,但是看文档似乎罗布是对的:Android ACTION_UP常数尝试从getAction()检查ACTION_UP吗?

编辑:e1.getAction()显示什么?它曾经返回ACTION_UP吗?文档说它保存了初始的down事件,所以也许它还会在指针向上时通知

编辑:我能想到的只有两件事。您是否随时返回假?这可能会阻止ACTION_UP

我唯一想做的另一件事是有一个单独的事件,也许是onDown,并在onScroll中设置一个标记,例如isScrolling。将ACTION_UP赋予onDown并设置isScrolling时,您可以执行任何操作并将isScrolling重置为false。也就是说,假设onDown与onScroll一起被调用,并且getAction将在onDown期间返回ACTION_UP


是的,我尝试检查getAction(),但是在调用onScroll时始终返回2。onGestureListener可以识别的唯一“向上”事件是onSingleTapUp;滚动结束时不会返回此值。
prepbgg

但是在这段代码中,他们在SimpleOnGestureListener的onScroll中侦听ACTION_UP:code.google.com/p/connectbot/source/browse/trunk/connectbot/src/…–
Bob

谢谢,鲍勃。这是一个谜。我已经Log.i("onScroll", Integer.toString(e2.getAction()));在onScroll中插入了第一条语句,并且在滚动时LogCat仅显示字符串“ 2”。如果if(e2.getAction() == MotionEvent.ACTION_UP)在结束滚动后仍未在LogCat中记录任何内容而放置了Log语句。
prepbgg

抱歉,在这里尝试消除可能性。见我的编辑以上
鲍勃

再次感谢您的建议。不幸的是,e1.getAction()只返回0
prepbgg

0

我还没有尝试过/使用过,但是有一个方法的想法:

停止/中断每次滚动事件时重新绘制画布,等待1s,然后开始每次滚动时重新绘制画布。

这将导致仅在滚动结束时执行重绘,因为只有最后一个滚动实际上不会中断,才能完成重绘。

希望这个想法对您有帮助:)


谢谢你的建议。但是,我认为这不会帮助我。我不想中断重绘,因为我想平滑滚动。我的方法是将详细图形绘制到缓冲区位图,然后在onDraw()方法中将此位图绘制到视图的画布上。在滚动过程中,我重新绘制了位图,以略微反映滚动。我想做的是检测Scroll何时结束,以便我可以重新绘制缓冲区位图(这很耗时)以准备下一次滚动。
prepbgg

0

从GestureListener API的onScroll事件中提取:链接文本

公共抽象布尔boolean onScroll(MotionEvent e1,MotionEvent e2,float distanceX,float distanceY)自:API级别1

如果事件已消耗,则返回* true,否则返回false

也许一旦事件被消耗,动作就完成了,用户将手指从屏幕上移开了,或者至少完成了此onScroll动作

然后,您可以在IF语句中使用它来扫描== true,然后开始下一个操作。


谢谢你 您是否建议我的代码应调用onScroll方法并检查其返回的内容。如果是这样,我不确定如何执行此操作。我认为这是系统调用的方法来运行我插入的代码。我对文档中的“ returns”一词感到困惑,但是假设它们的意思是“ return”……换句话说,如果事件已被使用,则我的代码应在方法结束时返回true,否则返回false。你能为我澄清一下吗?
prepbgg 2010年

将触摸事件的返回结果用作手指抬起的提示!当函数返回true时,事件已结束。因此在以下情况中使用它:If(onScroll == True){DoThis(); }其他{EffectErrorHandler();}和中提琴!
汤姆·布鲁”皮多克

谢谢您的进一步评论,Blue。恐怕我还是不明白你的意思。onScroll()方法是OnSimpleGestureListener的重写方法(它是“扩展” SimpleOnGestureListener的类的方法),当手指在屏幕上拖动时由Android系统调用。我怎么称呼它?我应该在代码中的哪个位置拨打电话?我应该提供哪些论点?
prepbgg 2010年

Android参考上的SimpleOnGestureListener令人困惑:OnScroll的部分说该方法“如果事件被使用则返回true,否则返回false”,但类概述则说该类“不执行任何操作,并且对所有适用的方法返回false”。
prepbgg 2010年


0

如果您SimpleGestureDetector用于处理滚动事件,则可以执行此操作

fun handleTouchEvents(event: MotionEvent): Boolean {
    if(event.action == ACTION_UP) yourListener.onScrollEnd()
    return gestureDetector.onTouchEvent(event)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.