I have an ImageView which is sometimes, but not always, being positioned incorrectly. It appears that sometimes it gets displayed before its pivotX and pivotY values are set. So they are both zero and the ImageView appears in the upper left hand corner of its space. And once drawn, both pivotX and pivotY never get set for that View and it remains out of place. I can "fix" it by explicitly setting pivotX and pivotY in the XML. But that is a hack. Better to determine root cause and find out when pivotX and pivotY should be set to understand why they aren't. Clearly it is a multi-threading issue and something is (somehow) drawing the image before it is properly initialized. So when do these values get set and/or what can cause them to not be set?
Related
I know how to make a typical animation using Animation API in Android. But I don't know how to make this:
It is simply a textview inside a ring while the ring is animating and the textview is just in a fix state. Note that the ring thickness is not changing while scaling.
Assuming you have your ring as a view already, you simply create an AnimatorSet with two ObjectAnimators that changes the scaleX and scaleY of the ring view. How far to scale depends on how far you want the ring to extend out or in.
Make sure you set the pivotX and pivotY to be the center of the view.
Then, simply have it repeat.
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...
I Have some really strange problems.
First problem is as follow:
I read how to override onMeasure(). I did it. I was expecting that when I set width/height in onMeasure that they will be exactly the same in onDraw, but apparently this is not true.
Also I except that measuredWidth will be same as width in draw phase, but again it isn't true.
onMeasure could be called many times. On each call I invoke setMeasuredDimension with calculated width and height. So for example, first time my view should be 480 x 245, on second call I recalculate it again, based on parent of course, and it should be 90 x 245.
For my great surprise android somehow is just ignoring everything but the first call. So in this case my final view size is 480 x 245.
Second issue which is as follow:
My view height is match_parent, my parent height is 0. So how am I supposed to set right height on onMeasure when I don't know it ?
Any Idea how to make android not to ignore my setMeasureDimensions calls and how to set match_parent ?
The space that's allocated to your view doesn't depend only on the size you measured, here's a snapshot of the process :
View.onMeasure gets called during the measurement of your parent. You got your MeasureSpec that is essentially how much space at most you can take (very summarized).
Given these specs, you determine the size of yourself, logically measuring your own children and calling setMeasuredDimension
A while after, your parent assigns you concrete dimensions, based on what you measured (but this also means it can be different). Then as these will be your dimensions, that's the one you have to use. The callback called at this point is onLayout, and you shall layout your children in the process, based on the dimensions that were affected to you.
After all this, your View will be drawn, that is View.dispatchDraw being called and resulting for simple views to onDraw. Drawing yourself also means drawing your children if you're a ViewGroup.
When drawing, the system passes a Canvas whose dimensions are these of the screen, and using translation and clipping, the canvas is passed along views so that they draw themselves. This avoids allocation during draw. For this reason, if your want to know what space is dedicated to you, you should not use Canvas.getWidth or Canvas.getHeight which are the dimensions of the screen, but rather retrieve the clipped dimensions (Canvas.getClipBounds())
And finally, when you correctly retrieve the clip bounds, they should usually be the same as your width and height (View.getWidth or View.getHeight), but they might be different, for example if the canvas was scaled.
So to summarize :
onMeasure purpose, is to determine the size of children, so that ViewGroup can compute they're dezired size.
onLayout purpose is to affect a width and height to each view, propagating to children.
onDraw purpose is to render your view.
I am trying to animate a custom scroll view which I have resized its using LayoutParams (height & width).
Something like that:
ObjectAnimator rotation = ObjectAnimator.ofFloat(view, "rotation", rotationValue);
rotation.start();
Animation runs fine but all of the ScrollView's content is drawn within the rotation pivot bounds. Images attached.
Anyone?
Thanks.
before: http://i.stack.imgur.com/TIxZL.png
after: http://i.stack.imgur.com/EWtag.png
I suspect your problem is that you need to set the android:clipChildren attribute on the parent ViewGroup.
Quoting the ViewGroup documentation for android:clipChildren:
Defines whether a child is limited to draw inside of its bounds or not. This is useful with animations that scale the size of the children to more than 100% for instance. In such a case, this property should be set to false to allow the children to draw outside of their bounds. The default value of this property is true.
So either add android:clipChildren="false" to the XML layout, or call the ViewGroup's setClipChildren method if you are building the interface programatically.
According to the docs for the View class:
The geometry of a view is that of a rectangle. A view has a location, expressed as a pair of left and top coordinates, and two dimensions, expressed as a width and a height. The unit for location and dimensions is the pixel.
It is possible to retrieve the location of a view by invoking the methods getLeft() and getTop(). The former returns the left, or X, coordinate of the rectangle representing the view. The latter returns the top, or Y, coordinate of the rectangle representing the view.
In addition, several convenience methods are offered to avoid unnecessary computations, namely getRight() and getBottom(). These methods return the coordinates of the right and bottom edges of the rectangle representing the view. For instance, calling getRight() is similar to the following computation: getLeft() + getWidth().
My interpretation of the above is that the View's position is controlled by its "Left" and "Top" values, while its width and height are controlled by its "Width" and "Height" values. This seems especially clear considering that last sentence, where "Right" is derived by adding Left and Width.
Despite this, when I use setLeft() and/or setTop() to change the position of the View, the SIZE of the View changes on screen! Meanwhile, the lower right corner of the View stays anchored to its original spot. This behavior implies that "Right" and "Bottom" are actual values, not derived as described in the docs.
So what is really going on here? The docs say one thing, but the behavior says the opposite. What is the proper way to reposition a View?
EDIT: I added a RelativeLayout:
myParams = new RelativeLayout.LayoutParams(300,300);
myParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
myParams.addRule(RelativeLayout.CENTER_VERTICAL);
myView.setLayoutParams(myParams);
...to create a View 300x300 centered on the screen. Works perfectly. But examining that RelativeLayout, the location seems to be controlled by leftMargin and topMargin - yet both are zero! That raises the questions of 1) how can you examine the LayoutParams to know where the View is right now, and 2) how can you alter the LayoutParams to move it to a different location?
EDIT: As an experiment, I added an onTouch method to the View and did this within it (excerpt):
if (MotionEvent.ACTION_MOVE == iAction) {
myParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
myParams.leftMargin = 0;
myParams.topMargin = 0;
v.setLayoutParams(myParams);
}
...on the theory that my vertically and horizontally centered View would then move to the upper left corner of the screen. Result: It didn't move at all. Not exactly surprising, since .leftMargin and .topMargin were already zero, but I wanted to try it just in case there was some magic hiding here.
Other suggestions?