Within Android, I'm trying to move a TextView from outside the parents bounds into view, but the contents never shows up, or remains clipped if it was partially within the bounds already.
Initial situation at start
Situation after animation
(Below this is another view, that was completely out of bounds and isn't drawn either)
I have 4 TextViews below each other in a custom Object extending RelativeLayout. Based on a percentage the top 2 should move outside it's bounds and the bottom 2 should move in (from the bottom).
I use the following code to update the properties of each TextView. In this class each variable **positionY* is filled with their initial position from the layout-xml. effect is percentage between 0 & 1. The animation works, but the views aren't drawn again.
public class ActionBarTitleView extends RelativeLayout {
public void updateTransition(float effect) {
float height = getHeight();
titleView1.setY(title1positionY - height*effect);
detailView1.setY(detail1positionY - height*effect);
titleView2.setY(title2positionY - height*effect);
detailView2.setY(detail2positionY - height*effect);
invalidate();
}
}
What I tried
After some researching I found a few hints what the issue might be, but so far none of the tried options had any effect. Below is a list of things I've found on SO and tried.
Calling invalidate() on the RelativeLayout - No effect.
Invalditing the TextViews - No effect.
clipChildren = false for the RelativeLayout - No effect.
setWillNotDraw = false for the RelativeLayout - No effect. (onDraw is being called)
I haven't tried to solve this with a ScrollView, but I don't want to really, cause that adds another layer in the hierachy for something pretty small.
I thought I understood the drawing logic, but perhaps I'm missing something, so I hope someone might be able to point me in the right direction.
What I ended up doing (September 3rd)
Since no real solution was offered, I tried again and came to the following "fix". I set both second labels to Visibility.GONE, but within the original bounds of the container view. Then when I start the animation, I set their correct values, then move them outside the bounds and finally setting Visiblity.VISIBLE. When the animation progresses the labels roll into view as supposed to. So a fix to the issue, but no real explanation why the TextViews aren't drawn again...
Background
To learn animations, I'm creating a Towers of Hanoi type of game. My main goal is to animate the movement of the block from one tower to another. I've got the following layout
|------RelativeLayout------|
|-Linear-|-Linear-|-Linear-|
|-Block1-|-----------------|
|-Block2-|-----------------|
|-Block3-|-----------------|
|--------------------------|
ID| Tower1 | Tower2 | Tower3 |
I've set the XML attribute android:clipChildren="false" on every ViewGroup.
For example, if I tried to animate the movement of Block1 from Tower1 to Tower2 using Block1.animate().setDuration(3000).translationX(1000). As the layout is right now, Block1 will animate within Tower1, but Block1 gets clipped the second it leaves Tower1.
I've played with changing the z-order by adding the blocks last in the XML file. It doesn't reliably work, though.
To ensure animations don't get clipped, I've decided to add a copy of the Block1 (named copyBlock) to the root RelativeLayout, position copyBlock on top of Block1, and animate it to the destination Tower2 (defined in coordinates)
To get the destination coordinates, I was planning on adding an invisible copyBlock to Tower2, then get the coordinates. This way, I can take advantage of the LinearLayout's layout functions to account for matters such as padding, gravity, etc. Otherwise, I'll have to get the position of Tower2, calculate the topmost block, adjust the coordinates of Block1 so that it'll be on top of the topmost block and centered.
But, I'm pretty sure this way is hacky, and there's a better way
Questions
How can I get the above destination coordinates without having to add an invisible view to the destination tower? Is there a way to ask for a "prelayout" without having to add View to the layout?
Do you have any explanations for why the animation gets clipped? Is there a better way to approach this rather than adding a new view to the root RelativeLayout ViewGroup?
Ok,
1- You can't overcome the clipping issue, because you are trying to move the child beyond the parent dimensions, unless you are moving within the same parent.
2- Use FrameLayout instead of RelativeLayout so you can control views margins correctly;
3- You can get the destination coordinates by knowing the height of the root layout and width, its seems the towers width are equal, so the (total width/3) will give the cell width then get the X coordinate, and the (total height - cell height ) will give the Y coordinate of the tower ( assuming you have the tower hight)
I am facing the same problem in this question, but I have difficulty in understanding the exact meaning of the four position parameters in layout(int l, int t, int r, int b). I know they represent left, top, right and bottom respectively relative to parent, but "relative" to where exactly?
For example, if I translate the button down 100 pixel, in order to move the clickable area down 100 pixel should I set
// l t r b
button.layout(0, 100, 0, button.getHeight()+100)
? Does the integer 100 of second parameter mean 100 pixel down relative to the top of parent? Is the fourth parameter means button.getHeight()+100 relative to the top of parent also??? I tested on my device, the clickable area does not move down as I want. I'm very confused here, any help is greatly appreciated.
The parameters to layout are relative to the parent view's upper left corner. X increases as you move to the right and Y increases as you move down. It is not relative to the view's current position.
You probably don't want to call layout on a child view outside of its parent's onLayout method. There are several other bits of internal bookkeeping that happen as a result of calling layout that will change internal state in a way you don't want for simple animations.
Typically layout is called like this:
child.layout(x, y, x + child.getMeasuredWidth(), y + child.getMeasuredHeight());
The parameters are absolute, with (0, 0) at the parent's upper left corner.
But if you're simply moving a view around for animation you probably want to do something more like:
// Adjust horizontally; dx = change in x position
child.offsetLeftAndRight(dx);
// Adjust vertically; dy = change in y position
child.offsetTopAndBottom(dy);
The parameters to the offset* methods are relative to the view's current position.
If you write a ViewGroup class that moves its child views around in this way, remember that you can get a layout request at any time. At that point your ViewGroup's onLayout method will try to position the child based on its usual layout rules and it will need to adjust this positioning to reflect any other accumulated movement you want to preserve.
what is the difference between
View.getLeft() vs View.getScrollX() ?
Please don't copy and paste definition from the documentation, because I am about to do it for you below
getScrollX()
Return the scrolled left position of this view.
getLeft()
Left position of this view relative to its parent
I think those 2 values should be the same, but a sample program of mine, if i do View.scrollBy(20, 0)
i see that getScrollX() will return 20 and the view is actually moved to the right, but getLeft() remains to be zero
i am confused, because if visually the view is being scrolled to the right by 20px, its left position should be updated as well, but it is still 0
obviously they can't be the same, otherwise there is no need to have 2 different methods that return the same result
please help
getLeft() returns the views location relative to its parent. How it has scrolled does not impact this at all. Scrolling impacts the contents of the view, not its location.
a quote from the android documentation regarding getLeft():
For instance, when getLeft() returns 20, that means the view is located 20 pixels to
the right of the left edge of its direct parent.
getScrollX(), on the other hand, lets you know how the content in the view has moved.
View.scrollBy(20,0) affects the content in the view (like subviews of the view) and doesn't actually move the view relative to the view's parent.
How can I change the position of view through code? Like changing its X, Y position. Is it possible?
For anything below Honeycomb (API Level 11) you'll have to use setLayoutParams(...).
If you can limit your support to Honeycomb and up you can use the setX(...), setY(...), setLeft(...), setTop(...), etc.
Yes, you can dynamically set the position of the view in Android. Likewise, you have an ImageView in LinearLayout of your XML file. So you can set its position through LayoutParams.But make sure to take LayoutParams according to the layout taken in your XML file. There are different LayoutParams according to the layout taken.
Here is the code to set:
LayoutParams layoutParams=new LayoutParams(int width, int height);
layoutParams.setMargins(int left, int top, int right, int bottom);
imageView.setLayoutParams(layoutParams);
There are different valid answers already, but none seems to properly suggest which method(s) to use in which case, except for the corresponding API level restrictions:
If you can wait for a layout cycle and the parent view group supports MarginLayoutParams (or a subclass), set marginLeft / marginTop accordingly.
If you need to change the position immediately and persistently (e.g. for a PopupMenu anchor), additionally call layout(l, t, r, b) with the same coordinates. This preempts what the layout system will confirm later.
For immediate (temporary) changes (such as animations), use setX() / setY() instead. In cases where the parent size doesn't depend on WRAP_CHILDREN, it might be fine to use setX() / setY() exclusively.
Never use setLeft() / setRight() / setBottom() / setTop(), see below.
Background:
The mLeft / mTop / mBottom / mRight fields get filled from the corresponding LayoutParams in layout(). Layout is called implicitly and asynchronously by the Android view layout system. Thus, setting the MarginLayoutParams seems to be the safest and cleanest way to set the position permanently. However, the asynchronous layout lag might be a problem in some cases, e.g. when using a View to render a cursor, and it's supposed to be re-positioned and serve as a PopupMenu anchor at the same time. In this case, calling layout() worked fine for me.
The problems with setLeft() and setTop() are:
Calling them alone is not sufficient -- you also need to call setRight() and setBottom() to avoid stretching or shrinking the view.
The implementation of these methods looks relatively complex (= doing some work to account for the view size changes caused by each of them)
They seem to cause strange issues with input fields: EditText soft numeric keyboard sometimes does not allow digits
setX() and setY() work outside of the layout system, and the corresponding values are treated as an additional offset to the left / top / bottom / right values determined by the layout system, shifting the view accordingly. They seem to have been added for animations (where an immediate effect without going through a layout cycle is required).
There is a library called NineOldAndroids, which allows you to use the Honeycomb animation library all the way down to version one.
This means you can define left, right, translationX/Y with a slightly different interface.
Here is how it works:
ViewHelper.setTranslationX(view, 50f);
You just use the static methods from the ViewHelper class, pass the view and which ever value you want to set it to.
I would recommend using setTranslationX and setTranslationY. I'm only just getting started on this myself, but these seem to be the safest and preferred way of moving a view. I guess it depends a lot on what exactly you're trying to do, but this is working well for me for 2D animation.
You can try to use the following methods, if you're using HoneyComb Sdk(API Level 11).
view.setX(float x);
Parameter x is the visual x position of this view.
view.setY(float y);
Parameter y is the visual y position of this view.
I hope it will be helpful to you. :)
For support to all API levels you can use it like this:
ViewPropertyAnimator.animate(view).translationYBy(-yourY).translationXBy(-yourX).setDuration(0);
Set the left position of this view relative to its parent:
view.setLeft(int leftPosition);
Set the right position of this view relative to its parent:
view.setRight(int rightPosition);
Set the top position of this view relative to its parent:
view.setTop(int topPosition);
Set the bottom position of this view relative to its parent:
view.setBottom(int bottomPositon);
The above methods are used to set the position the view related to its parent.
Use LayoutParams.
If you are using a LinearLayout you have to import android.widget.LinearLayout.LayoutParams, else import the proper version of LayoutParams for the layout you're using, or it will cause a ClassCastException, then:
LayoutParams layoutParams = new LayoutParams(int width, int height);
layoutParams.setMargins(int left, int top, int right, int bottom);
imageView.setLayoutParams(layoutParams);
NB: Note that you can use also imageView.setLeft(int dim), BUT THIS WON'T set the position of the component, it will set only the position of the left border of the component, the rest will remain at the same position.
Use RelativeLayout, place your view in it, get RelativeLayout.LayoutParams object from your view and set margins as you need. Then call requestLayout() on your view. This is the only way I know.
In Kotlin you can do it as below;
view
.animate()
.x(50f)
.y(100f)
.duration = 500L
I found that #Stefan Haustein comes very close to my experience, but not sure 100%. My suggestion is:
setLeft() / setRight() / setBottom() / setTop() won't work sometimes.
If you want to set a position temporarily (e.g for doing animation, not affected a hierachy) when the view was added and shown, just use setX()/ setY() instead. (You might want search more in difference setLeft() and setX())
And note that X, Y seem to be absolute, and it was supported by AbsoluteLayout which now is deprecated. Thus, you feel X, Y is likely not supported any more. And yes, it is, but only partly. It means if your view is added, setX(), setY() will work perfectly; otherwise, when you try to add a view into view group layout (e.g FrameLayout, LinearLayout, RelativeLayout), you must set its LayoutParams with marginLeft, marginTop instead (setX(), setY() in this case won't work sometimes).
Set position of the view by marginLeft and marginTop is an unsynchronized process. So it needs a bit time to update hierarchy. If you use the view straight away after set margin for it, you might get a wrong value.
One thing to keep in mind with positioning is that each view has an index relative to its parent view. So if you have a linear layout with three subviews, the subviews will each have an index: 0, 1, 2 in the above case.
This allows you to add a view to the last position (or the end) in a parent view by doing something like this:
int childCount = parent.getChildCount();
parentView.addView(newView, childCount);
Alternatively you could replace a view using something like the following:
int childIndex = parentView.indexOfChild(childView);
childView.setVisibility(View.GONE);
parentView.addView(newView, childIndex);