Android 网格布局上的投掷手势检测
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/937313/
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
Fling gesture detection on grid layout
提问by gav
I want to get fling
gesture detection working in my Android application.
我想fling
在我的 Android 应用程序中进行手势检测。
What I have is a GridLayout
that contains 9 ImageView
s. The source can be found here: Romain Guys's Grid Layout.
我所拥有的是一个GridLayout
包含 9ImageView
秒的。来源可以在这里找到:Romain Guys 的网格布局。
That file I take is from Romain Guy's Photostream applicationand has only been slightly adapted.
我拿的那个文件来自 Romain Guy 的Photostream 应用程序,只是稍微做了一点修改。
For the simple click situation I need only set the onClickListener
for each ImageView
I add to be the main activity
which implements View.OnClickListener
. It seems infinitely more complicated to implement something that recognizes a fling
. I presume this is because it may span views
?
对于简单的点击情况,我只需要将onClickListener
for each ImageView
I add设置为activity
实现View.OnClickListener
. 实现识别 a 的东西似乎要复杂得多fling
。我想这是因为它可能跨越views
?
If my activity implements
OnGestureListener
I don't know how to set that as the gesture listener for theGrid
or theImage
views that I add.public class SelectFilterActivity extends Activity implements View.OnClickListener, OnGestureListener { ...
If my activity implements
OnTouchListener
then I have noonFling
method tooverride
(it has two events as parameters allowing me to determine if the fling was noteworthy).public class SelectFilterActivity extends Activity implements View.OnClickListener, OnTouchListener { ...
If I make a custom
View
, likeGestureImageView
that extendsImageView
I don't know how to tell the activity that afling
has occurred from the view. In any case, I tried this and the methods weren't called when I touched the screen.
如果我的活动实现,
OnGestureListener
我不知道如何将其设置为我添加Grid
的Image
视图或视图的手势侦听器。public class SelectFilterActivity extends Activity implements View.OnClickListener, OnGestureListener { ...
如果我的活动实现了,
OnTouchListener
那么我没有onFling
方法override
(它有两个事件作为参数,允许我确定是否值得一提)。public class SelectFilterActivity extends Activity implements View.OnClickListener, OnTouchListener { ...
如果我做一个 custom
View
,像GestureImageView
这样扩展ImageView
我不知道如何fling
从视图中告诉活动 a已经发生。无论如何,我尝试了这个并且当我触摸屏幕时没有调用这些方法。
I really just need a concrete example of this working across views. What, when and how should I attach this listener
? I need to be able to detect single clicks also.
我真的只需要一个跨视图工作的具体例子。我应该什么,何时以及如何附加这个listener
?我还需要能够检测到单击。
// Gesture detection
mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
int dx = (int) (e2.getX() - e1.getX());
// don't accept the fling if it's too short
// as it may conflict with a button push
if (Math.abs(dx) > MAJOR_MOVE && Math.abs(velocityX) > Math.absvelocityY)) {
if (velocityX > 0) {
moveRight();
} else {
moveLeft();
}
return true;
} else {
return false;
}
}
});
Is it possible to lay a transparent view over the top of my screen to capture flings?
是否可以在我的屏幕顶部放置一个透明视图来捕捉投掷?
If I choose not to inflate
my child image views from XML can I pass the GestureDetector
as a constructor parameter to a new subclass of ImageView
that I create?
如果我选择不inflate
从 XML 传递给我的子图像视图,我可以将它GestureDetector
作为构造函数参数传递给ImageView
我创建的新子类吗?
This is the very simple activity that I'm trying to get the fling
detection to work for: SelectFilterActivity (Adapted from photostream).
这是我试图让fling
检测工作的非常简单的活动:SelectFilterActivity (Adapted from photostream)。
I've been looking at these sources:
我一直在查看这些来源:
Nothing has worked for me so far and I was hoping for some pointers.
到目前为止,没有任何东西对我有用,我希望得到一些指导。
采纳答案by gav
Thanks to Code Shogun, whose code I adapted to my situation.
感谢Code Shogun,他的代码适合我的情况。
Let your activity implementOnClickListener
as usual:
让您的活动OnClickListener
照常实施:
public class SelectFilterActivity extends Activity implements OnClickListener {
private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 250;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
private GestureDetector gestureDetector;
View.OnTouchListener gestureListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* ... */
// Gesture detection
gestureDetector = new GestureDetector(this, new MyGestureDetector());
gestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
};
}
class MyGestureDetector extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
// right to left swipe
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Toast.makeText(SelectFilterActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Toast.makeText(SelectFilterActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
// nothing
}
return false;
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
}
}
Attach your gesture listener to all the views you add to the main layout;
将您的手势侦听器附加到您添加到主布局的所有视图;
// Do this for each view added to the grid
imageView.setOnClickListener(SelectFilterActivity.this);
imageView.setOnTouchListener(gestureListener);
Watch in awe as your overridden methods are hit, both the onClick(View v)
of the activity and the onFling
of the gesture listener.
敬畏地看着您的覆盖方法被命中,无论onClick(View v)
是活动onFling
的还是手势侦听器的。
public void onClick(View v) {
Filter f = (Filter) v.getTag();
FilterFullscreenActivity.show(this, input, f);
}
The post 'fling' dance is optional but encouraged.
后“甩”舞是可选的,但鼓励。
回答by Xion
One of the answers above mentions handling different pixel density but suggests computing the swipe parameters by hand. It is worth noting that you can actually obtain scaled, reasonable values from the system using ViewConfiguration
class:
上面的答案之一提到处理不同的像素密度,但建议手动计算滑动参数。值得注意的是,您实际上可以使用ViewConfiguration
类从系统中获取按比例缩放的合理值:
final ViewConfiguration vc = ViewConfiguration.get(getContext());
final int swipeMinDistance = vc.getScaledPagingTouchSlop();
final int swipeThresholdVelocity = vc.getScaledMinimumFlingVelocity();
final int swipeMaxOffPath = vc.getScaledTouchSlop();
// (there is also vc.getScaledMaximumFlingVelocity() one could check against)
I noticed that using these values causes the "feel" of fling to be more consistent between the application and rest of system.
我注意到使用这些值会使应用程序和系统其余部分之间的“感觉”更加一致。
回答by Thomas Fankhauser
I do it a little different, and wrote an extra detector class that implements the View.onTouchListener
我做的有点不同,并编写了一个额外的检测器类来实现 View.onTouchListener
onCreate
is simply add it to the lowest layout like this:
onCreate
只需将其添加到最低布局,如下所示:
ActivitySwipeDetector activitySwipeDetector = new ActivitySwipeDetector(this);
lowestLayout = (RelativeLayout)this.findViewById(R.id.lowestLayout);
lowestLayout.setOnTouchListener(activitySwipeDetector);
where id.lowestLayout is the id.xxx for the view lowest in the layout hierarchy and lowestLayout is declared as a RelativeLayout
其中 id.lowestLayout 是布局层次结构中最低的视图的 id.xxx,而最低布局被声明为一个相对布局
And then there is the actual activity swipe detector class:
然后是实际的活动滑动检测器类:
public class ActivitySwipeDetector implements View.OnTouchListener {
static final String logTag = "ActivitySwipeDetector";
private Activity activity;
static final int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;
public ActivitySwipeDetector(Activity activity){
this.activity = activity;
}
public void onRightSwipe(){
Log.i(logTag, "RightToLeftSwipe!");
activity.doSomething();
}
public void onLeftSwipe(){
Log.i(logTag, "LeftToRightSwipe!");
activity.doSomething();
}
public void onDownSwipe(){
Log.i(logTag, "onTopToBottomSwipe!");
activity.doSomething();
}
public void onUpSwipe(){
Log.i(logTag, "onBottomToTopSwipe!");
activity.doSomething();
}
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN: {
downX = event.getX();
downY = event.getY();
return true;
}
case MotionEvent.ACTION_UP: {
upX = event.getX();
upY = event.getY();
float deltaX = downX - upX;
float deltaY = downY - upY;
// swipe horizontal?
if(Math.abs(deltaX) > Math.abs(deltaY))
{
if(Math.abs(deltaX) > MIN_DISTANCE){
// left or right
if(deltaX > 0) { this.onRightSwipe(); return true; }
if(deltaX < 0) { this.onLeftSwipe(); return true; }
}
else {
Log.i(logTag, "Horizontal Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
return false; // We don't consume the event
}
}
// swipe vertical?
else
{
if(Math.abs(deltaY) > MIN_DISTANCE){
// top or down
if(deltaY < 0) { this.onDownSwipe(); return true; }
if(deltaY > 0) { this.onUpSwipe(); return true; }
}
else {
Log.i(logTag, "Vertical Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
return false; // We don't consume the event
}
}
return true;
}
}
return false;
}
}
Works really good for me!
对我来说真的很好用!
回答by Marek Sebera
I slightly modified and repaired solution from Thomas Fankhauser
我稍微修改和修复了Thomas Fankhauser 的解决方案
Whole system consists from two files, SwipeInterfaceand ActivitySwipeDetector
整个系统由两个文件组成,SwipeInterface和ActivitySwipeDetector
SwipeInterface.java
滑动界面.java
import android.view.View;
public interface SwipeInterface {
public void bottom2top(View v);
public void left2right(View v);
public void right2left(View v);
public void top2bottom(View v);
}
Detector
探测器
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class ActivitySwipeDetector implements View.OnTouchListener {
static final String logTag = "ActivitySwipeDetector";
private SwipeInterface activity;
static final int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;
public ActivitySwipeDetector(SwipeInterface activity){
this.activity = activity;
}
public void onRightToLeftSwipe(View v){
Log.i(logTag, "RightToLeftSwipe!");
activity.right2left(v);
}
public void onLeftToRightSwipe(View v){
Log.i(logTag, "LeftToRightSwipe!");
activity.left2right(v);
}
public void onTopToBottomSwipe(View v){
Log.i(logTag, "onTopToBottomSwipe!");
activity.top2bottom(v);
}
public void onBottomToTopSwipe(View v){
Log.i(logTag, "onBottomToTopSwipe!");
activity.bottom2top(v);
}
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN: {
downX = event.getX();
downY = event.getY();
return true;
}
case MotionEvent.ACTION_UP: {
upX = event.getX();
upY = event.getY();
float deltaX = downX - upX;
float deltaY = downY - upY;
// swipe horizontal?
if(Math.abs(deltaX) > MIN_DISTANCE){
// left or right
if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
}
else {
Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
}
// swipe vertical?
if(Math.abs(deltaY) > MIN_DISTANCE){
// top or down
if(deltaY < 0) { this.onTopToBottomSwipe(v); return true; }
if(deltaY > 0) { this.onBottomToTopSwipe(v); return true; }
}
else {
Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
v.performClick();
}
}
}
return false;
}
}
it is used like this:
它是这样使用的:
ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);
LinearLayout swipe_layout = (LinearLayout) findViewById(R.id.swipe_layout);
swipe_layout.setOnTouchListener(swipe);
And in implementing Activity
you need to implement methods from SwipeInterface, and you can find out on which View the Swipe Eventwas called.
在实现中,Activity
您需要从SwipeInterface实现方法,您可以找出在哪个 View 上调用了Swipe Event。
@Override
public void left2right(View v) {
switch(v.getId()){
case R.id.swipe_layout:
// do your stuff here
break;
}
}
回答by paiego
The swipe gesture detector code above is very useful! You may however wish to make this solution density agnostic by using the following relative values (REL_SWIPE)
rather than the absolute values (SWIPE_)
上面的滑动手势检测器代码非常有用!但是,您可能希望通过使用以下相对值(REL_SWIPE)
而不是绝对值来使此解决方案密度不可知(SWIPE_)
DisplayMetrics dm = getResources().getDisplayMetrics();
int REL_SWIPE_MIN_DISTANCE = (int)(SWIPE_MIN_DISTANCE * dm.densityDpi / 160.0f);
int REL_SWIPE_MAX_OFF_PATH = (int)(SWIPE_MAX_OFF_PATH * dm.densityDpi / 160.0f);
int REL_SWIPE_THRESHOLD_VELOCITY = (int)(SWIPE_THRESHOLD_VELOCITY * dm.densityDpi / 160.0f);
回答by Exterminator13
My version of solution proposed by Thomas Fankhauserand Marek Sebera(does not handle vertical swipes):
我的Thomas Fankhauser和Marek Sebera提出的解决方案版本(不处理垂直滑动):
SwipeInterface.java
滑动界面.java
import android.view.View;
public interface SwipeInterface {
public void onLeftToRight(View v);
public void onRightToLeft(View v);
}
ActivitySwipeDetector.java
ActivitySwipeDetector.java
import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
public class ActivitySwipeDetector implements View.OnTouchListener {
static final String logTag = "ActivitySwipeDetector";
private SwipeInterface activity;
private float downX, downY;
private long timeDown;
private final float MIN_DISTANCE;
private final int VELOCITY;
private final float MAX_OFF_PATH;
public ActivitySwipeDetector(Context context, SwipeInterface activity){
this.activity = activity;
final ViewConfiguration vc = ViewConfiguration.get(context);
DisplayMetrics dm = context.getResources().getDisplayMetrics();
MIN_DISTANCE = vc.getScaledPagingTouchSlop() * dm.density;
VELOCITY = vc.getScaledMinimumFlingVelocity();
MAX_OFF_PATH = MIN_DISTANCE * 2;
}
public void onRightToLeftSwipe(View v){
Log.i(logTag, "RightToLeftSwipe!");
activity.onRightToLeft(v);
}
public void onLeftToRightSwipe(View v){
Log.i(logTag, "LeftToRightSwipe!");
activity.onLeftToRight(v);
}
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN: {
Log.d("onTouch", "ACTION_DOWN");
timeDown = System.currentTimeMillis();
downX = event.getX();
downY = event.getY();
return true;
}
case MotionEvent.ACTION_UP: {
Log.d("onTouch", "ACTION_UP");
long timeUp = System.currentTimeMillis();
float upX = event.getX();
float upY = event.getY();
float deltaX = downX - upX;
float absDeltaX = Math.abs(deltaX);
float deltaY = downY - upY;
float absDeltaY = Math.abs(deltaY);
long time = timeUp - timeDown;
if (absDeltaY > MAX_OFF_PATH) {
Log.i(logTag, String.format("absDeltaY=%.2f, MAX_OFF_PATH=%.2f", absDeltaY, MAX_OFF_PATH));
return v.performClick();
}
final long M_SEC = 1000;
if (absDeltaX > MIN_DISTANCE && absDeltaX > time * VELOCITY / M_SEC) {
if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
} else {
Log.i(logTag, String.format("absDeltaX=%.2f, MIN_DISTANCE=%.2f, absDeltaX > MIN_DISTANCE=%b", absDeltaX, MIN_DISTANCE, (absDeltaX > MIN_DISTANCE)));
Log.i(logTag, String.format("absDeltaX=%.2f, time=%d, VELOCITY=%d, time*VELOCITY/M_SEC=%d, absDeltaX > time * VELOCITY / M_SEC=%b", absDeltaX, time, VELOCITY, time * VELOCITY / M_SEC, (absDeltaX > time * VELOCITY / M_SEC)));
}
}
}
return false;
}
}
回答by georgiecasey
This question is kind of old and in July 2011 Google released the Compatibility Package, revision 3)which includes the ViewPager
that works with Android 1.6 upwards. The GestureListener
answers posted for this question don't feel very elegant on Android. If you're looking for the code used in switching between photos in the Android Gallery or switching views in the new Play Market app then it's definitely ViewPager
.
这个问题有点老了,2011 年 7 月谷歌发布了兼容包,修订版 3),其中包括ViewPager
适用于 Android 1.6 以上的版本。GestureListener
为这个问题发布的答案在 Android 上感觉不是很优雅。如果您正在寻找用于在 Android 图库中的照片之间切换或在新的 Play Market 应用程序中切换视图的代码,那么它绝对是ViewPager
.
Here's some links for more info:
以下是更多信息的一些链接:
回答by Viswanath Lekshmanan
回答by MappaM
There's some proposition over the web (and this page) to use ViewConfiguration.getScaledTouchSlop()to have a device-scaled value for SWIPE_MIN_DISTANCE
.
网络(和本页)上有一些建议使用 ViewConfiguration。getScaledTouchSlop()以获得 的设备缩放值SWIPE_MIN_DISTANCE
。
getScaledTouchSlop()
is intended for the "scrollingthreshold" distance, not swipe. The scrolling threshold distance has to be smaller than a "swing between page" threshold distance. For example, this function returns 12 pixels on my Samsung GS2, and the examples quoted in this page are around 100 pixels.
getScaledTouchSlop()
用于“滚动阈值”距离,而不是滑动。滚动阈值距离必须小于“页面之间的摆动”阈值距离。比如这个函数在我的三星GS2上返回12像素,本页引用的例子都是100像素左右。
With API Level 8 (Android 2.2, Froyo), you've got getScaledPagingTouchSlop()
, intended for page swipe.
On my device, it returns 24 (pixels). So if you're on API Level < 8, I think "2 * getScaledTouchSlop()
" should be the "standard" swipe threshold.
But users of my application with small screens told me that it was too few... As on my application, you can scroll vertically, and change page horizontally. With the proposed value, they sometimes change page instead of scrolling.
使用 API 级别 8(Android 2.2,Froyo),您可以使用getScaledPagingTouchSlop()
,用于页面滑动。在我的设备上,它返回 24(像素)。因此,如果您的 API 级别 < 8,我认为“2 * getScaledTouchSlop()
”应该是“标准”滑动阈值。但是我的应用程序的小屏幕用户告诉我它太少了......就像在我的应用程序中一样,您可以垂直滚动,并水平更改页面。使用建议的值,他们有时会更改页面而不是滚动。
回答by Noah
Also as a minor enhancement.
也作为一个小的增强。
The main reason for the try/catch block is that e1 could be null for the initial movement. in addition to the try/catch, include a test for null and return. similar to the following
try/catch 块的主要原因是初始移动时 e1 可能为空。除了 try/catch 之外,还包括对 null 和 return 的测试。类似于以下
if (e1 == null || e2 == null) return false;
try {
...
} catch (Exception e) {}
return false;