Android: negative coordinates in ViewGroup and touch rectangles - android

I come from an ActionScript/Flash/AIR background and I'm new to the Android View hierarchy. Given that, I found something very odd about ViewGroup and it's coordinate system.
Let's say we have the following parent/child relationship inside the main Activity View which extends RelativeView (or basically some group layout that allows absolute coordinates and scale):
circle1 = new ImageView(context);
circle1.setImageBitmap(...);
circle2 = new ImageView(context);
circle2.setImageBitmap(...);
cont = new RelativeView(context);
cont.addView(circle1);
cont.addView(circle2);
this.addView(cont);
The idea is to create a container which is centered on the main view and to center two circles inside of the container. This allows the container to be animated, grouping the two circles.
cont.setX(getWidth() / 2);
cont.setY(getHeight() / 2);
circle1.setX(-circle1.getWidth() / 2);
circle1.setY(-circle1.getHeight() / 2);
circle2.setX(-circle2.getWidth() / 2);
circle2.setY(-circle2.getHeight() / 2);
Doing negative coordinates inside a ViewGroup, I immediately noticed that 'cont' clips them at the [0, 0] coordinate and cont.setClipChildren(false); has to be called. I'm guessing this is a bad idea, because it looks like a optimization for the invalidate() area. After disabling the clipping, the result renders as expected, but there is another problem:
illustration
Adding a touch event listener to circle2 returns a bogus touch rectangle (marked in purple) instead of the expected one (marked in cyan) which should be staring at the negative [X, Y] offset of circle2. The resulting rectangle starts at [0, 0] of 'cont' and ends at [circle2X + circle2W, circle2Y + circle2H] as if it's clipped at [0, 0] of 'cont'.
I know that you can solve the issue by not using negative coordinates, but that's not really a good solution if you are porting from ActionScript where negative coordinates make perfect sense (as in any real world coordinate system) and touch rectangles are calculated correctly in the DisplayObjectContainer class.
So what other solutions are there?
Should a custom ViewGroup class be created and what has to be done there?
Is there a magical setting which can allow touch rectangles of children Views not to be clipped at [0, 0] of the parent ViewGroup?
Something else?
Thanks.

You can use View#scrollTo to move the origin so that your content is shown entirely within the View's bounding rect.
Many things in the Android UI toolkit rely on the measured and laid out bounds of Views being accurate. In general you don't want to go against the grain here.

Related

Simultaneously move six points in circular paths around their own orbital centers

Suppose I have six points drawn in a Canvas along the circumference of a circle, in the pattern of a hexagon.
I want to simultaneously move and re-draw each of these six points in a small circular path - for example, each point moves along the circumference of a circle with 1/10th the radius of the larger circle.
I'm a little lost on how to do this with Canvas and onDraw and I don't know if better solutions exist. How would you do this?
Some answers have at least pointed me toward this which shows how a point might move along a circular path, but I don't know how to implement it for this situation:
for (double t = 0; t < 2*Pi; t += 0.01)
{
x = R*cos(t) + x_0;
y = R*sin(t) + y_0;
}
Thank you!
Here's one approach:
Start with a custom view that extends View and overrides onDraw. You are on the right track with using the drawing functions of Canvas.
Give your custom view a field to hold the current angle:
private float mTheta;
Then add a method like:
public void setTheta(float radians) {
mTheta = radians;
invalidate();
}
Then in onDraw, use the current value of mTheta to calculate the position of your points.
Of course, with the custom view you might need to handle sizing with an onMeasure override and possibly some layout with an onLayout override. If you set the dimensions of the view to an absolute dp value, the default behavior should work for you.
Doing it this way will set you up to override onTouch and allow user interaction to move the graphics, or use a ValueAnimator to cycle from 0 to 2π calling setTheta from within your AnimatorUpdateListener.
Put some code together and when you get stuck, post another question. If you add a comment to this answer with a link to the new question, I'll take a look at it.

how to make coordinates relative to a canvas

I have a 600x1000 pixel screen and my player starts near the bottom left. His x and y coordinates are (50, 900). I'm drawing this player to a canvas and using
canvas.translate(-player.getX()+GAMEWIDTH/2, 0)
to make sure the player stays in the middle of the screen with respect to the x-axis. This results in my player being rendered at (300, 900), however the player's x and y coordinates remain at (50, 900). This presents an issue because I need the player's coordinates to be the same as his rendered location because I use touch events on the player's collision rectangles to move him around. Is there any way to make my screen's coordinates be relative to my canvas coordinates so that my player's coordinates correspond to where they actually get rendered? Or is there maybe another way to do it?
The touchEvents are always based on the the x & y relative to the canvas element itself. Since the view is centering the character by translating the view canvas.translate(-player.getX() + GAMEWIDTH/2 , 0); you need to also apply that translation to the touchEvent. You can do it in the touch handler itself, or you could store both a relative and absolute position for the items in your game world. This will become more important if items become nested in one another. This is typically done by storing the parent element of a sprite/object.
//example of how I usually handle object hierarchy
this._x = this.x + this.parent._x;
this._y = this.y + this.parent._y;
the canvas/stage would also store it's center as a ._x and ._y which would be the parent of the the objects added, this way when you generate the global position to do your touchEvents against instead of passing in the .x or .y you would pass in the ._x and ._y which are already translated by the GAMEWIDTH/2.

Determining translationX/Y values for ObjectAnimator; how to move a view to an exact screen position?

I'm trying to move a view to the upper right corner of the screen, using ObjectAnimator.ofFloat(...) But, I'm not getting the results I expect. I'm getting the coordinates of the view beforehand, using ViewTreeListener, etc, and I already know the x value I need to offset from the end of the overall width. I can't get either dimension to move to where I want it. Relevant code:
Getting the starting coordinate; where the view currently is:
int[] userCoords = new int[]{0,0};
userControlLayout.getLocationInWindow(userCoords);
//also tried getLocationInScreen(userCoords); same result
userUpLeft = userCoords[0];
userUpTop = userCoords[1];
Surprisingly, I get the same value as userUpLeft (which is in screen coordinates, and not relative to parent) when I call userControlLayout.getLeft() I'd expect them to be different per my understanding of the docs. Anyway...
Constructing the ObjectAnimators:
//testXTranslate is a magic number of 390 that works; arrived at by trial. no idea why that
// value puts the view where I want it; can't find any correlation between the dimension
// and measurements I've got
ObjectAnimator translateX = ObjectAnimator.ofFloat(userControlLayout, "translationX",
testXTranslate);
//again, using another magic number of -410f puts me at the Y I want, but again, no idea //why; currently trying the two argument call, which I understand is from...to
//if userUpTop was derived using screen coordinates, then isn't it logical to assume that -//userUpTop would result in moving to Y 0, which is what I want? Doesn't happen
ObjectAnimator translateY = ObjectAnimator.ofFloat(userControlLayout, "translationY",
userUpTop, -(userUpTop));
My understanding is that the one arg call is equivalent to specifying the end coordinate you want to translate/move to, and the two arg version is starting at...ending at, or, from...to I've messed around with both and can't get there.
Clearly, I'm missing very fundamental knowledge, just trying to figure out what exactly that is. Any guidance much appreciated. Thanks.
First, userControlLayout.getLeft() is relative to the parent view. If this parent is aligned to the left edge of the screen, those values will match. For getTop() it's generally different simply because getLocationInWindow() returns absolute coordinates, which means that y = 0 is the very top left of the window -- i.e. behind the action bar.
Generally you want to translate the control relative to its parent (since it won't even be drawn if it moves outside those bounds). So supposing you want to position the control at (targetX, targetY), you should use:
int deltaX = targetX - button.getLeft();
int deltaY = targetY - button.getTop();
ObjectAnimator translateX = ObjectAnimator.ofFloat(button, "translationX", deltaX);
ObjectAnimator translateY = ObjectAnimator.ofFloat(button, "translationY", deltaY);
When you supply multiple values to an ObjectAnimator, you're indicating intermediate values in the animation. So in your case userUpTop, -userUpTop would cause the translation to go down first and then up. Remember that translation (as well as rotation and all the other transformations) is always relative to the original position.

TouchUtils.dragViewToX will only scroll in one direction

I can easily use the following code to scroll the view to the left using
TouchUtils.dragViewToX(this, myView, Gravity.LEFT, -1000);
It will even just sit there for a second if it can't scroll anymore, like it's still trying to, which is the expected behavior, so the contents of the view shouldn't be the issue.
But if I do the opposite, it acts as though it's not even there.
TouchUtils.dragViewToX(this, myView, Gravity.LEFT, 1000);
It doesn't even pause for a second to simulate the dragging like the other one will no matter what. It even returns the propper pixel value for distance covered! Why will it only listen to this function when it's supplied with a negative value? Why will it not drag in the opposite direction?
It's not even a positive/negative issue, it will scroll to the left if I supply a positive value and a different Gravity (like RIGHT or END), but no matter what it won't scroll to the right.
First of all, dragViewToX will try to drag the view you specify to the X-coordinate you specify.
The Gravity field specifies which part of the view is to be dragged.
So I'd suggest you use the rect values rather that using hard coded coordinates so that the test will work independent of the device specification. Here's a sample code.
View view = sListView.findViewById(R.id.foo);
Rect rect = new Rect();
view.getHitRect(rect);
TouchUtils.dragViewToX(this, view, Gravity.CENTER, rect.left); // To drag left. Make sure the view is to the right of rect.left
TouchUtils.dragViewToX(this, view, Gravity.CENTER, rect.right); // To drag left. Make sure the view is to the left of rect.right
The only thing that is wrong with your code is that the value 1000 is already out of the screen and therefore there is no where to drag to :)

Android TranslationAnimation not working correctly

I am trying to animate cards across the screen.
My code looks like this:
private void animate(ImageView animationSource, ImageView animationTarget, int animationDuration, int animationDelay) {
int[] animationSourcePosition = {0,0};
int[] animationTargetPosition = {0,0};
animationSource.getLocationInWindow(animationSourcePosition);
animationTarget.getLocationInWindow(animationTargetPosition);
TranslateAnimation cardDealingAnimation = new TranslateAnimation(
Animation.ABSOLUTE,animationSourcePosition[0],
Animation.ABSOLUTE,animationTargetPosition[0],
Animation.ABSOLUTE,animationSourcePosition[1],
Animation.ABSOLUTE,animationTargetPosition[1]);
cardDealingAnimation.setDuration(animationDuration);
cardDealingAnimation.setStartOffset(animationDelay);
animationTarget.startAnimation(cardDealingAnimation);
}
What happens is that it just pops up in the new place without showing the places in-between.
Funnily enough, on one of my targets it does sometimes show up.
There are two problems I see here.
One problem is that you're getting the position of your objects in the Window, where you should just use the position of the objects in their parent container. It's easier, faster, and more what you're looking for - where the objects live in their layout. If they do not live in the same container, then you may need to get the position in the window or the screen, and then just deal with delta values (the difference in their locations, not the absolute locations themselves).
You can get these simple container positions from the getLeft() and getTop() methods on View.
The second problem is that the TranslateAnimation parameters don't do what you think they do. The numeric values (the second, fourth, sixth, and eighth values) specify the starting and ending locations of the target object as offsets from its current location. That's why they have the word 'delta' in them in the method declaration. So you should not send in the absolute coordinates of the object positions their, but, rather, the deltas that they should have.
Let's suppose you have calculated an xDelta and yDelta as the difference in x and y between the source and target positions. If you're animating the source to the target position, then you would use something like 0, xDelta, 0, yDelta for these values.
Note that TranslateAnimation() does not physically move the object - it just draws it in a different place. So as soon as the animation is complete, the object will snap back to its original location. You may want to set the fillAfter property to maintain its end location on the screen when the animation completes.

Categories

Resources