I'm trying to scrub a seekbar of a 3rd party app with Accessibility Services. This is what I'm using to scrub.
val arguments = Bundle()
arguments.putFloat(AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE, 50.0.toFloat())
seekBarNode?.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS.id, arguments)
Since I'm scrubbing a video, the SeekBar position changes but the content does not change.
Does anyone know what the issue is here? Or are there any alternatives to scrub a 3rd party SeekBar with accessibility services?
Also, I've read about GestureDescription to perform swipes. But I don't know how to use that to perform SeekBar scrub.
Try and click the center of the seek bar with dispatchGesture:
fun AccessibilityService.tapCenterOfNode(node: AccessibilityNodeInfo, onDone: (Boolean) -> Any){
this.dispatchPath(
drawPath = pathOnPoint(node.centerInScreen()),
pathDuration = 10,
onDone = {
success -> Log.d("dispatch", "success? $success")
}
)
}
fun AccessibilityService.dispatchPath(drawPath: Path, pathDuration: Long, onDone: (Boolean) -> Any) {
val stroke = GestureDescription.StrokeDescription(drawPath, 0, pathDuration)
val gesture = GestureDescription.Builder().addStroke(stroke).build()
this.dispatchGesture(gesture, object : AccessibilityService.GestureResultCallback() {
override fun onCompleted(gestureDescription: GestureDescription) {
super.onCompleted(gestureDescription)
onDone(true)
}
override fun onCancelled(gestureDescription: GestureDescription) {
super.onCancelled(gestureDescription)
onDone(false)
}
}, null)
}
fun AccessibilityNodeInfo.centerInScreen(): Pair<Float, Float> =
Pair(this.getBoundsInScreen().exactCenterX(), this.getBoundsInScreen().exactCenterY())
fun AccessibilityNodeInfo.getBoundsInScreen(): Rect {
val rect = Rect()
getBoundsInScreen(rect)
return rect
}
fun pathOnPoint(point: Pair<Float, Float>) = Path().apply {
val (x, y) = point
moveTo(x, y)
lineTo(x, y)
}
Related
I'm building my first game in Android Studio. Right now, dots fall from the top of the screen down to the bottom. For some reason, in Layout Inspector the view of each dot is the entire screen even though the dots are comparatively small. This negatively affects the game since when a user presses anywhere on the screen, it deletes the most recently created dot rather than the one pressed. I want to get the dot's view to match the size of the actual dots without effecting other functionality.
Dot.kt
class Dot(context: Context, attrs: AttributeSet?, private var dotColor: Int, private var xPos: Int, private var yPos: Int) : View(context, attrs) {
private var isMatching: Boolean = false
private var dotIsPressed: Boolean = false
private var isDestroyed: Boolean = false
private lateinit var mHandler: Handler
private lateinit var runnable: Runnable
init {
this.isPressed = false
this.isDestroyed = false
mHandler = Handler()
runnable = object : Runnable {
override fun run() {
moveDown()
invalidate()
mHandler.postDelayed(this, 20)
}
}
val random = Random()
xPos = random.nextInt(context.resources.displayMetrics.widthPixels)
startFalling()
startDrawing()
}
// other methods
fun getDotColor() = dotColor
fun getXPos() = xPos
fun getYPos() = yPos
fun isMatching() = isMatching
fun setMatching(matching: Boolean) {
this.isMatching = matching
}
fun dotIsPressed() = dotIsPressed
override fun setPressed(pressed: Boolean) {
this.dotIsPressed = pressed
}
fun isDestroyed() = isDestroyed
fun setDestroyed(destroyed: Boolean) {
this.isDestroyed = destroyed
}
fun moveDown() {
// code to move the dot down the screen
yPos += 10
}
fun checkCollision(line: Line) {
// check if dot is colliding with line
// if yes, check if dot is matching or not
// update the dot state accordingly
}
fun startFalling() {
mHandler.post(runnable)
}
fun startDrawing() {
mHandler.postDelayed(object : Runnable {
override fun run() {
invalidate()
mHandler.postDelayed(this, 500)
}
}, 500)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (!isDestroyed) {
val paint = Paint().apply {
color = dotColor
}
canvas?.drawCircle(xPos.toFloat(), yPos.toFloat(), 30f, paint)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var score = 0
private lateinit var scoreCounter: TextView
private val dots = mutableListOf<Dot>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
createLine(Color.RED, 5000)
scoreCounter = TextView(this)
scoreCounter.text = score.toString()
scoreCounter.setTextColor(Color.WHITE)
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.setBackgroundColor(Color.BLACK)
val params = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
scoreCounter.layoutParams = params
layout.addView(scoreCounter)
val dotColors = intArrayOf(Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW)
val random = Random()
val handler = Handler()
val runnable = object : Runnable {
override fun run() {
val dotColor = dotColors[random.nextInt(dotColors.size)]
createAndAddDot(0, 0, dotColor)
handler.postDelayed(this, 500)
}
}
handler.post(runnable)
}
fun updateScore(increment: Int) {
score += increment
scoreCounter.text = score.toString()
}
fun createAndAddDot(x: Int, y: Int, color: Int) {
Log.d("Dot", "createAndAddDot called")
val dot = Dot(this, null, color, x, y)
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.addView(dot)
dots.add(dot)
dot.setOnTouchListener { view, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
val dotToRemove = dots.find { it == view }
dotToRemove?.let {
layout.removeView(it)
dots.remove(it)
updateScore(1)
view.performClick()
}
}
true
}
}
fun createLine(color: Int, interval: Int) {
Log.d("Line", "createLine called")
val line = Line(color, interval)
val lineView = Line.LineView(this, null, line)
val layout = findViewById<ConstraintLayout>(R.id.layout)
if (layout == null) {
throw IllegalStateException("Layout not found")
}
layout.addView(lineView)
val params = ConstraintLayout.LayoutParams(2000, 350)
lineView.layoutParams = params
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID
params.bottomMargin = (0.1 * layout.height).toInt()
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Your view here -->
<View
android:id="#+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- Guideline set to 10% from the bottom -->
<androidx.constraintlayout.widget.Guideline
android:id="#+id/bottom_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.1" />
</androidx.constraintlayout.widget.ConstraintLayout>
I tried changing the view size with
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val diameter = 40 // or any other desired diameter for the dots setMeasuredDimension(diameter, diameter) }
That made the view size a square stuck in the top left corner. As I played around with it, I could only get dots to show in that small window in the top corner rather than moving down the screen from different starting x-positions
Your custom view isn't a dot, it's a large display area that draws a dot somewhere inside it and animates its position. In onDraw you're drawing a circle at xPos (a random point on the screen width via displayMetrics.widthPixels) and yPos (an increasing value which moves the dot down the view).
There are two typical approaches to things like this:
use simple views like ImageViews. Let the containing Activity or Fragment add them to a container and control their position, maybe using the View Animation system. Handle player interaction by giving them click listeners and let the view system work out what's been clicked.
create a custom view that acts as the game area. Let that custom view control the game state (what dots exist, where they currently are) and draw that state in onDraw. Handle touch events on the view, and work out if those touches coincide with a dot (by comparing to the current game state).
What you're doing is sort of a combination of the two with none of the advantages that either approach gives on its own. You have multiple equally-sized "game field" views stacked on top of each other, so any clicks will be consumed by the top one, because you're clicking the entire view itself. And because your custom view fills the whole area, you can't move it around with basic view properties to control where the dot is - you have to write the logic to draw the view and animate its contents.
You could implement some code that handles the clicks and decides whether the view consumes it (because it intersects a dot) or passes it on to the next view in the stack, but that's a lot of work and you still have all your logic split between the Activity/Fragment and the custom view itself.
I think it would be way easier to just pick one approach - either use ImageViews sized to the dot you want and let the view system handle the interaction, or make a view that runs the game internally. Personally I'd go with the latter (you'll find it a lot easier to handle dots going out of bounds, get better performance, more control over the look and interaction etc, no need to cancel Runnables) but it's up to you!
Has anyone found an equivalent of onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) in LazyColumnFor?
You can get the position of a first item by adding an onGloballyPositioned https://developer.android.com/reference/kotlin/androidx/compose/ui/layout/OnGloballyPositionedModifier option to the first item's modifier.
It returns:
an initial position after layout creation
an updated position every time during a scroll event
The LayoutCoordinates object inside the lambda let you get:
positionInParent()
positionInRoot()
positionInWindow()
To get an x or y from the LazyColumn/LazyRow you can use this part of code:
val initialLayoutY: MutableState<Float?> = rememberSaveable { mutableStateOf(null) }
Text(
text = "Lorem",
modifier = Modifier.onGloballyPositioned { layoutCoordinates ->
if (initialLayoutY.value == null) {
initialLayoutY.value = layoutCoordinates.positionInRoot().y
}
val currentTextLayoutY: Float = layoutCoordinates.positionInRoot().y
val currentLazyListY: Float = initialLayoutY.value!! - currentTextLayoutY
Log.e("Lazy Column y", currentLazyListY.toString())
}
)
Unfortunately, it skips a lot of values during fast scrolling. Here is a result:
It is not exactly onScrolled.
You can use LazyListState#isScrollInProgress to check if the list is currently scrolling by gesture, fling or programmatically or not.
val itemsList = (0..55).toList()
val state = rememberLazyListState()
val scrollInProgress: Boolean by remember {
derivedStateOf {
state.isScrollInProgress
}
}
if (scrollInProgress){
//....do something
}
LazyColumn(state = state) {
items(itemsList) {
Text(".....")
}
}
with 1.0.0-alpha12
It should be what you want:
val scrollCallback = object : ScrollCallback {
override fun onCancel() {
super.onCancel()
}
override fun onScroll(scrollDistance: Float): Float {
return super.onScroll(scrollDistance)
}
override fun onStart(downPosition: Offset) {
super.onStart(downPosition)
}
override fun onStop(velocity: Float) {
super.onStop(velocity)
}
}
LazyColumnFor(
modifier = Modifier.scrollGestureFilter(scrollCallback, Orientation.Vertical),
items = listOf("")
) { item ->
}
I merged some CameraX tutorials so that it has pinch to zoom and tap to focus.
By themselves, they work well, but together, the OnClickListener and OnTouchListeners are interfering with each other.
I thought merging them under a single OnClickListener method where the pinch to zoom is executed on the ACTION_DOWN, and the tap to focus on the ACTION_UP, but only the tap to focus is running. Even if it did work, this feels a bit clunky and I'd appreciate some more advanced guidance.
zoomAndFocus is triggered by: "viewFinder.setOnClickListener{zoomAndFocus()}" in onCreate.
private fun zoomAndFocus(){
Log.d("onclick","detecting clck?")
viewFinder.setOnTouchListener { _, event ->
return#setOnTouchListener when (event.action) {
MotionEvent.ACTION_DOWN -> {
val listener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val zoomRatio = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0f
val scale = zoomRatio * detector.scaleFactor
camera!!.cameraControl.setZoomRatio(scale)
return true
}
}
val scaleGestureDetector = ScaleGestureDetector(this, listener)
scaleGestureDetector.onTouchEvent(event)
true
}
MotionEvent.ACTION_UP -> {
val factory: MeteringPointFactory = SurfaceOrientedMeteringPointFactory(
viewFinder.width.toFloat(), viewFinder.height.toFloat()
)
val autoFocusPoint = factory.createPoint(event.x, event.y)
try {
camera?.cameraControl?.startFocusAndMetering(
FocusMeteringAction.Builder(
autoFocusPoint,
FocusMeteringAction.FLAG_AF
).apply {
//focus only when the user tap the preview
disableAutoCancel()
}.build()
)
} catch (e: CameraInfoUnavailableException) {
Log.d("ERROR", "cannot access camera", e)
}
viewFinder.performClick()
true
}
else -> false // Unhandled event.
}
}
Use this to add pinch to zoom and tap to focus together:
private fun setupZoomAndTapToFocus() {
val listener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val currentZoomRatio: Float = cameraInfo.zoomState.value?.zoomRatio ?: 1F
val delta = detector.scaleFactor
cameraControl.setZoomRatio(currentZoomRatio * delta)
return true
}
}
val scaleGestureDetector = ScaleGestureDetector(viewFinder.context, listener)
viewFinder.setOnTouchListener { _, event ->
scaleGestureDetector.onTouchEvent(event)
if (event.action == MotionEvent.ACTION_DOWN) {
val factory = viewFinder.createMeteringPointFactory(cameraSelector)
val point = factory.createPoint(event.x, event.y)
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
.setAutoCancelDuration(5, TimeUnit.SECONDS)
.build()
cameraControl.startFocusAndMetering(action)
}
true
}
}
I am writing test cases for recyclerview. I am stuck with drag and drop test in recyclerview. I tried in some way. But not able to write drag and drop test for recyclerview.
In recyclerview I am having drag button. Using of that button I am drag and drop the item on position to other position.
My code to swipe bottom to top
onView(withId(R.id.recyclerView)).perform(
RecyclerViewActions.actionOnItemAtPosition(0, new GeneralSwipeAction(
Swipe.SLOW, GeneralLocation.BOTTOM_CENTER, GeneralLocation.TOP_CENTER,
Press.FINGER)));
This code is swipe the recyclerview.
So I am trying like this:
onView(withId(R.id.recyclerView)).perform(
RecyclerViewActions.actionOnItemAtPosition(0,
MyViewAction.clickChildViewWithId(R.id.img_drag)),new GeneralSwipeAction(
Swipe.SLOW, GeneralLocation.TOP_RIGHT, GeneralLocation.CENTER_RIGHT,
Press.FINGER));
But this is not workout to me. Please let me any idea to test the drag and drop functionality.
The code you wrote first clicks the drag handle and then swipes from top right of the first item to its center right. If you want to drag and drop an item, you will need several custom CoordinatesProviders.
RecyclerViewCoordinatesProvider will find coordinates of an item within your RecyclerView:
class RecyclerViewCoordinatesProvider(private val position: Int, private val childItemCoordinatesProvider: CoordinatesProvider) : CoordinatesProvider {
override fun calculateCoordinates(view: View): FloatArray {
return childItemCoordinatesProvider.calculateCoordinates((view as RecyclerView).layoutManager!!.findViewByPosition(position)!!)
}
}
ChildViewCoordinatesProvider will find coordinates of a child with a given id:
class ChildViewCoordinatesProvider(private val childViewId: Int, private val insideChildViewCoordinatesProvider: CoordinatesProvider) : CoordinatesProvider {
override fun calculateCoordinates(view: View): FloatArray {
return insideChildViewCoordinatesProvider.calculateCoordinates(view.findViewById<View>(childViewId))
}
}
Lastly we will create new custom general locations, which will provide coordinates above and under a View (I assume we are reordering inside a LinearLayoutManager):
enum class CustomGeneralLocation : CoordinatesProvider {
UNDER_RIGHT {
override fun calculateCoordinates(view: View): FloatArray {
val screenLocation = IntArray(2)
view.getLocationOnScreen(screenLocation)
return floatArrayOf(screenLocation[0] + view.width - 1f, screenLocation[1] + view.height * 1.5f)
}
},
ABOVE_RIGHT {
override fun calculateCoordinates(view: View): FloatArray {
val screenLocation = IntArray(2)
view.getLocationOnScreen(screenLocation)
return floatArrayOf(screenLocation[0] + view.width - 1f, screenLocation[1] - view.height * 0.5f)
}
}
}
Now if you want to drag an item from the original position to the target position where RecyclerView's ID is server_list and the drag handle's ID is drag_handle, use the following method:
private fun reorder(from: Int, to: Int) {
onView(withId(R.id.server_list)).perform(GeneralSwipeAction(
Swipe.SLOW,
RecyclerViewCoordinatesProvider(from, ChildViewCoordinatesProvider(R.id.drag_handle, GeneralLocation.CENTER)),
RecyclerViewCoordinatesProvider(to, if (from < to) CustomGeneralLocation.UNDER_RIGHT else CustomGeneralLocation.ABOVE_RIGHT),
Press.FINGER))
}
I have a working example, although I do not know if it works with scrolling, and it throws an Exception if it fails.
The idea is to create a custom ViewAction that acts on a RecyclerView. MotionEvents are created and injected into the uicontroller. GeneralLocation, despite its name, is used to get accurate coordinates for the view holders.
I tried to use MotionEvents.sendMovement previously, but it did not work, so it appears that the interpolation is necessary in order for the phone to process the event.
class MoveViewHolder(val fromPosition: Int, val toPosition: Int) : ViewAction {
override fun getConstraints(): Matcher<View?>? {
return allOf(isAssignableFrom(RecyclerView::class.java), isDisplayed())
}
override fun getDescription(): String {
return "Long click ViewHolder #$fromPosition and swipe to position $toPosition"
}
override fun perform(uiController: UiController, view: View?) {
// For long clicking the ViewHolder
val rv = view as RecyclerView
rv.scrollToPosition(fromPosition)
val viewHolder = rv.findViewHolderForAdapterPosition(fromPosition)!!
val location = GeneralLocation.CENTER.calculateCoordinates(viewHolder.itemView)
val downEvent = MotionEvents.obtainDownEvent(
location,
floatArrayOf(0f, 0f)
)
// For moving the ViewHolder
val viewHolder2 = rv.findViewHolderForAdapterPosition(toPosition)!!
val location2 = if (toPosition > fromPosition) {
GeneralLocation.BOTTOM_CENTER.calculateCoordinates(viewHolder2.itemView)
} else {
GeneralLocation.TOP_CENTER.calculateCoordinates(viewHolder2.itemView)
}
// Interpolating the move
val longPressTimeout = (ViewConfiguration.getLongPressTimeout() * 1.5f).toLong()
var eventTime = downEvent.downTime + longPressTimeout
val durationMS = 1500
val steps = interpolate(location, location2, 10)
val intervalMS: Long = (durationMS / steps.size).toLong()
val events = mutableListOf<MotionEvent>()
for (step in steps) {
eventTime += intervalMS
events.add(MotionEvents.obtainMovement(downEvent.downTime, eventTime, step))
}
eventTime += intervalMS
events.add(
MotionEvent.obtain(
downEvent.downTime,
eventTime,
MotionEvent.ACTION_UP,
location2[0],
location2[1],
0
)
)
// For releasing the ViewHolder to its new position
val upEvent = MotionEvents.obtainUpEvent(downEvent, location2)
events.add(upEvent)
try {
uiController.injectMotionEvent(downEvent)
rv.scrollToPosition(toPosition)
uiController.loopMainThreadForAtLeast(longPressTimeout)
uiController.injectMotionEventSequence(events)
} finally {
downEvent.recycle()
for (event in events) {
event.recycle()
}
}
}
}
I'm implementing a custom view which draws some kind ProgressBar view, taking two views as parameters (origin and destination). Like this:
This is the complete class:
class BarView #JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
private var valueAnimator: ObjectAnimator? = null
private lateinit var path: Path
private val pathMeasure = PathMeasure()
private var pauseProgress: Int = dip(40)
var progress = 0f
set(value) {
field = value.coerceIn(0f, pathMeasure.length)
invalidate()
}
private var originPoint: PointF? = null
private var destinationPoint: PointF? = null
private val cornerEffect = CornerPathEffect(dip(10).toFloat())
private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeWidth = dip(10f).toFloat()
color = ContextCompat.getColor(context, android.R.color.darker_gray)
pathEffect = cornerEffect
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
if (progress < pathMeasure.length) {
val intervals = floatArrayOf(progress, pathMeasure.length - progress)
val progressEffect = DashPathEffect(intervals, 0f)
linePaint.pathEffect = ComposePathEffect(progressEffect, cornerEffect)
}
canvas.drawPath(path, linePaint)
}
object PROGRESS : Property<BarView, Float>(Float::class.java, "progress") {
override fun set(view: BarView, progress: Float) {
view.progress = progress
}
override fun get(view: BarView) = view.progress
}
private fun startAnimator() {
valueAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 0f, pathMeasure.length).apply {
duration = 500L
interpolator = LinearInterpolator()
}
setPauseListener()
valueAnimator!!.start()
}
fun resume() {
valueAnimator!!.resume()
}
fun reset() {
startAnimator()
}
fun setPoints(originView: View, destinationView: View) {
originPoint = PointF(originView.x + originView.width / 2, 0f)
destinationPoint = PointF(destinationView.x + destinationView.width / 2, 0f)
setPath()
startAnimator()
}
private fun setPath() {
path = Path()
path.moveTo(originPoint!!.x, originPoint!!.y)
path.lineTo(destinationPoint!!.x, destinationPoint!!.y)
pathMeasure.setPath(path, false)
}
private fun setPauseListener() {
valueAnimator!!.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
override fun onAnimationUpdate(valueAnimator: ValueAnimator?) {
val progress = valueAnimator!!.getAnimatedValue("progress") as Float
if (progress > pauseProgress) {
valueAnimator.pause()
this#BarView.valueAnimator!!.removeUpdateListener(this)
}
}
})
}
}
What im trying to do is to pause the animation at a specific progress, 40dp in this case:
private fun setPauseListener() {
valueAnimator!!.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
override fun onAnimationUpdate(valueAnimator: ValueAnimator?) {
val progress = valueAnimator!!.getAnimatedValue("progress") as Float
if (progress > pauseProgress) {
valueAnimator.pause()
this#BarView.valueAnimator!!.removeUpdateListener(this)
}
}
})
}
But the animations have different speeds since the views have different path lengths, and all of them have to finish in 500ms. They are not pausing at the same distance from the origin:
I tried switching from a LinearInterpolator to a AccelerateInterpolator, to make the start of the animation slower, but i'm still not satisfied with the results.
The next step for me, would be to try to implement a custom TimeInterpolator to make the animation start speed the same no matter how long the path is on each view, but I cannot wrap my head arrow the maths to create the formula needed.
valueAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 0f, pathMeasure.length).apply {
duration = 500L
interpolator = TimeInterpolator { input ->
// formula here
}
}
Any help with that would be well received. Any suggestions about a different approach. What do you think?
1) As I mentioned I would use determinate ProgressBar and regulate bar width by maximum amount of progress, assigned to certain view
2) I would use ValueAnimator.ofFloat with custom interpolator and set progress inside it
3) I would extend my custom interpolator from AccelerateInterpolator to make it look smthng like this:
class CustomInterpolator(val maxPercentage: Float) : AccelerateInterpolator(){
override fun getInterpolation(input: Float): Float {
val calculatedVal = super.getInterpolation(input)
return min(calculatedVal, maxPercentage)
}
}
where maxPercentage is a fraction of view width (from 0 to 1) you bar should occupy
Hope it helps