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)
Related
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 am start to develop a editing app i can got take images from gallery to app
But i don't know how to pinch zoom it and pan
Please anyone tell me how to do it
It was a small part of my project.
class ZoomView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
private val imageBound = RectF()
private val imageMatrixArray = FloatArray(9)
private val scaleDetector: ScaleGestureDetector
private var scale: Float = 1f
private var scalePoint = PointF()
private var translateX: Float = 0f
private var translateY: Float = 0f
private var lastTouchX = 0f
private var lastTouchY = 0f
private var lastDownTouchX = 0f
private var lastDownTouchY = 0f
private var lastGestureX = 0f
private var lastGestureY = 0f
private var isScaling = false
private var activePointerId = -1
private var drawableHeight = -1.0
private var drawableWidth = -1.0
private var minBoxRectSide = 0
init {
scaleDetector = ScaleGestureDetector(context, object : ScaleGestureDetector
.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
isScaling = true
return super.onScaleBegin(detector)
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
scale *= detector.scaleFactor
scale = Math.min(scale, 25f)
scale = Math.max(0.5f, scale)
invalidate()
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector?) {
super.onScaleEnd(detector)
isScaling = false
}
})
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
//Timber.d("Image on layout : layoutChanged = $changed")
if (changed) {
drawable?.let {
resetBoxRect(it, left, right, top, bottom)
}
}
}
private fun resetBoxRect(it: Drawable, left: Int, right: Int, top: Int, bottom: Int) {
//Timber.d("image coordinates $left, $top, $right, $bottom")
drawableHeight = it.intrinsicHeight.toDouble()
drawableWidth = it.intrinsicWidth.toDouble()
imageMatrix.getValues(imageMatrixArray)
imageBound.set((left + right) / 2 - imageMatrixArray[Matrix.MSCALE_X] * it.intrinsicWidth / 2,
(bottom - top) / 2 - imageMatrixArray[Matrix.MSCALE_Y] * it.intrinsicHeight / 2,
(left + right) / 2 + imageMatrixArray[Matrix.MSCALE_X] * it.intrinsicWidth / 2,
(bottom - top) / 2 + imageMatrixArray[Matrix.MSCALE_Y] * it.intrinsicHeight / 2)
//Timber.d("Image bound $imageBound, and $boxRect")
minBoxRectSide = (.01f * Math.max(imageBound.bottom - imageBound.top, imageBound.right - imageBound.left)).toInt()
scale = 1f
translateX = 0f
translateY = 0f
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
scalePoint.set(w / 2.toFloat(), h / 2.toFloat())
}
override fun setImageDrawable(drawable: Drawable?) {
super.setImageDrawable(drawable)
drawable?.let {
if (imageMatrixArray != null) {
resetBoxRect(it, left, right, top, bottom)
}
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
parent.requestDisallowInterceptTouchEvent(true)
//Timber.d("On touch start")
scaleDetector.onTouchEvent(event)
//Timber.d("On touch gesture sent")
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> onActionDown(event)
MotionEvent.ACTION_MOVE -> onMoveEvent(event)
MotionEvent.ACTION_CANCEL -> activePointerId = -1
MotionEvent.ACTION_UP -> {
activePointerId = -1
}
MotionEvent.ACTION_POINTER_UP -> onActionUp(event)
}
return true
}
private fun onActionDown(event: MotionEvent) {
val actionIndex = event.actionIndex
lastDownTouchX = event.getX(actionIndex)
lastDownTouchY = event.getY(actionIndex)
lastTouchX = event.getX(actionIndex)
lastTouchY = event.getY(actionIndex)
lastGestureX = lastTouchX
lastGestureY = lastTouchY
activePointerId = event.getPointerId(0)
invalidate()
}
private fun onMoveEvent(event: MotionEvent) {
if (!isScaling) {
val index = event.findPointerIndex(activePointerId)
val dx = (event.getX(index) - lastTouchX) / scale
val dy = (event.getY(index) - lastTouchY) / scale
lastTouchX = event.getX(index)
lastTouchY = event.getY(index)
if (Math.abs(translateX + dx) < imageBound.right - imageBound.left)
translateX += dx
if (Math.abs(translateY + dy) < imageBound.bottom - imageBound.top)
translateY += dy
invalidate()
}
}
private fun onActionUp(event: MotionEvent) {
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
if (pointerId == activePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
val newPointerIndex = if (pointerIndex == 0) 1 else 0
lastTouchX = event.getX(newPointerIndex)
lastTouchY = event.getY(newPointerIndex)
activePointerId = event.getPointerId(newPointerIndex)
}
}
override fun onDraw(canvas: Canvas) {
canvas.save()
canvas.scale(scale, scale, scalePoint.x, scalePoint.y)
canvas.translate(translateX, translateY)
super.onDraw(canvas)
canvas.restore()
}
}
The view can be used the xml in this way :-
<com.myproject.app.widgets.ZoomView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/abc" />
It will be too broad to write here all the things. Basically there are lot of libraries which provide ImageView with zoom gesture.
You can see their code or use that library as well. Here are two with highest rating on github.
https://github.com/chrisbanes/PhotoView
https://github.com/davemorrissey/subsampling-scale-image-view
You just need to use their ImageView and your ImageView will have the gesture. Like
<com.github.chrisbanes.photoview.PhotoView
android:id="#+id/photo_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
What I'm trying to do.
Create a simple carousel with RecyclerView.
Problem
Initially the view is not snap to center and the view is not getting the style I intended to.(i.e, the item which is fully visible should be bigger than other, when scroll by finger it works fine)
When scroll programmatically the view is not getting snap effect like it does when scroll with finger.
See the attached gif below for example.
Question
How to have the style as intended (i.e the fully visible item is bigger) when started.
How to get the style when scroll to button is click. (It scrolls to correct position the only problem is not getting the style as intended and its not snap to center)
Full code here on github
Here's the code for custom LayoutManager
open class CarouselLayoutManager(
context: Context,
orientation: Int,
reverseLayout: Boolean
) : LinearLayoutManager(context, orientation, reverseLayout) {
private val mShrinkAmount = 0.15f
private val mShrinkDistance = 0.9f
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
scrollVerticallyBy(0, recycler, state)
super.onLayoutChildren(recycler, state)
}
override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State?): Int {
val orientation = orientation
if (orientation == LinearLayoutManager.HORIZONTAL) {
val scrolled = super.scrollHorizontallyBy(dx, recycler, state)
val midpoint = width / 2f
val d0 = 0f
val d1 = mShrinkDistance * midpoint
val s0 = 1f
val s1 = 1f - mShrinkAmount
for (i in 0 until childCount) {
val child = getChildAt(i)
val childMidpoint = (getDecoratedRight(child) + getDecoratedLeft(child)) / 2f
val d = Math.min(d1, Math.abs(midpoint - childMidpoint))
val scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0)
child.scaleX = scale
child.scaleY = scale
}
return scrolled
} else {
return 0
}
}
override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State?): Int {
val orientation = orientation
if (orientation == LinearLayoutManager.VERTICAL) {
val scrolled = super.scrollVerticallyBy(dy, recycler, state)
val midpoint = height / 2f
val d0 = 0f
val d1 = mShrinkDistance * midpoint
val s0 = 1f
val s1 = 1f - mShrinkAmount
for (i in 0 until childCount) {
val child = getChildAt(i)
val childMidpoint = (getDecoratedBottom(child) + getDecoratedTop(child)) / 2f
val d = Math.min(d1, Math.abs(midpoint - childMidpoint))
val scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0)
child.scaleX = scale
child.scaleY = scale
}
return scrolled
} else {
return 0
}
}
}
Finally I have solved the problem by using this libraries/examples
DiscreteScrollView
android-viewpager-transformers
Here is the final result.
For full code see Carousel Demo
call scrollVerticallyBy(0, recycler, state) in onLayoutCompleted() method
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"
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))