How to Detect Common Gestures- Detect Drag

Animation in Android
When we want to detect a subset (not all of them) of common gestures, we can use GestureDetector.SimpleOnGestureListener which is an implementation of the GestureDetector.OnGestureListener interface.
Note that if we use GestureDetector.OnGestureListener, all of the common gestures must be overrided.
GestureDetector.SimpleOnGestureListener provides an implementation for all of the on methods by returning false for all of them. Thus you can override only the methods you care about.
Whether or not you use GestureDetector.OnGestureListener, it's best practice to implement an onDown() method that returns true. This is because all gestures begin with an onDown() message. If you return false from onDown(), as GestureDetector.SimpleOnGestureListener does by default, the system assumes that you want to ignore the rest of the gesture, and the other methods of GestureDetector.OnGestureListener never get called. This has the potential to cause unexpected problems in your app. The only time you should return false from onDown() is if you truly want to ignore an entire gesture.
List of all common gestures are:
  • onDown(MotionEvent e): Notified when a tap occurs with the down MotionEvent that triggered it.
  • onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY): Notified of a fling event when it occurs with the initial on down MotionEvent and the matching up MotionEvent.
  • onLongPress(MotionEvent e): Notified when a long press occurs with the initial on down MotionEvent that trigged it.
  • onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY): Notified when a scroll occurs with the initial on down MotionEvent and the current move MotionEvent.
  • onShowPress(MotionEvent e): The user has performed a down MotionEvent and not performed a move or up yet.
  • onSingleTapUp(MotionEvent e): Notified when a tap occurs with the up MotionEvent that triggered it.

Example: Here we try to do a practice of overriding onScroll() to drag a drawable (image) inside a frame.
Create a new project with an empty template. I extend a new view called ImageBoard. To define new attributes (see also custom view in Android Apps) go to res->values->attrs (if there is not such file, you can create it: right click on the values folder and select New->values resource file then enter attrs as File name). Edit attrs.xml as below. Here we define just src attribute as a reference or color.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageBoard">
<attr name="src" format="reference|color"/>
</declare-styleable>
</resources>
view raw attrs.xml hosted with ❤ by GitHub

The ImageBoard class that is the core of this tiny project is like this.
public class ImageBoard extends View {
RectF mCurrentViewPort;
Rect mContentRect;
ImageView image;
int width;
int height;
int dragedWidth = 0;
int dragedHeight = 0;
int dx = 0;
int dy = 0;
Paint boarderPaint;
int drawableWidth;
int drawableHeight;
Drawable src;
//GestureDetector gd;
GestureDetectorCompat gdc;
public ImageBoard(Context context){
//this(context, null, 0);
super(context);
}
public ImageBoard(Context context, AttributeSet attrs){
//this(context, attrs, 0);
super(context, attrs);
init(context, attrs);
}
public ImageBoard(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public ImageBoard(Context context, @Nullable AttributeSet attrs,
int defStyleAttr, int defStyleRes){
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int desiredWidth = 300;
int desiredHeight = 300;
int width;
int height;
//width
if(widthMode == MeasureSpec.EXACTLY){
//Must be this size
width = widthSize;
}else if(widthMode == MeasureSpec.AT_MOST){
//Can't be bigger than...
width = Math.min(widthSize, desiredWidth);
}else{
//Be whatever you want
width = desiredWidth;
}
//height
if(heightMode == MeasureSpec.EXACTLY){
//Must be this size
height = heightSize;
}else if(heightMode == MeasureSpec.AT_MOST){
//Can't be bigger than...
height = Math.min(heightSize, desiredHeight);
}else{
//Be whatever you want
height = desiredHeight;
}
//MUST CALL THIS
setMeasuredDimension(width, height);
}
public void init(Context context, @Nullable AttributeSet attrs){
//gd = new GestureDetector(context, mGestureListener);
gdc = new GestureDetectorCompat(context, new MyGestureListener());
mCurrentViewPort = new RectF();
mContentRect = new Rect();
boarderPaint = new Paint();
boarderPaint.setColor(Color.BLUE);
boarderPaint.setStyle(Paint.Style.STROKE);
boarderPaint.setStrokeWidth(20);
TypedArray ta = context.
obtainStyledAttributes(attrs, R.styleable.ImageBoard, 0, 0);
try{
src = (Drawable) ta.getDrawable(R.styleable.ImageBoard_src);
drawableWidth = src.getIntrinsicWidth();
drawableHeight = src.getIntrinsicHeight();
}catch(Exception ex){
Toast.makeText(context, "Error: " + ex.toString(),
Toast.LENGTH_LONG + 3000).show();
}
finally {
ta.recycle();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mContentRect.left = (int) dx;
mContentRect.top = (int) dy;
mContentRect.right = (int) dx + drawableWidth;
mContentRect.bottom = (int) dy + drawableHeight;
mCurrentViewPort.left = 0;
mCurrentViewPort.top = 0;
mCurrentViewPort.right = width;
mCurrentViewPort.bottom = height;
if(src != null)
{
src.setBounds(mContentRect);
src.draw(canvas);
}
canvas.drawRect(mCurrentViewPort, boarderPaint);
}
class MyGestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onDown(MotionEvent e){
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY){
dx -= (int) distanceX;
dy -= (int) distanceY;
ImageBoard.this.invalidate();
return true;
}
}
@Override
public boolean onTouchEvent(MotionEvent e){
this.gdc.onTouchEvent(e);
// return super.onTouchEvent(e);
return true;
}
}
view raw ImageBoard.java hosted with ❤ by GitHub

and the activity_main.xml file is shown below.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.msh_n.myp.myapplication.ImageBoard
android:layout_width="100dp"
android:layout_height="100dp"
app:src="@drawable/ic_launcher_foreground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Now just run the project and see the result which should be something like this.

Comments