I want my app to simulate a swipe touch event (to up/down/left/right) when I click a button, then a TextView will scroll down/up.
I have tried to use Motion Event, but nothing happen after I dispatch 3 Motion Event of ACTION_DOWN, ACTION_MOVE and ACTION_UP respectively.
Is that possible to simulate a swipe event?
public void simulation(View view){
swipe(Direction.Bot);
}
public enum Direction {
Top, Bot, Left, Right;
}
protected void swipe(Direction direction) {
Point size = new Point();
this.getWindowManager().getDefaultDisplay().getSize(size);
int height = size.y; // height will be at top of the screen
int width = size.x; // width will be rightmost location of the screen
float xStart = size.x-50;
float xEnd = size.x-50;
float yStart = size.y-50;
float yEnd = size.y-50;
long downTime = SystemClock.uptimeMillis();
if(direction == Direction.Top || direction == Direction.Bot){
yStart = ((direction == Direction.Top) ? 50 : (height - 50));
yEnd = ((direction == Direction.Top) ? (height - 50) : 50);
}else {
xStart = ((direction == Direction.Left) ? (width - 50) : 50); // true: xStart = w-10; else: = 10
xEnd = ((direction == Direction.Left) ? 50 : (width - 50)); // true: xEnd = 10; else: = w-10
}
findViewById(R.id.my_id).dispatchTouchEvent(MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),MotionEvent.ACTION_DOWN, xStart/2, yStart/2, 0));
System.out.println("ACTION_DOWN");
findViewById(R.id.my_id).dispatchTouchEvent(MotionEvent.obtain(downTime, SystemClock.uptimeMillis() + 500, MotionEvent.ACTION_MOVE, xEnd / 2, yEnd / 2, 0));
System.out.println("ACTION_MOVE");
findViewById(R.id.my_id).dispatchTouchEvent(MotionEvent.obtain(downTime, SystemClock.uptimeMillis() + 1000, MotionEvent.ACTION_UP, xEnd / 2, yEnd / 2, 0));
System.out.println("ACTION_UP");
}
I was able to programmatically emulate fling event in scrolling activity demo.
This is an example of an emulating fling event I was trying and it worked.
Blue dotted line is the fling event I have emulated:
class ScrollingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scrolling)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
Thread(Runnable {
try {
fling(500f ,900f ,530f ,20f, 5);
// emulateMptionEvent()
} catch (e: Exception) {
}
}).start()
}
}
/** * Simulate touching a specific location and dragging to a new location.
*
* #param fromX X coordinate of the initial touch, in screen coordinates
* #param toX Xcoordinate of the drag destination, in screen coordinates
* #param fromY X coordinate of the initial touch, in screen coordinates
* #param toY Y coordinate of the drag destination, in screen coordinates
* #param stepCount How many move steps to include in the drag
*/
fun fling(
fromX: Float, toX: Float, fromY: Float,
toY: Float, stepCount: Int
) {
val inst = Instrumentation()
val downTime = SystemClock.uptimeMillis()
var eventTime = SystemClock.uptimeMillis()
var y = fromY
var x = fromX
val yStep = (toY - fromY) / stepCount
val xStep = (toX - fromX) / stepCount
var event = MotionEvent.obtain(
downTime, eventTime,
MotionEvent.ACTION_DOWN, fromX, fromY, 0
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
event.source = InputDevice.SOURCE_TOUCHSCREEN
}
inst.sendPointerSync(event)
for (i in 0 until stepCount) {
y += yStep
x += xStep
eventTime = SystemClock.uptimeMillis()
event = MotionEvent.obtain(
downTime, eventTime + stepCount,
MotionEvent.ACTION_MOVE, x, y, 0
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
event.source = InputDevice.SOURCE_TOUCHSCREEN
}
inst.sendPointerSync(event)
}
eventTime = SystemClock.uptimeMillis() + stepCount.toLong() + 2
event = MotionEvent.obtain(
downTime, eventTime,
MotionEvent.ACTION_UP, toX, toY, 0
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
event.source = InputDevice.SOURCE_TOUCHSCREEN
}
inst.sendPointerSync(event)
}
}
Hope it help somebody
I have wrote extension functions that doesn't require Instrumentation, so they could be used not only in androidTest but also in robolectric tests and even in release build:
fun ViewGroup.performSwipeToLeft(target: View) {
this.performSwipe(target, distanceX = -this.width * .5f, distanceY = 0f)
}
fun ViewGroup.performSwipeToRight(target: View) {
this.performSwipe(target, distanceX = +this.width * .5f, distanceY = 0f)
}
fun ViewGroup.performSwipeToTop(target: View) {
this.performSwipe(target, distanceX = 0f, distanceY = -this.height * .5f)
}
fun ViewGroup.performSwipeToBottom(target: View) {
this.performSwipe(target, distanceX = 0f, distanceY = +this.width * .5f)
}
fun ViewGroup.performSwipe(target: View, distanceX: Float, distanceY: Float) {
val parentCoords = intArrayOf(0, 0)
this.getLocationInWindow(parentCoords)
val childCoords = intArrayOf(0, 0)
target.getLocationInWindow(childCoords)
val initGlobalX = childCoords[0].toFloat() + 1f
val initGlobalY = childCoords[1].toFloat() + 1f
val initLocalX = (childCoords[0] - parentCoords[0]).toFloat() + 1f
val initLocalY = (childCoords[1] - parentCoords[1]).toFloat() + 1f
val downTime = SystemClock.uptimeMillis()
var eventTime = SystemClock.uptimeMillis()
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
eventTime,
MotionEvent.ACTION_DOWN,
initGlobalX,
initGlobalY,
0
).apply {
setLocation(initLocalX, initLocalY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
val steps = 20
var i = 0
while (i in 0..steps) {
val globalX = initGlobalX + i * distanceX / steps
val globalY = initGlobalY + i * distanceY / steps
val localX = initLocalX + i * distanceX / steps
val localY = initLocalY + i * distanceY / steps
if (globalX <= 10f || globalY <= 10f) {
break
}
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
++eventTime,
MotionEvent.ACTION_MOVE,
globalX,
globalY,
0
).apply {
setLocation(localX, localY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
i++
}
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
++eventTime,
MotionEvent.ACTION_UP,
initGlobalX + i * distanceX,
initGlobalY + i * distanceY,
0
).apply {
setLocation(initLocalX + i * distanceX, initLocalY + i * distanceY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
}
To use it you need pass your textView as an argument, and parent of textView should be receiver of this functions:
val textView = ...
(textView.parent as ViewGroup).performSwipeToTop(textView)
With RecyclerView you also wanna take a look at the smoothScollBy function where you can pass an Interpolator like the following.
recyclerView.smoothScrollBy(0, 500, AccelerateDecelerateInterpolator())
Comes close enough to a "swipe scoll"
Related
How can get x y position of bitmap inside imageview?
image view type is matrix.
after rotate imageview how can i get correct x, y coordinate from imageview
if (selectView.mode == selectView.DRAG) {
selectView.matrix1.set(selectView.savedMatrix)
val dx = event.x - selectView.start.x
val dy = event.y - selectView.start.y
selectView.matrix1.postTranslate(dx, dy)
} else if (selectView.mode == selectView.ZOOM) {
val newDist = spacing(event)
if (newDist > 10f) {
selectView.matrix1.set(selectView.savedMatrix)
val scale = newDist / selectView.oldDist
selectView.matrix1.postScale(
scale, scale, selectView.mid.x, selectView.mid.y
)
}
if (selectView.lastEvent != null && event.pointerCount >= 2) {
selectView.newRot = rotation(event)
val r = selectView.newRot - selectView.d
val values = FloatArray(9)
selectView.matrix1.getValues(values)
val tx = values[2]
val ty = values[5]
val sx = values[0]
val xc: Float = (selectView.width.div(2)).times(sx)
val yc: Float = (selectView.height.div(2)).times(sx)
selectView.matrix1.postRotate(r, tx + xc, ty + yc)
}
}
}
i try this but not work
private fun getXValueFromMatrix(matrix: Matrix): Float {
val values = FloatArray(9)
matrix.getValues(values)
return values[2]
}
private fun getYValueFromMatrix(matrix: Matrix): Float {
val values = FloatArray(9)
matrix.getValues(values)
return values[5]
}
i try this but not work
val bounds = RectF()
val drawable: Drawable = selectedView!!.drawable
selectedView!!.imageMatrix.mapRect(bounds, RectF(drawable.bounds))
binding.border.x = bounds.left
binding.border.y = bounds.top
when i rotate image bitmap using matrix i got wrong xy coordinates.
when bitmap angle is 90, 180, 270, 360 i got right xy coordinates.
i have a problem with rotating when i touch the rotate button and swipe right and left like normal rotate it will work fine but if you move your finger inside the layout container it will keep rotating as a fan i have been trying to fix this problem since 5 days but i got nothing if you have any solution would be great
here is the code
btnrot.setOnTouchListener(OnTouchListener { v, event ->
if (!freeze) {
layoutParams = layGroup.layoutParams as LayoutParams
layBg = v.parent as RelativeLayout
val arrayOfInt = IntArray(2)
layBg!!.getLocationInWindow(arrayOfInt)
var mX = event.rawX.toInt() - arrayOfInt[0]
var mY = event.rawY.toInt() - arrayOfInt[1]
when (event.action) {
MotionEvent.ACTION_DOWN -> {
layGroup.invalidate()
startDegree = layGroup.rotation
pivx = layoutParams.leftMargin + v.width / 2
pivy = layoutParams.topMargin + v.height / 2
basex = mX - pivx
basey = pivy - mY
}
MotionEvent.ACTION_MOVE -> {
val k = pivx
val m = pivy
mY = (Math.toDegrees(
atan2(
basey.toDouble(),
basex.toDouble()
)
) - Math.toDegrees(atan2(m - mY.toDouble(), mX - k.toDouble()))).toInt()
mX = mY
if (mY < 0) {
mX = mY + 360
}
val km = event.y.toInt()
// if (km in 0..1000) {
layGroup.rotation = (startDegree + mX) % 360f
// }
}
}
return#OnTouchListener true
}
freeze
})
I'm trying to implement a pie chart as shown in pictures below, where corners of the arc should be rounded.
I've tried to use CornerPathEffect(), but it seems to work only on the intersection of two lines (path.lineTo()). Nothing changes if I use this method for arc (path.arcTo()).
Try to set Stroke Cap of paint.
mPaint.setStrokeCap(Paint.Cap.ROUND);
I know it's too late for this answer but here is my solution.
PieSlice.kt
data class PieSlice(
val name: String,
var value: Double,
var startAngle: Float,
var sweepAngle: Float,
var indicatorCircleLocation: PointF,
val paint: Paint
)
Function to make round corner arc
private fun drawCurvedArc(canvas: Canvas?, pieItem: PieSlice) {
val path = Path()
path.moveTo(originX, originY)
val angleStart = pieItem.startAngle
val angleEnd = (pieItem.startAngle - pieItem.sweepAngle)
val arcOffset = pieItem.sweepAngle.coerceAtMost(7f)
val lineOffset = if (pieItem.sweepAngle < 7f) 0f else 25f
// line from origin to top
val line1x = getPointX(angleStart, lineOffset)
val line1y = getPointY(angleStart, lineOffset)
path.lineTo(line1x, line1y)
//Curve corner from line top to arc start
val arcStartx = getPointX(angleStart - arcOffset)
val arcStarty = getPointY(angleStart - arcOffset)
joinLineAndArc(path, line1x, line1y, arcStartx, arcStarty)
//Arc
path.arcTo(
outerRect, (pieItem.startAngle - arcOffset),
(-pieItem.sweepAngle + 2 * arcOffset), true
)
val line2x = getPointX(angleEnd, lineOffset)
val line2y = getPointY(angleEnd, lineOffset)
val arcEndx = getPointX(angleEnd + arcOffset)
val arcEndy = getPointY(angleEnd + arcOffset)
//Curve corner from arc end to bottom line
joinLineAndArc(path, arcEndx, arcEndy, line2x, line2y)
// Bottom line
path.lineTo(originX, originY)
val borderPaint = Paint()
borderPaint.strokeJoin = Paint.Join.ROUND
borderPaint.strokeCap = Paint.Cap.ROUND
borderPaint.color = pieItem.paint.color
borderPaint.style = Paint.Style.FILL
borderPaint.strokeWidth = 0f
canvas?.drawPath(path, borderPaint)
}
/**
* Join line and arc with a curve
*
* vector = (x1-x2,y1-y2)
*
* pVector perpendicular to vector
* pVector = (y1-y2,x2-x1)
*
* midX = (x1+x2)/2
* midY = (y1+y2)/2
*
* (px,py) = (midX,midY) ± (D/√((y1−y2)^2,(x2−x1)^2))*(y1-y2,x2-x1)
*/
private fun joinLineAndArc(
path: Path,
x1: Float,
y1: Float,
x2: Float,
y2: Float
) {
val midX: Float = (x2 + x1) / 2f
val midY: Float = (y2 + y1) / 2f
val x2_x1 = (x2 - x1).toDouble()
val y1_y2 = (y1 - y2).toDouble()
val powY = y1_y2.pow(2.0)
val powX = x2_x1.pow(2.0)
val den = sqrt(powY + powX)
val len = 20.0
// perpendicular1
val p1x = (midX + ((len * y1_y2) / den)).toFloat()
val p1y = (midY + ((len * x2_x1) / den)).toFloat()
// perpendicular2
val p2x = (midX - ((len * y1_y2) / den)).toFloat()
val p2y = (midY - ((len * x2_x1) / den)).toFloat()
val len1 = Math.sqrt(
Math.pow((originX - p1x).toDouble(), 2.0)
+ Math.pow((originY - p1y).toDouble(), 2.0)
)
val len2 = Math.sqrt(
Math.pow((originX - p2x).toDouble(), 2.0)
+ Math.pow((originY - p2y).toDouble(), 2.0)
)
//Make a curve to the point which is far from origin
if (len1 > len2) {
path.cubicTo(x1, y1, p1x, p1y, x2, y2)
} else {
path.cubicTo(x1, y1, p2x, p2y, x2, y2)
}
}
/**
* Get the x coordinate on a circle
* formula for x pos: (radius) * cos(angle) + (distance from left edge of screen)
* #param angle angle of point from origin
* #param offset to make shorter line than actual radius
*/
private fun getPointX(angle: Float, offset: Float = 0f): Float {
return ((radius - offset)
* cos(Math.toRadians((angle.toDouble())))
+ originX).toFloat()
}
/**
* Get the y coordinate on a circle
* formula for y pos: (radius) * sin(angle) + (distance from top edge of screen)
*
* #param angle angle of point from origin
* #param offset to make shorter line than actual radius
*/
private fun getPointY(angle: Float, offset: Float = 0f): Float {
return ((radius - offset)
* sin(Math.toRadians((angle.toDouble())))
+ originY).toFloat()
}
I have a RecyclerView with items inside like this:
I use ItemTouchHelper.SimpleCallback to listen for swipe and onChildDraw() to draw a canvas when items are swiped:
A bit more swipe:
My problem:
I want to simulate a swipe (at run time) just on first item inside the list of items; I need first item to go (more or less ) -100 px on its X axis and then go back to original position. How to achieve this?
I created some utility method in case someone needs it
import android.animation.ValueAnimator
import android.os.SystemClock
import android.view.MotionEvent
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
/**
* #author Oleh Haidaienko
* #since 29.07.2020
*/
object SwipeUtils {
/**
* Programmatically swipe RecyclerView item
* #param recyclerView RecyclerView which item will be swiped
* #param index Position of item
* #param distance Swipe distance
* #param direction Swipe direction, can be [ItemTouchHelper.START] or [ItemTouchHelper.END]
* #param time Animation time in milliseconds
*/
fun swipeRecyclerViewItem(
recyclerView: RecyclerView,
index: Int,
distance: Int,
direction: Int,
time: Long
) {
val childView = recyclerView.getChildAt(index) ?: return
val x = childView.width / 2F
val viewLocation = IntArray(2)
childView.getLocationInWindow(viewLocation)
val y = (viewLocation[1] + childView.height) / 2F
val downTime = SystemClock.uptimeMillis()
recyclerView.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
downTime,
MotionEvent.ACTION_DOWN,
x,
y,
0
)
)
ValueAnimator.ofInt(0, distance).apply {
duration = time
addUpdateListener {
val dX = it.animatedValue as Int
val mX = when (direction) {
ItemTouchHelper.END -> x + dX
ItemTouchHelper.START -> x - dX
else -> 0F
}
recyclerView.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE,
mX,
y,
0
)
)
}
}.start()
}
}
i guess you could achieve that with RecyclerView by simply registering DataObserver for your Adapter which will notify you when an item has been removed, inserted or changed. lets consider this scenario
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override public void onChanged() {
super.onChanged();
adapter.unregisterAdapterDataObserver(this);//unregister the adapter since no longer needed.
}
});
in above code will notify you when an item has been inserted you could then use recyclerView.getChildAt(0).translateX(-100); to simulate the swipe and simply recyclerView.getChildAt(0).translateX(0); to reposition the X of the view back within the method onChanged().
This extension functions could help you:
fun ViewGroup.performSwipeToLeft(target: View, distance: Float) {
this.performSwipe(target, distanceX = -distance, distanceY = 0f)
}
fun ViewGroup.performSwipeToRight(target: View, distance: Float) {
this.performSwipe(target, distanceX = +distance, distanceY = 0f)
}
fun ViewGroup.performSwipe(target: View, distanceX: Float, distanceY: Float) {
val parentCoords = intArrayOf(0, 0)
this.getLocationInWindow(parentCoords)
val childCoords = intArrayOf(0, 0)
target.getLocationInWindow(childCoords)
val initGlobalX = childCoords[0].toFloat() + 1f
val initGlobalY = childCoords[1].toFloat() + 1f
val initLocalX = (childCoords[0] - parentCoords[0]).toFloat() + 1f
val initLocalY = (childCoords[1] - parentCoords[1]).toFloat() + 1f
val downTime = SystemClock.uptimeMillis()
var eventTime = SystemClock.uptimeMillis()
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
eventTime,
MotionEvent.ACTION_DOWN,
initGlobalX,
initGlobalY,
0
).apply {
setLocation(initLocalX, initLocalY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
val steps = 20
var i = 0
while (i in 0..steps) {
val globalX = initGlobalX + i * distanceX / steps
val globalY = initGlobalY + i * distanceY / steps
val localX = initLocalX + i * distanceX / steps
val localY = initLocalY + i * distanceY / steps
if (globalX <= 10f || globalY <= 10f) {
break
}
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
++eventTime,
MotionEvent.ACTION_MOVE,
globalX,
globalY,
0
).apply {
setLocation(localX, localY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
i++
}
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
++eventTime,
MotionEvent.ACTION_UP,
initGlobalX + i * distanceX,
initGlobalY + i * distanceY,
0
).apply {
setLocation(initLocalX + i * distanceX, initLocalY + i * distanceY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
}
In your case you could use this as follows:
recylerView.performSwipeToLeft(recyclerView.getChildAt(0), 100f)
I've been trying to find an answer for this specific case on Google and amazingly couldn't find it anywhere (there's just answers about detecting, not creating, swipes). I want to actually 'run' a simulated swipe from right to left on the screen (doesn't matter where, but ideally from the right edge to the left). I tried something like this, but I get a NPE:
final float viewWidth = view.getWidth();
TouchUtils.drag(null,viewWidth,1f,0f,0f,1);
The reason? I need to force a swipe to the next page in an infinite viewpager which has no real reference to the page position (so I can't just use setCurrentItem).
Any ideas?
As already said TouchUtils.drag uses Instrumentation that could be used in androidTest only.
I have wrote extension functions that doesn't require Instrumentation, so they could be used in release build:
fun ViewGroup.performSwipeToLeft(target: View) {
this.performSwipe(target, distanceX = -this.width * .5f, distanceY = 0f)
}
fun ViewGroup.performSwipeToRight(target: View) {
this.performSwipe(target, distanceX = +this.width * .5f, distanceY = 0f)
}
fun ViewGroup.performSwipeToTop(target: View) {
this.performSwipe(target, distanceX = 0f, distanceY = -this.height * .5f)
}
fun ViewGroup.performSwipeToBottom(target: View) {
this.performSwipe(target, distanceX = 0f, distanceY = +this.width * .5f)
}
fun ViewGroup.performSwipe(target: View, distanceX: Float, distanceY: Float) {
val parentCoords = intArrayOf(0, 0)
this.getLocationInWindow(parentCoords)
val childCoords = intArrayOf(0, 0)
target.getLocationInWindow(childCoords)
val initGlobalX = childCoords[0].toFloat() + 1f
val initGlobalY = childCoords[1].toFloat() + 1f
val initLocalX = (childCoords[0] - parentCoords[0]).toFloat() + 1f
val initLocalY = (childCoords[1] - parentCoords[1]).toFloat() + 1f
val downTime = SystemClock.uptimeMillis()
var eventTime = SystemClock.uptimeMillis()
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
eventTime,
MotionEvent.ACTION_DOWN,
initGlobalX,
initGlobalY,
0
).apply {
setLocation(initLocalX, initLocalY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
val steps = 20
var i = 0
while (i in 0..steps) {
val globalX = initGlobalX + i * distanceX / steps
val globalY = initGlobalY + i * distanceY / steps
val localX = initLocalX + i * distanceX / steps
val localY = initLocalY + i * distanceY / steps
if (globalX <= 10f || globalY <= 10f) {
break
}
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
++eventTime,
MotionEvent.ACTION_MOVE,
globalX,
globalY,
0
).apply {
setLocation(localX, localY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
i++
}
this.dispatchTouchEvent(
MotionEvent.obtain(
downTime,
++eventTime,
MotionEvent.ACTION_UP,
initGlobalX + i * distanceX,
initGlobalY + i * distanceY,
0
).apply {
setLocation(initLocalX + i * distanceX, initLocalY + i * distanceY)
source = InputDevice.SOURCE_TOUCHSCREEN
}
)
}
To use it you need pass your current page content as an argument, and viewPager as a receiver of this functions:
val viewPager = ...
viewPager.performSwipeToLeft(viewPager.getChildAt(viewPager.currentItem))