Android:如何检测滚动何时结束

发布于 2024-08-18 10:59:06 字数 308 浏览 3 评论 0原文

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

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

一定有更好的方法:有人可以帮忙吗?

I am using the onScroll method of GestureDetector.SimpleOnGestureListener to scroll a large bitmap on a canvas. When the scroll has ended I want to redraw the bitmap in case the user wants to scroll further ... off the edge of the bitmap, but I can't see how to detect when the scroll has ended (the user has lifted his finger from the screen).

e2.getAction() always seems to return the value 2 so that is no help.
e2.getPressure seems to return fairly constant values (around 0.25) until the final onScroll call when the pressure seems to fall to about 0.13. I suppose I could detect this reduction in pressure, but this will be far from foolproof.

There must be a better way: can anyone help, please?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

迷路的信 2024-08-25 10:59:06

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

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

Here is how I solved the problem. Hope this helps.

// 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);
}
糖粟与秋泊 2024-08-25 10:59:06

您应该查看 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

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

You should take a look at http://developer.android.com/reference/android/widget/Scroller.html.
Especially this could be of help (sorted by relevance):

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

This implies that you have to create a Scroller.

If you want to use touching you could also use GestureDetector and define your own canvas scrolling. The following sample is creating a ScrollableImageView and in order to use it, you have to define the measurements of your image. You can define your own scrolling range and after finishing your scrolling the image gets redrawn.

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

Depending on your code you should consider invalidating (int l, int t, int r, int b); for the invalidation.

以往的大感动 2024-08-25 10:59:06

几个月后回到这个问题,我现在采取了不同的策略:使用处理程序(如 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() {

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

// 以下指令重置时间检查时钟
// 时钟第一次启动的时间是
// 当应用程序启动时,主 Activity 调用此方法

        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 类的其余部分

}

Coming back to this after a few months I've now followed a different tack: using a Handler (as in the Android Snake sample) to send a message to the app every 125 milliseconds which prompts it to check whether a Scroll has been started and whether more than 100 milliseconds has elapsed since the last scroll event.

This seems to work pretty well, but if anyone can see any drawbacks or possible improvements I should be grateful to hear of them.

The relevant the code is in the MyView class:

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;

// Scroll has ended, so insert code here

// which calls doDrawing() method

// to redraw bitmap re-centred where scroll ended

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

// code to draw large buffer bitmap onto the view's canvas
// positioned to take account of any scroll that is in progress

}

public void doDrawing() {

// code to do detailed (and time-consuming) drawing
// onto large buffer bitmap

// the following instruction resets the Time Check clock
// the clock is first started when
// the main activity calls this method when the app starts

        mTimeCheckHandler.sleep(timeCheckInterval);
}

// rest of MyView class

}

and in the MyGestureDetector class

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;

// the next instruction causes the View's onDraw method to be called
// which plots the buffer bitmap onto the screen
// shifted to take account of the scroll

    [MyView].invalidate();

}

// rest of MyGestureDetector class

}

苄①跕圉湢 2024-08-25 10:59:06
SimpleOnGestureListener.onFling() 

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

SimpleOnGestureListener.onFling() 

It seems to take place when a scroll ends (i.e. the user lets the finger go), that's what I am using and it works great for me.

笨死的猪 2024-08-25 10:59:06

这对我有用。

我用 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;
    }
}

This is what worked for me.

I've enriched the existing GestureDetector.OnGestureListener with onFingerUp() method. This listener does everything as the built-in GestureDetector and it can also listen to the finger up event (it's not onFling() as this is called only when the finger is lifted up along with a quick swipe action).

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;
    }
}
赏烟花じ飞满天 2024-08-25 10:59:06

我认为这会根据你的需要工作

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

I think this will work as you need

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;
    }
    
}
心舞飞扬 2024-08-25 10:59:06

我确信这对你来说已经太晚了,但是,似乎我已经找到了你最初问题的正确解决方案,但不一定是意图。

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

public boolean computeScrollOffset() 

I am sure it is too late for you, however, it seems I have found the right solution to your original question and not necessarily the intention.

If you are using Scroller/OverScroller Object for scrolling you should check the return value from the following function.

public boolean computeScrollOffset() 
悟红尘 2024-08-25 10:59:06

我正在研究同样的问题。我看到了 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”

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

I was looking into this same issue. I saw Akos Cz's answer to your question. I created something similar, but with my version, I noticed that it only worked for a regular scroll - meaning one that doesn't generate a fling. But if a fling did get generated - regardless if I processed a fling or not, then it did NOT detect the "ACTION_UP" in "onTouchEvent". Now maybe this was just something with my implementation, but if it was I couldn't figure out why.

After further investigation, I noticed that during a fling, the "ACTION_UP" was passed into "onFling" in "e2" every time. So I figured that must be why it wasn't being handled in "onTouchEvent" in those instances.

To make it work for me I only had to call a method to handle the "ACTION_UP" in "onFling" and then it worked for both types of scrolling. Below are the exact steps I took to implement my app:

-initialized a "gestureScrolling" boolean to "false" in a constructor.

-I set it to "true" in "onScroll"

-created a method to handle the "ACTION_UP" event. Inside that event, I reset "gestureSCrolling" to false and then did the rest of the processing I needed to do.

-in "onTouchEvent", if an "ACTION_UP" was detected and "gestureScrolling" = true, then I called my method to handle "ACTION_UP"

-And the part that I did that was different was: I also called my method to handle "ACTION_UP" inside of "onFling".

烟沫凡尘 2024-08-25 10:59:06

我自己没有这样做过,但是查看 onTouch() 你总是得到一个序列 0<2>1,所以手指抬起的结尾必须是 1。

I haven't done this myself but looking at onTouch() you always get a sequence 0<2>1, so the end has to be a 1 for finger lift.

吃不饱 2024-08-25 10:59:06

我不了解 Android,但查看文档,Rob 似乎是对的: Android ACTION_UP 常量 尝试从 getAction() 检查 ACTION_UP 吗?

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

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

我唯一尝试的另一件事是有一个单独的事件,可能是 onDown,并在 onScroll 中设置一个标志,例如 isScrolling。当 ACTION_UP 被赋予 onDown 并设置 isScrolling 时,你可以做任何你想做的事情并将 isScrolling 重置为 false。也就是说,假设 onDown 与 onScroll 一起被调用,并且 getAction 将在 onDown 期间返回 ACTION_UP

I don't know Android, but looking at the documentation it seems Rob is right: Android ACTION_UP constant Try checking for ACTION_UP from getAction()?

Edit: What does e1.getAction() show? Does it ever return ACTION_UP? The documentation says it holds the initial down event, so maybe it'll also notify when the pointer is up

Edit: Only two more things I can think of. Are you returning false at any point? That may prevent ACTION_UP

The only other thing I'd try is to have a seperate event, maybe onDown, and set a flag within onScroll such as isScrolling. When ACTION_UP is given to onDown and isScrolling is set then you could do whatever you want and reset isScrolling to false. That is, assuming onDown gets called along with onScroll, and getAction will return ACTION_UP during onDown

落墨 2024-08-25 10:59:06

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

在每个滚动事件等待1秒时停止/中断重绘画布,然后在每个滚动上开始重绘画布。

这将导致仅在滚动结束,因为只有最后一个滚动实际上会不间断地完成重绘。

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

i have not tried / used this but an idea for an approach:

stop / interrupt redrawing canvas on EVERY scroll event wait 1s and then start redrawing canvas on EVERY scroll.

this will lead to performing the redraw only at scroll end as only the last scroll will actually be uninterrupted for the redraw to complete.

hope this idea helps you :)

漫雪独思 2024-08-25 10:59:06

从 GestureListener API 的 onScroll 事件中摘录: 链接文本< /a>

公共抽象布尔值onScroll
(运动事件 e1、运动事件 e2、浮动
距离X,浮动距离Y)自:API
1级

退货
* 如果事件被消耗则为 true,否则为 false

也许一旦事件被消耗,操作就完成了,并且用户已经将手指从屏幕上移开,或者至少完成了这个 onScroll 操作,

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

Extract from the onScroll event from GestureListener API: link text

public abstract boolean onScroll
(MotionEvent e1, MotionEvent e2, float
distanceX, float distanceY) Since: API
Level 1

Returns
* true if the event is consumed, else false

Perhaps once the event has been consumed, the action is finished and the user has taken their finger off the screen or at the least finished this onScroll action

You can then use this in an IF statement to scan for == true and then commence with the next action.

眼角的笑意。 2024-08-25 10:59:06

如果您使用 SimpleGestureDetector 来处理滚动事件,您可以这样做

fun handleTouchEvents(event: MotionEvent): Boolean {
    if(event.action == ACTION_UP) yourListener.onScrollEnd()
    return gestureDetector.onTouchEvent(event)
}

If you're using SimpleGestureDetector to handle your scroll events, you can do this

fun handleTouchEvents(event: MotionEvent): Boolean {
    if(event.action == ACTION_UP) yourListener.onScrollEnd()
    return gestureDetector.onTouchEvent(event)
}
囚你心 2024-08-25 10:59:06

我尝试向手势检测器添加附加功能。希望它可以帮助人们更好地利用时间... 要点

My attempt at adding additional functionality to the gesture detector. Hope it helps someone put his time to better use... gist

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文