Android:如何检测滚动何时结束
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2089552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android: How to detect when a scroll has ended
提问by prepbgg
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).
我正在使用 GestureDetector.SimpleOnGestureListener 的 onScroll 方法在画布上滚动大位图。当滚动结束时,我想重新绘制位图,以防用户想要进一步滚动......离开位图的边缘,但我无法看到如何检测滚动何时结束(用户抬起手指从屏幕上)。
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.
e2.getAction() 似乎总是返回值 2,因此没有帮助。e2.getPressure 似乎返回相当恒定的值(大约 0.25),直到最后的 onScroll 调用时压力似乎下降到大约 0.13。我想我可以检测到这种压力的降低,但这远非万无一失。
There must be a better way: can anyone help, please?
一定有更好的方法:有人可以帮忙吗?
回答by Akos Cz
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);
}
回答by prepbgg
You should take a look at http://developer.android.com/reference/android/widget/Scroller.html. Especially this could be of help (sorted by relevance):
你应该看看http://developer.android.com/reference/android/widget/Scroller.html。特别是这可能会有所帮助(按相关性排序):
isFinished();
computeScrollOffset();
getFinalY(); getFinalX(); and getCurrY() getCurrX()
getDuration()
This implies that you have to create a Scroller.
这意味着您必须创建一个 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.
如果你想使用触摸,你也可以使用 GestureDetector 并定义你自己的画布滚动。以下示例正在创建一个 ScrollableImageView,为了使用它,您必须定义图像的测量值。您可以定义自己的滚动范围,并在完成滚动后重新绘制图像。
http://www.anddev.org/viewtopic.php?p=31487#31487
http://www.anddev.org/viewtopic.php?p=31487#31487
Depending on your code you should consider invalidate(int l, int t, int r, int b); for the invalidation.
根据您的代码,您应该考虑 invalidate(int l, int t, int r, int b); 为无效。
回答by Aron Cederholm
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.
它似乎发生在滚动结束时(即用户放开手指),这就是我正在使用的并且对我来说效果很好。
回答by prepbgg
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.
几个月后回到这个问题上,我现在采取了不同的策略:使用一个处理程序(如在 Android Snake 示例中)每 125 毫秒向应用程序发送一条消息,提示它检查 Scroll 是否已启动和自上次滚动事件以来是否已过去 100 毫秒以上。
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:
相关代码在 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;
// Scroll has ended, so insert code here
// 滚动已结束,请在此处插入代码
// which calls doDrawing() method
// 调用 doDrawing() 方法
// 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
// 以下指令重置时间检查时钟 // 当应用程序启动时主 Activity 调用此方法时,该时钟首次启动
mTimeCheckHandler.sleep(timeCheckInterval);
}
// rest of MyView class
// MyView 类的其余部分
}
}
and in the MyGestureDetector class
并在 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;
// 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
// 下一条指令导致调用 View 的 onDraw 方法 // 它将缓冲区位图绘制到屏幕上 // 移动以考虑滚动
[MyView].invalidate();
}
// rest of MyGestureDetector class
// MyGestureDetector 类的其余部分
}
}
回答by pepan
This is what worked for me.
这对我有用。
I've enriched the existing GestureDetector.OnGestureListenerwith 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).
我用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;
}
}
回答by Mike
I was looking into this same issue. I saw Akos Cz 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.
我正在研究同样的问题。我看到 Akos Cz 回答了您的问题。我创建了类似的东西,但在我的版本中,我注意到它只适用于常规滚动 - 这意味着不会产生一扔。但是,如果确实生成了投掷 - 无论我是否处理过投掷,那么它都不会在“onTouchEvent”中检测到“ACTION_UP”。现在也许这只是我的实现的问题,但如果是的话,我不知道为什么。
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.
经过进一步调查,我注意到在一次投掷过程中,“ACTION_UP”每次都被传递给“e2”中的“onFling”。所以我想这一定是为什么在这些情况下没有在“onTouchEvent”中处理它。
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 in my app:
为了让它对我有用,我只需要调用一个方法来处理“onFling”中的“ACTION_UP”,然后它就适用于两种类型的滚动。以下是我在我的应用程序中实施的确切步骤:
-initialized a "gestureScrolling" boolean to "false" in a constructor.
- 在构造函数中将“gestureScrolling”布尔值初始化为“false”。
-I set it to "true" in "onScroll"
- 我在“onScroll”中将其设置为“true”
-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.
- 创建了处理“ACTION_UP”事件的方法。在该事件中,我将“gestureSCrolling”重置为 false,然后执行我需要执行的其余处理。
-in "onTouchEvent", if an "ACTION_UP" was detected and "gestureScrolling" = true, then I called my method to handle "ACTION_UP"
-在“onTouchEvent”中,如果检测到“ACTION_UP”并且“gestureScrolling”=true,那么我调用我的方法来处理“ACTION_UP”
-And the part that I did that was different was: I also called my method to handle "ACTION_UP" inside of "onFling".
-我所做的不同之处在于:我还调用了我的方法来处理“onFling”内部的“ACTION_UP”。
回答by resp78
I am sure it is too late for you, however, it seems I have found the right solution to your original question and not necessary the intention.
我相信对您来说为时已晚,但是,似乎我已经找到了您最初问题的正确解决方案,而并非必要的意图。
If you are using Scroller/OverScroller Object for scrolling you should check the return value from the following function.
如果您使用 Scroller/OverScroller 对象进行滚动,您应该检查以下函数的返回值。
public boolean computeScrollOffset()
enjoy
请享用
harvinder
收割机
回答by nautilusvn
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;
}
}
回答by b0x0rz
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.
在每次滚动事件上停止/中断重绘画布等待 1 秒,然后在每次滚动时开始重绘画布。
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 :)
希望这个想法可以帮助你:)
回答by Tom 'Blue' Piddock
Extract from the onScroll event from GestureListener API: link text
从 GestureListener API 的 onScroll 事件中提取:链接文本
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
public abstract boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) 从:API Level 1
如果事件被消耗,则返回 * true,否则返回 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
也许一旦事件被消费,动作就完成了,用户已经将手指从屏幕上移开,或者至少完成了这个 onScroll 动作
You can then use this in an IF statement to scan for == true and then commence with the next action.
然后,您可以在 IF 语句中使用它来扫描 == true,然后开始下一个操作。