So I am new to Kotlin and I am trying to make a super simple app. All it does is when I click Right button it goes right same with left button. The problem is that when I click either button (e.g right button) I can click it until the image goes completely offscreen. So how can I implement a code that once it hits the edge off the screen it stops moving ?
My code
package com.example.change_position_circle
import android.animation.ObjectAnimator
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//val picture = findViewById<ImageView>(R.id.SpongeBob)
val right_button = findViewById<Button>(R.id.right)
val left_button = findViewById<Button>(R.id.left)
right_button.setOnClickListener()
{
//ObjectAnimator.ofFloat(SpongeBob, "x", 100)
SpongeBob.animate().setDuration(90).translationXBy(100f)
}
left_button.setOnClickListener()
{
//ObjectAnimator.ofFloat(SpongeBob, "translationXBy", 100f).apply {
//duration = 200
// start()
SpongeBob.animate().setDuration(90).translationXBy(-100f)
//}
}
}
}
Thank you for your help
Welcome to Kotlin!
So yeah, you've got your Spongebob animating, now you need some logic to control that animation. The problem here is you don't always want that full animation to happen, right? If he's too close to the edge of the screen, you want the button to only move him as far as that invisible wall (and if he's right against it, that means no movement at all).
The animation and drawing systems don't put any restrictions on where you can put a View, so it's up to you to handle that yourself. You basically need to do this when a button is clicked:
get the Spongebob's position coordinates (it's the X you really care about right now)
work out the position of the edge you care about (the View's coordinates describe where the top left corner is, so if you're looking at the right edge, you need that X coordinate + the width of the View)
work out the X coordinate of the edge of the screen (or parent layout, whatever you want the Spongebob to be contained within)
if the distance between the Spongebob edge and the screen edge is less than your normal animation movement, you need to change it to that remaining distance
you'll also want to work out the appropriate duration too, if he's moving half the usual distance the animation should take half as long
that's a lot to work on, and there are a few ways to do it, but here's one approach just using the screen edges as the bounds
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import kotlin.math.abs
import kotlin.math.min
private const val MOVE_DISTANCE = 100
private const val MOVE_TIME = 90
class MainActivity : AppCompatActivity() {
private var screenWidth = 0
private lateinit var spongeBob : View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.spongebob)
// store this when the Activity is created, if the device is rotated the Activity
// will be recreated and this method gets run again
screenWidth = applicationContext.resources.displayMetrics.widthPixels
//val picture = findViewById<ImageView>(R.id.SpongeBob)
val right_button = findViewById<Button>(R.id.right)
val left_button = findViewById<Button>(R.id.left)
spongeBob = findViewById(R.id.spongeBob)
right_button.setOnClickListener()
{
// two possible values - the distance to the edge, and the normal amount we move
// we want the smaller of the two (i.e. always move the normal amount, unless
// the edge is closer than that
val distance = min(distanceToEdge(left = false), MOVE_DISTANCE)
moveSpongeBob(distance)
}
left_button.setOnClickListener()
{
val distance = min(distanceToEdge(left = true), MOVE_DISTANCE)
// we're moving left so we need to use a negative distance
moveSpongeBob (-distance)
}
}
private fun distanceToEdge(left: Boolean): Int {
// Get the Spongebob's top-left position - the call is a method on the View class,
// I'm assuming SpongeBob is a View, and you need to pass an array in because
// that's just how it works for whatever reason...
val location = IntArray(2)
spongeBob.getLocationOnScreen(location)
val x = location[0]
// I'm just using the View.getWidth() call here (Kotlin style) but I don't know
// what the SpongeBob class is, so you'll need to handle this
// You could set this once, like when we get the screen width, but width will be 0 until
// the View is laid out - so you can't do it in #onCreate, #onViewCreated should work
val spongeBobWidth = spongeBob.width
// the left edge is just the x position, however far that is from zero
return if (left) x
// the right edge is the x position plus the width of the bob
else screenWidth - (x + spongeBobWidth)
}
// Actually move the view, by the given distance (negative values to move left)
private fun moveSpongeBob(distance: Int) {
// work out how much this distance relates to our standard move amount, so we can
// adjust the time by the same proportion - converting to float so we don't get
// integer division (where it's rounded to a whole number)
val fraction = distance.toFloat() / MOVE_DISTANCE
// distance can be negative (i.e. moving left) so we need to use the abs function
// to make the duration a postitive number
val duration = abs(MOVE_TIME * fraction).toLong()
spongeBob.animate().setDuration(duration).translationXBy(distance.toFloat())
}
}
There's nicer stuff you can do (and SpongeBob should be called spongeBob and be a View) but that's the basics. This article on the coordinate system might help you out too.
Related
I have a SwipeToDismiss instance to delete items with dismissThresholds 75%.
If user swipes row too fast without reaching 75% threshold the row being deleted. How to prevent that?
Here is code where I execute an action:
val dismissState = rememberDismissState(
confirmStateChange = {
if (it == DismissValue.DismissedToStart) {
viewModel.deleteCity(city)
}
true
}
)
I ran into the same issue and found a fix that works for me. My theory is, that when the swipe is very fast but doesn't go very far (doesn't reach the set fractional threshold), the layout just immediately resets. In this case (DismissToStart), meaning the view snaps back to the right screen edge, giving us the value of 1.0f for the threshold and thus triggering the confirmStateChange because the fraction is per definition higher than our threshold. The problem being that our threshold is measured from the right screen edge, and this fracion (in my theory) is measured from the left screen edge.
So my solution is to track the current fractional value, and inside confirmStateChange check if the current value is higher than the threshold, BUT NOT 1.0f. In a real world scenario, I think it's impossible to reach 1.0 with actual swiping the finger from right to left, so the solution seems safe to me.
val dismissThreshold = 0.25f
val currentFraction = remember { mutableStateOf(0f) }
val dismissState = rememberDismissState(
confirmStateChange = {
if (it == DismissValue.DismissedToStart) {
if (currentFraction.value >= dismissThreshold && currentFraction.value < 1.0f) {
onSwiped(item)
}
}
dismissOnSwipe
}
)
SwipeToDismiss(
state = dismissState,
modifier = Modifier.animateItemPlacement(),
directions = setOf(DismissDirection.EndToStart),
dismissThresholds = { direction ->
FractionalThreshold(dismissThreshold)
},
background = {
Box(...) {
currentFraction.value = dismissState.progress.fraction
...
}
}
dismissContent = {...}
)
I'm dealing with the same issue, it seems to be a feature, not a bug, but I did not find the way to disable it.
*If the user has dragged their finger across over 50% of the screen
width, the app should trigger the rest of the swipe back animation. If
it's less than that, the app should snap back to the full app view.
If the gesture is quick, ignore the 50% threshold rule and swipe back.*
https://developer.android.com/training/wearables/compose/swipe-to-dismiss
So Im supernew to Kotlin so im trying to create a simple game where if I press a button an Imageview will shift left or right. Below I tried using ObjectAnimator and it works. When I press the button it shifts to the right but it only does that only once on runtime. As it stands now I have one button programmed but I hope to have 4 directional buttons where I can move an Imageview around the screen. So How can I keep changing the position of an imageview when the app is running ?
Thank You!
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//val picture = findViewById<ImageView>(R.id.SpongeBob)
val position_button = findViewById<Button>(R.id.position_button)
position_button.setOnClickListener()
{
ObjectAnimator.ofFloat(SpongeBob, "TranslationX", 100f).apply {
duration = 200
start()
}
}
}
}
You can use picture.animate().setDuration(200).translationXBy(100f), be sure to type translationXBy and not translationX! You can also use "translationXBy" in your ObjectAnimator or use a variable instead of 100f.
What you are doing right now is moving the picture to x: 100 all the time, but what you really want is to move it by 100.
I made a small demo for you here.
I am working on a custom view, TreeView, which would display a tree given a root node. I am using the Kotlin language.
Here's what part of it looks like at the moment:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
drawNodeAndChildren(canvas, rootNode)
}
/**
* Draws a representation of a node and its children onto the canvas.
* ...
*/
private fun drawNodeAndChildren(canvas: Canvas, node: TreeNode<Person>, ...): Int {
...
// Calculate coordinates of rect
val top = ...
val bottom = ...
val left = ...
val right = ...
// Draw node representation
val rect = RectF(left, top, right, bottom)
canvas.drawRect(rect, nodePaint)
// Draw line to connect to parent
...
// Repeat for children
...
}
This works successfully, and I am able to produce a visual tree with rectangles representing nodes and lines between parents and children.
However, I want to be able to display an image and text of the person. I have written a compound view called PersonView which is able to do this given a Person object. Ideally, in the future, the user would be able to click on these PersonViews in the TreeView to trigger an action.
So essentially, instead of rectangles, PersonViews would be drawn.
I have tried creating the view programmatically and setting its layout params like so:
...
val personView = PersonView(context).apply { person = node.data }
val layoutParams = ViewGroup.LayoutParams(
(right - left).toInt(), // width
(bottom - top).toInt() // height
)
personView.layoutParams = layoutParams
personView.x = left
personView.y = top
personView.draw(canvas)
...
I've also tried:
...
val personView = PersonView(context).apply { person = node.data }
personView.layout(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
personView.draw(canvas)
...
In both cases, nothing is drawn instead of the rectangles - it's as if the two code samples above has no effect.
N.B. the lines between where the rectangles would be (i.e. connections between nodes) is drawn, and this part of the code has not been altered.
So my question is how do I draw a custom view inside another custom view so that I can get the effect I have described?
I have a RecyclerView with items of varying heights with a scrollbar.
Because of the different heights of the items, the scrollbar changes it's vertical size, dependent on which items are currently displayed (see screenshots).
I have created an example project that displays the problem here.
Has anyone had the same problem and fixed it?
How can I override the calculation of the scrollbar height and position to come up with an own implementation?
EDIT: The scrollbar's position and height can be controlled by overriding RecyclerViews computeVerticalScrollOffset, computeVerticalScrollRange and computeVerticalScrollExtent.
I have no idea though on how to implement these to make the scrollbar work properly with dynamic item heights.
The problem, I reckon, is that RecyclerView estimates the total height of all items based on the items currently visible and sets position and height of the scrollbar accordingly. One way to solve this might be to give a better estimation of the total height of all items.
The best way to handle this situation may be to somehow calculate the scroll bar range based on the size of each item. That may not be practical or desirable. In lieu of that, here is a simple implementation of a custom RecyclerView that you can play with to try to get what you want. It will show you how you can use the various scroll methods to control the scroll bar. It will stick the size of the thumb to an initial size based upon the number of items displayed. The key thing to remember is that the scroll range is arbitrary but all other measurements (extent, offset) must use the same units.
See the documentation for computeVerticalScrollRange().
Here is a video of the result.
Update: The code has been updated to correct a few issues: The movement of the thumb is less jerky and the thumb will now come to rest at the bottom as the RecyclerView scrolls to the bottom. There are also a few caveats that are given after the code.
MyRecyclerView.java (updated)
public class MyRecyclerView extends RecyclerView {
// The size of the scroll bar thumb in our units.
private int mThumbHeight = UNDEFINED;
// Where the RecyclerView cuts off the views when the RecyclerView is scrolled to top.
// For example, if 1/4 of the view at position 9 is displayed at the bottom of the RecyclerView,
// mTopCutOff will equal 9.25. This value is used to compute the scroll offset.
private float mTopCutoff = UNDEFINED;
public MyRecyclerView(Context context) {
super(context);
}
public MyRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Retrieves the size of the scroll bar thumb in our arbitrary units.
*
* #return Scroll bar thumb height
*/
#Override
public int computeVerticalScrollExtent() {
return (mThumbHeight == UNDEFINED) ? 0 : mThumbHeight;
}
/**
* Compute the offset of the scroll bar thumb in our scroll bar range.
*
* #return Offset in scroll bar range.
*/
#Override
public int computeVerticalScrollOffset() {
return (mTopCutoff == UNDEFINED) ? 0 : (int) ((getCutoff() - mTopCutoff) * ITEM_HEIGHT);
}
/**
* Computes the scroll bar range. It will simply be the number of items in the adapter
* multiplied by the given item height. The scroll extent size is also computed since it
* will not vary. Note: The RecyclerView must be positioned at the top or this method
* will throw an IllegalStateException.
*
* #return The scroll bar range
*/
#Override
public int computeVerticalScrollRange() {
if (mThumbHeight == UNDEFINED) {
LinearLayoutManager lm = (LinearLayoutManager) getLayoutManager();
int firstCompletePositionw = lm.findFirstCompletelyVisibleItemPosition();
if (firstCompletePositionw != RecyclerView.NO_POSITION) {
if (firstCompletePositionw != 0) {
throw (new IllegalStateException(ERROR_NOT_AT_TOP_OF_RANGE));
} else {
mTopCutoff = getCutoff();
mThumbHeight = (int) (mTopCutoff * ITEM_HEIGHT);
}
}
}
return getAdapter().getItemCount() * ITEM_HEIGHT;
}
/**
* Determine where the RecyclerVIew display cuts off the list of views. The range is
* zero through (getAdapter().getItemCount() - 1) inclusive.
*
* #return The position in the RecyclerView where the displayed views are cut off. If the
* bottom view is partially displayed, this will be a fractional number.
*/
private float getCutoff() {
LinearLayoutManager lm = (LinearLayoutManager) getLayoutManager();
int lastVisibleItemPosition = lm.findLastVisibleItemPosition();
if (lastVisibleItemPosition == RecyclerView.NO_POSITION) {
return 0f;
}
View view = lm.findViewByPosition(lastVisibleItemPosition);
float fractionOfView;
if (view.getBottom() < getHeight()) { // last visible position is fully visible
fractionOfView = 0f;
} else { // last view is cut off and partially displayed
fractionOfView = (float) (getHeight() - view.getTop()) / (float) view.getHeight();
}
return lastVisibleItemPosition + fractionOfView;
}
private static final int ITEM_HEIGHT = 1000; // Arbitrary, make largish for smoother scrolling
private static final int UNDEFINED = -1;
private static final String ERROR_NOT_AT_TOP_OF_RANGE
= "RecyclerView must be positioned at the top of its range.";
}
Caveats
The following issues may need to be addressed depending on the implementation.
The sample code works only for vertical scrolling. The sample code also assumes that the contents of the RecyclerView are static. Any updates to the data backing the RecyclerView may cause scrolling issues. If any changes are made that effect the height of any view displayed on the first full screen of the RecyclerView, the scrolling will be off. Changes below that will probably work OK. This is due to how the code calculates the scrolling offset.
To determine the base value for the scrolling offset, (variable mTopCutOff), the RecyclerView must be scrolled to the top the first time computeVerticalScrollRange() is invoked so views can be measured; otherwise, the code will stop with an "IllegalStateException". This is especially troublesome on an orientation change if the RecyclerView is scrolled at all. A simple way around this would be to inhibit restoration of the scrolling position so it defaults to the top on an orientation change.
(The following is probably not the best solution...)
var lm: LinearLayoutManager = object : LinearLayoutManager(this) {
override fun onRestoreInstanceState(state: Parcelable?) {
// Don't restore
}
}
I hope this helps. (btw, your MCVE made this a lot easier.)
Use item positions as metric of scroll progress. This will cause your scroll indicator to become a bit jumpy, but at least it will remain fixed-sized.
There are multiple implementations of custom scroll indicators for RecyclerView. Most double as fast scrollers.
Here is my own implementation, based on RecyclerViewFastScroller library. Basically, one have to create a custom View subclass, that will be animated, similarly to ScrollView and DrawerLayout:
Store current offset
During animation offset position of thumb View via View#offset* calls
During layout set position based on current offset.
You probably don't want to start learning all that magic now, just use some existing fast scrolling library (RecyclerViewFastScroller or one of it's clones).
Inspired by Cheticamp's solution I managed to spin my own extension of RecyclerView which doesn't have the computeVerticalScrollRange limitations.
In fact, this alternative solution doesn't require extending computeVerticalScrollRange at all.
By reasoning with things in terms of spans I managed to think of a solution that doesn't depend on calculating the height of any items in the RecyclerView.
Each item in the list has a span of 1, and I am fixing the scrollbar thumb size to a certain number of spans (meaning the scrollbar doesn't change its height as the user scrolls).
Now consider the following things:
rangeSpanCount to be the number of spans a.k.a the number of items in the adapter
firstSpan to be the position of the first visible span (first completely visible if any, otherwise the first partially visible)
lastSpan to be the position of the last visible span (last completely visible if any, otherwise the last partially visible)
visibleSpanCount, equal to lastSpan - firstSpan, to be the number of spans currently visible in the screen
remainingSpanCount, equal to rangeSpanCount - 1 - visibleSpanCount, to be the number of spans remaining in the RecyclerView
Then for the sake of the explanation assume we have a list of 9 spans, and only 3 of them can be visible at any given time (although the logic holds even if the number of visible spans at a given moment is dynamic):
0 1 2 3 4 5 6 7 8 9
0 1 2{------------}
| the size of this range is:
{--}2 3 4 |===========> rangeSpanCount - 1 - visibleSpanCount
{-----}3 4 5
{-------}4 5 6
{-----------}6 7 8
{-------------}7 8 9
| you can see that this range is simply computed as:
|===========> firstSpan - 0
Then notice how we can use the range that grows as the scrolling from top to bottom happens and the range of spans that is left out of sight at any given moment to calculate the progress of the scrolling throughout the RecyclerView.
First we figure out how much has the growing range grown:
partialProgress = (firstSpan - 0) / remainingSpanCount
(From 0% all the way to 100% when firstSpan == remainingSpanCount)
Then we calculate which span among the visible ones better represent the progress of the scrolling throughout the RecyclerView. Basically, we want to make sure the first span (of position 0) is chosen when RecyclerView is at the very top and the last span (of position rangeSpanCount - 1) to be chosen when we reach the very bottom. This is important otherwise your scrolling will be off when reaching these edges.
progressSpan = firstSpan + (visibleSpanCount * partialProgress)
And finally, you can use the position of this chosen span and the total number of spans to figure out the actual progress percentage across the RecyclerView, and use the real computed scroll range to determine the best offset for the scrollbar:
scrollProgress = progressSpan / rangeSpanCount
scrollOffset = scrollProgress * super.computeVerticalScrollRange()
And that's it! This solution can be adapted to support the horizontal axis, so it carries none of the caveats from Cheticamp's alternative.
It has one caveat, though: the movement of the scrollbar thumb is discrete, not continuous along the axis, meaning the jumping from one position to the next is noticeable. It is consistent, though, never "shaking" itself / going back and forth while the user performs a scroll to any direction.
This caveat can probably be solved by working with a much higher number of spans in respect to the number of items in the adapter (e.g. having multiple spans per item) but I didn't give it too much thought right now.
I hope my explanation is reasonably clear... and I thank you all for helping me with your answers, it really helped point me to the right direction!
Below you can check out the complete solution and source code:
package cz.nn.calllog.view.utils.recyclerview
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class SmartScrollbarRecyclerView(
context: Context,
attributeSet: AttributeSet?,
defaultStyleAttribute: Int
) : RecyclerView(context, attributeSet, defaultStyleAttribute) {
constructor(
context: Context,
attributeSet: AttributeSet
) : this(context, attributeSet, 0)
constructor(
context: Context
) : this(context, null, 0)
override fun computeVerticalScrollExtent(): Int {
return checkCalculationPrerequisites(
onFailure = {
super.computeVerticalScrollExtent()
},
onSuccess = { _, rangeSpan, scrollRange ->
val extentSpanCount = 1.5F
val scrollExtent = (extentSpanCount / rangeSpan)
(scrollExtent * scrollRange).toInt()
}
)
}
override fun computeVerticalScrollOffset(): Int {
return checkCalculationPrerequisites(
onFailure = {
super.computeVerticalScrollOffset()
},
onSuccess = { layoutManager, rangeSpanCount, scrollRange ->
val firstSpanPosition = calculateFirstVisibleItemPosition(layoutManager)
val lastSpanPosition = calculateLastVisibleItemPosition(layoutManager)
val visibleSpanCount = lastSpanPosition - firstSpanPosition
val remainingSpanCount = rangeSpanCount - 1 - visibleSpanCount
val partialProgress = (firstSpanPosition / remainingSpanCount)
val progressSpanPosition = firstSpanPosition + (visibleSpanCount * partialProgress)
val scrollProgress = progressSpanPosition / rangeSpanCount
(scrollProgress * scrollRange).toInt()
}
)
}
private fun calculateFirstVisibleItemPosition(layoutManager: LinearLayoutManager): Int {
val firstCompletelyVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition()
return if (firstCompletelyVisibleItemPosition == -1) {
layoutManager.findFirstVisibleItemPosition()
} else {
firstCompletelyVisibleItemPosition
}
}
private fun calculateLastVisibleItemPosition(layoutManager: LinearLayoutManager): Int {
val lastCompletelyVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition()
return if (lastCompletelyVisibleItemPosition == -1) {
layoutManager.findLastVisibleItemPosition()
} else {
lastCompletelyVisibleItemPosition
}
}
private fun checkCalculationPrerequisites(
onFailure: () -> Int,
onSuccess: (LinearLayoutManager, Float, Int) -> Int
): Int {
val layoutManager = layoutManager
if (layoutManager !is LinearLayoutManager) {
return onFailure.invoke()
}
val scrollRange = computeVerticalScrollRange()
if (scrollRange < height) {
return 0
}
val rangeSpanCount = calculateRangeSpanCount()
if (rangeSpanCount == 0F) {
return 0
}
return onSuccess.invoke(layoutManager, rangeSpanCount, scrollRange)
}
private fun calculateRangeSpanCount(): Float {
val recyclerAdapter = adapter ?: return 0F
return recyclerAdapter.itemCount.toFloat()
}
}
If I'm not mistaken the attribute android:scollBarSize="Xdp" should work for you. Add it to your RecyclerView xml.
That way you decide the size, and it will remain fixed.
I have a circle at the center of the screen inside which there's an ImageView + TextView. I have another two ImageView+TextView, one at the top and another at bottom of the screen.
My requirement is :
I want a copy of the top ImageView+TextView and a copy of the bottom ImageView+TextView to move in animation into the center of the circle, thereby changing the value of the textView inside the circle.
For example:
Say top textView has value 200 and bottom textview has value 300. I want a portion of those values (say 100 or 150) to animate and move into the circle, but the original values 200 and 300 should remain on the same position.
I've tried using TranslateAnimation. However I face issues finding the x and y coordinates of the center circle. It is not exactly going to the center of the circle. Also original view's position is not retained.
TranslateAnimation animation = new
TranslateAnimation(startLayout.getX(),endLayout.getX(),
startLayout.getY(),endLayout.getY);
animation.setDuration(1000);
animation.setFillAfter(false);
startView.startAnimation(animation);
startLayout is the linearlayout in which ImageView and TextView reside.
Please help! Thanks!
I had the same issue and I fixed by using the next code (sorry is in Kotlin, but works the same in Java).Let's say viewFirst wants to reach viewTwo position:
(DON'T USE):
viewFirst.animate()
.translationX(viewSecond.x)
.translationY(viewSecond.y)
.setDuration(1000)
.withEndAction {
//to make sure that it arrives,
//but not needed actually these two lines
viewFirst.x = viewSecond.x
viewFirst.y = viewSecond.y
}
.start()
(USE THIS SOLUTION):
viewFirst.animate()
.x(viewSecond.x)
.y(viewSecond.y)
.setDuration(1000)
.withEndAction {
//to make sure that it arrives,
//but not needed actually these two lines
viewFirst.x = viewSecond.x
viewFirst.y = viewSecond.y
}
.start()
Using the getX() and getY() methods define the position of the view in pixels, but the constructor you use defines Float type values that must be values from 0.0f to 1.0f
TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)
This is another option using the view`s position in pixels:
viewFirst.animate()
.x(viewSecond.getX())
.y(viewSecond.getY())
.setDuration(1000).withEndAction(new Runnable() {
#Override
public void run() {
viewFirst.setX(tv2.getX());
viewFirst.setY(tv2.getY());
}
}).start();
Try this for accurate coordinates
private fun moveView(viewToBeMoved: View, targetView: View) {
val targetX: Float =
targetView.x + targetView.width / 2 - viewToBeMoved.width / 2
val targetY: Float =
targetView.y + targetView.height / 2 - viewToBeMoved.height / 2
viewToBeMoved.animate()
.x(targetX)
.y(targetY)
.setDuration(2000)
.withEndAction {
targetView.visibility = View.GONE
}
.start()
}