Track Touch Events and Detecting Touch Gestures in a View or Activity
A "touch gesture" occurs when a user places one or more fingers on the touch screen,
and your application interprets that pattern of touches as a particular gesture.
There are correspondingly two phases to gesture detection.
Let discuss these phases more precisely.
Gather data
When a user places one or more fingers on the screen, this triggers the callback onTouchEvent() on the View that received the touch events. For each sequence of touch events (position, pressure, size, addition of another finger, etc.) that is ultimately identified as a gesture, onTouchEvent() is fired several times.
The gesture starts when the user first touches the screen, continues as the system tracks the position of the user's finger(s), and ends by capturing the final event of the user's fingers leaving the screen. Throughout this interaction, the MotionEvent delivered to onTouchEvent() provides the details of every interaction. Your app can use the data provided by the MotionEvent to determine if a gesture it cares about happened.
Note: For a single view we have an alternative to onTouchEvent(). The setOnTouchListener() method can attach a View.OnTouchListener object to a view.
Note: Beware of creating a listener that returns false for the ACTION_DOWN event. If you do this, the listener will not be called for the subsequent ACTION_MOVE and ACTION_UP string of events. This is because ACTION_DOWN is the starting point for all touch events.
Interpret the data There are some common gestures that we can detect by GestureDetector class. Common gestures include onDown(), onLongPress(), onFling(), and so on. You can use GestureDetector in conjunction with the onTouchEvent() method described above.
But there are situations in which we interpret the gathered data manually for example we want to detect a custom gesture. The following practice shows an example which we interpret the data manually.
Example:
Here we continue our example discussed in Animation by Canvas and just add some variables and suitable methods but the example is completely presented here to make it more easy to read.
Create a new Android Studio project and select an Empty Activity template. In CViewN class we add some proper variables(objects) and override onTouchEvent() method that has pivotal roll here.
Edit the activity_main.xml like below.
The MainActivity class which is an activity can also override the onTouchEvent() method. It makes it possible to for example drag the complete view instance inside the activity or any function related to whole view instance inside this activity. The below code shows how we can drag whole view when move our finger on touch.
Now run the application. The result should be something like below.
- Gather data about touch events.
- Interpret the data to see if it meets the criteria for any of the gestures your app supports.
Let discuss these phases more precisely.
Gather data
When a user places one or more fingers on the screen, this triggers the callback onTouchEvent() on the View that received the touch events. For each sequence of touch events (position, pressure, size, addition of another finger, etc.) that is ultimately identified as a gesture, onTouchEvent() is fired several times.
The gesture starts when the user first touches the screen, continues as the system tracks the position of the user's finger(s), and ends by capturing the final event of the user's fingers leaving the screen. Throughout this interaction, the MotionEvent delivered to onTouchEvent() provides the details of every interaction. Your app can use the data provided by the MotionEvent to determine if a gesture it cares about happened.
Note: For a single view we have an alternative to onTouchEvent(). The setOnTouchListener() method can attach a View.OnTouchListener object to a view.
Note: Beware of creating a listener that returns false for the ACTION_DOWN event. If you do this, the listener will not be called for the subsequent ACTION_MOVE and ACTION_UP string of events. This is because ACTION_DOWN is the starting point for all touch events.
Interpret the data There are some common gestures that we can detect by GestureDetector class. Common gestures include onDown(), onLongPress(), onFling(), and so on. You can use GestureDetector in conjunction with the onTouchEvent() method described above.
But there are situations in which we interpret the gathered data manually for example we want to detect a custom gesture. The following practice shows an example which we interpret the data manually.
Example:
Here we continue our example discussed in Animation by Canvas and just add some variables and suitable methods but the example is completely presented here to make it more easy to read.
Create a new Android Studio project and select an Empty Activity template. In CViewN class we add some proper variables(objects) and override onTouchEvent() method that has pivotal roll here.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CViewN extends View { | |
int width; | |
int height; | |
int gapV; | |
int x; | |
int y; | |
Paint vaweP; | |
Paint cPaint1; | |
Paint cPaint2; | |
Paint cPaint3; | |
Paint cPaint4; | |
int alpha = 0; | |
RectF rectCircle; | |
float initialRadius; | |
float radiusOffset; | |
ValueAnimator anim = ValueAnimator.ofFloat(0, 35); | |
int mActivePointerId = MotionEvent.INVALID_POINTER_ID; | |
float mLastTouchX; | |
float mLastTouchY; | |
float mPosX = 0f; | |
float mPosY = 0f; | |
@Override | |
public boolean onTouchEvent(MotionEvent ev){ | |
final int action = ev.getActionMasked(); | |
switch (action){ | |
case MotionEvent.ACTION_DOWN: | |
{ | |
final int pointerIndex = ev.getActionIndex(); | |
final float x = ev.getX(pointerIndex); | |
final float y = ev.getY(pointerIndex); | |
// Remember where we started (for dragging) | |
mLastTouchX = x; | |
mLastTouchY = y; | |
mActivePointerId = ev.getPointerId(pointerIndex); | |
break; | |
} | |
case MotionEvent.ACTION_MOVE: | |
{ | |
// Find the index of the active pointer and fetch its position | |
final int pointerIndex = ev.findPointerIndex(mActivePointerId); | |
final float x = ev.getX(pointerIndex); | |
final float y = ev.getY(pointerIndex); | |
// Calculate the distance moved | |
final float dx = x - mLastTouchX; | |
final float dy = y - mLastTouchY; | |
mPosX += dx; | |
mPosY += dy; | |
// Remember this touch position for the next move event | |
mLastTouchX = x; | |
mLastTouchY = y; | |
invalidate(); | |
break; | |
} | |
case MotionEvent.ACTION_UP: | |
{ | |
mActivePointerId = MotionEvent.INVALID_POINTER_ID; | |
break; | |
} | |
case MotionEvent.ACTION_CANCEL: | |
{ | |
mActivePointerId = MotionEvent.INVALID_POINTER_ID; | |
break; | |
} | |
case MotionEvent.ACTION_POINTER_UP: | |
{ | |
final int pointerIndex = ev.getActionIndex(); | |
final int pointerId = ev.getPointerId(pointerIndex); | |
if(pointerId == mActivePointerId){ | |
// This was our active pointer going up. Choose a new | |
// active pointer and adjust accordingly. | |
final int newPointerIndex = pointerIndex == 0 ? 1 : 0; | |
mLastTouchX = ev.getX(newPointerIndex); | |
mLastTouchY = ev.getY(newPointerIndex); | |
mActivePointerId = ev.getPointerId(newPointerIndex); | |
} | |
break; | |
} | |
} | |
return true; | |
} | |
public CViewN(Context context){ | |
//this(context, null, 0); | |
super(context); | |
} | |
public CViewN(Context context, AttributeSet attrs){ | |
//this(context, attrs, 0); | |
super(context, attrs); | |
init(context, attrs); | |
} | |
public CViewN(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(context, attrs); | |
} | |
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) | |
public CViewN(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){ | |
super(context, attrs, defStyleAttr, defStyleRes); | |
init(context, attrs); | |
} | |
@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 = 1000; | |
int desiredHeight = 1500; | |
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); | |
} | |
@Override | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
super.onSizeChanged(w, h, oldw, oldh); | |
width = w; | |
height = h; | |
x = width/2; | |
y = height/2; | |
} | |
protected void init(Context context, @Nullable AttributeSet attrs) { | |
vaweP = new Paint(Paint.ANTI_ALIAS_FLAG); | |
vaweP.setStyle(Paint.Style.STROKE); | |
vaweP.setColor(Color.RED); | |
vaweP.setStrokeWidth(5); | |
gapV = 30; | |
} | |
@Override | |
protected void onAttachedToWindow(){ | |
super.onAttachedToWindow(); | |
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { | |
@Override | |
public void onAnimationUpdate(ValueAnimator valueAnimator) { | |
radiusOffset = (float) valueAnimator.getAnimatedValue(); | |
alpha = (int) ((float) valueAnimator.getAnimatedValue()); | |
invalidate(); | |
} | |
}); | |
anim.setDuration(1000); | |
anim.setInterpolator(new LinearInterpolator()); | |
anim.setRepeatMode(ValueAnimator.RESTART); | |
anim.setRepeatCount(ValueAnimator.INFINITE); | |
anim.start(); | |
} | |
@Override | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
/* float currentRadius; | |
currentRadius = initialRadius; | |
while (currentRadius < (width/2)) { | |
canvas.drawCircle(x, y, currentRadius, vaweP); | |
currentRadius += gapV; | |
}*/ | |
float radius = initialRadius + radiusOffset + mPosY; | |
for(int i = 0; i < 10;i++ ){ | |
canvas.drawCircle(x, y, radius, vaweP); | |
radius = radius + 35; | |
} | |
rectCircle = new RectF(0,0, width, height); | |
cPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); | |
cPaint1.setColor(Color.GREEN); | |
cPaint1.setAlpha(30); | |
cPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG); | |
cPaint2.setColor(Color.RED); | |
cPaint2.setAlpha(30); | |
cPaint3 = new Paint(Paint.ANTI_ALIAS_FLAG); | |
cPaint3.setColor(Color.BLUE); | |
cPaint3.setAlpha(30); | |
cPaint4 = new Paint(Paint.ANTI_ALIAS_FLAG); | |
cPaint4.setColor(Color.YELLOW); | |
cPaint4.setAlpha(30); | |
//canvas.drawRoundRect(rectCircle, 10, 10, cPaint); | |
canvas.drawArc(rectCircle, alpha, 90, true, cPaint1); | |
canvas.drawArc(rectCircle, alpha + 90, 90, true, cPaint2); | |
canvas.drawArc(rectCircle, alpha + 180, 90, true, cPaint3); | |
canvas.drawArc(rectCircle, alpha + 270, 90, true, cPaint4); | |
} | |
} |
Edit the activity_main.xml like below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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" | |
tools:context=".MainActivity"> | |
<com.msh_n.myp.canvasanim1.CViewN | |
android:id="@+id/cview_1" | |
android:layout_width="200dp" | |
android:layout_height="200dp" | |
android:layout_marginStart="8dp" | |
android:layout_marginTop="8dp" | |
android:layout_marginEnd="8dp" | |
android:layout_marginBottom="8dp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
The MainActivity class which is an activity can also override the onTouchEvent() method. It makes it possible to for example drag the complete view instance inside the activity or any function related to whole view instance inside this activity. The below code shows how we can drag whole view when move our finger on touch.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MainActivity extends AppCompatActivity { | |
int mActivePointerId = MotionEvent.INVALID_POINTER_ID; | |
float mLastTouchX; | |
float mLastTouchY; | |
float mPosX = 0f; | |
float mPosY = 0f; | |
CViewN cview; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
cview = (CViewN) findViewById(R.id.cview_1); | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent ev){ | |
final int action = ev.getActionMasked(); | |
switch (action){ | |
case MotionEvent.ACTION_DOWN: | |
{ | |
final int pointerIndex = ev.getActionIndex(); | |
final float x = ev.getX(pointerIndex); | |
final float y = ev.getY(pointerIndex); | |
// Remember where we started (for dragging) | |
mLastTouchX = x; | |
mLastTouchY = y; | |
mActivePointerId = ev.getPointerId(pointerIndex); | |
break; | |
} | |
case MotionEvent.ACTION_MOVE: | |
{ | |
// Find the index of the active pointer and fetch its position | |
final int pointerIndex = ev.findPointerIndex(mActivePointerId); | |
final float x = ev.getX(pointerIndex); | |
final float y = ev.getY(pointerIndex); | |
// Calculate the distance moved | |
final float dx = x - mLastTouchX; | |
final float dy = y - mLastTouchY; | |
mPosX += dx; | |
mPosY += dy; | |
// Remember this touch position for the next move event | |
mLastTouchX = x; | |
mLastTouchY = y; | |
cview.setX((int) mPosX); | |
cview.setY((int) mPosY); | |
//invalidate(); | |
break; | |
} | |
case MotionEvent.ACTION_UP: | |
{ | |
mActivePointerId = MotionEvent.INVALID_POINTER_ID; | |
break; | |
} | |
case MotionEvent.ACTION_CANCEL: | |
{ | |
mActivePointerId = MotionEvent.INVALID_POINTER_ID; | |
break; | |
} | |
case MotionEvent.ACTION_POINTER_UP: | |
{ | |
final int pointerIndex = ev.getActionIndex(); | |
final int pointerId = ev.getPointerId(pointerIndex); | |
if(pointerId == mActivePointerId){ | |
// This was our active pointer going up. Choose a new | |
// active pointer and adjust accordingly. | |
final int newPointerIndex = pointerIndex == 0 ? 1 : 0; | |
mLastTouchX = ev.getX(newPointerIndex); | |
mLastTouchY = ev.getY(newPointerIndex); | |
mActivePointerId = ev.getPointerId(newPointerIndex); | |
} | |
break; | |
} | |
} | |
return true; | |
} | |
} |
Now run the application. The result should be something like below.
Comments
Post a Comment