Scale from start rect to final position - android

I'm trying to scale view from start rectangle (e.g. defined by another view) to it's final position.
I tried to use the following code to setup animations which looks straight forward:
float scaleX = 0f;
float scaleY = 0f;
Rect startRect = new Rect(10, 10, 100, 100); // taken from real view position with getLocationOnScreen
final Collection<Animator> animators = new ArrayList<>();
if (animatedView.getMeasuredHeight() != 0) {
scaleX = (float)startRect.width() / animatedView.getMeasuredWidth();
}
if (animatedView.getMeasuredHeight() != 0) {
scaleY = (float)startRect.height() / animatedView.getMeasuredHeight();
}
animatedView.getLocationInWindow(location);
animatedView.setPivotX(startRect.left);
animatedView.setPivotY(startRect.top);
animatedView.setScaleX(scaleX);
animatedView.setScaleY(scaleY);
animators.add(ObjectAnimator.ofFloat(animatedView, View.SCALE_X, 1.0f).setDuration(1000));
animators.add(ObjectAnimator.ofFloat(animatedView, View.SCALE_Y, 1.0f).setDuration(1000));
The animatedView is child of RelativeLayout (layout parameters set to below some title view of layout) and measured width and height and location are valid values at the moment of animation setup.
Depending on startRect I observe different animations - sometimes animated view get displayed below or above startRect.
Seems RectEvaluator is one of possible solutions, but it's available only from API 18.
What is the proper way to animate view from start rectangle position to final (not modified one)?

As per comments on the question, it's possible to copy RectEvaluator code from Android source, and then apply the following logic:
RectViewAnimator mRectAnimator;
/**
* Creates animator which can be played. From some start position
* to final (real position).
* From final position to start position can be achieved using reverse interpolation.
*/
private Collection<Animator> createMoveAnimators(View targetView, Rect startRect) {
final Collection<Animator> animators = new ArrayList<>();
final int[] location = new int[2];
targetView.getLocationOnScreen(location);
final Rect finalRect = new Rect(location[0], location[1],
location[0] + targetView.getMeasuredWidth(),
location[1] + targetView.getMeasuredHeight());
// Must keep this reference during animations, since Animator keeps only WeakReference to it's targets.
mRectAnimator = appendRectEvaluatorAnimation(animators, targetView, 500, startRect, finalRect);
return animators;
}
private RectViewAnimator appendRectEvaluatorAnimation(final Collection<Animator> animators, final View view, final int duration,
final Rect startRect, final Rect finalRect) {
final float scaleX = (float) startRect.width() / finalRect.width();
final float scaleY = (float) startRect.height() / finalRect.height();
view.setTranslationY(startRect.top - (finalRect.top + (1 - scaleY) * finalRect.height() / 2));
view.setTranslationX(startRect.left - (finalRect.left + (1 - scaleX) * finalRect.width() / 2));
view.setScaleX(scaleX);
view.setScaleY(scaleY);
final RectViewAnimator rectViewAnimator = new RectViewAnimator(view, finalRect);
final Animator animator = ObjectAnimator.ofObject(rectViewAnimator, RectViewAnimator.RECT,
new RectEvaluator(), startRect, finalRect);
animators.add(animator);
return rectViewAnimator;
}
private static class RectViewAnimator {
static final String RECT = "rect";
private final View target;
private final Rect finalRect;
RectViewAnimator(final View target, final Rect finalRect) {
this.target = target;
this.finalRect = finalRect;
}
#Keep
public void setRect(final Rect r) {
final float scaleX = (float)r.width() / finalRect.width();
final float scaleY = (float)r.height() / finalRect.height();
target.setScaleX(scaleX);
target.setScaleY(scaleY);
target.setTranslationX(r.left - (finalRect.left + (1 - scaleX) * finalRect.width() / 2));
target.setTranslationY(r.top - (finalRect.top + (1 - scaleY) * finalRect.height() / 2));
}
}

Related

Add view to layout in specific location using Rect

I have a custom view and I want to add this view in different location of screen. I created a method which can create a Rect base on some input parameters and I want to show this object on the screen. I used FrameLayout and RelativeLayout for this purpose but FrameLayout put it in the corner and RelativeLayout doesn't show it. How can I fix it? Thanks in advance.
Java Code:
RelativeLayout deviceContent = findViewById(R.id.deviceContent);
.
.
.
private void addView(){
ViewDevice obj = new ViewDevice(this);
int degrees = new Random().nextInt(360);
final Rect rect = computeChildFrame(w/2, h/2, 50, degrees, (int) Utilities.dpToPx(48));
obj.layout(rect.left, rect.top, rect.right, rect.bottom);
deviceContent.addView(obj);
}
private static Rect computeChildFrame(final int centerX, final int centerY,
final int radius, final float degrees, final int size) {
final double childCenterX = centerX + radius
* Math.cos(Math.toRadians(degrees));
final double childCenterY = centerY + radius
* Math.sin(Math.toRadians(degrees));
return new Rect((int) (childCenterX - size / 2),
(int) (childCenterY - size / 2),
(int) (childCenterX + size / 2),
(int) (childCenterY + size / 2));
}
After adding View in frame Layout use translationX and translationY to position it as the new added view is added to the view at (0,0) suppose you want to add the view at position at 100,200 relative to the frame layout then below will be ur code:
private void addView(){
ViewDevice obj = new ViewDevice(this);
int degrees = new Random().nextInt(360);
final Rect rect = computeChildFrame(w/2, h/2, 50, degrees, (int) Utilities.dpToPx(48));
obj.layout(rect.left, rect.top, rect.right, rect.bottom);
obj.setTranslationX(100)
obj.setTranslationY(200)
deviceContent.addView(obj);
}

Rendering Bitmap based on Views position in parent

I'm trying to make a simple image editor. At the beginning I've thought that it'll be a good idea to simply save view state as Bitmap but, as it turned out, there is a wide range of screen resolutions and that leads to huge quality (and memory usage) fluctuations.
Now I'm trying to make a module that renders views state translated to desired resolution.
In the code below I'm trying to recreate current state of the views in canvas:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.id.test_1_1);
bitmap = Bitmap.createScaledBitmap(bitmap, parentView.getMeasuredWidth(), parentView.getMeasuredHeight(), true);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
for (View rootView : addedViews) {
ImageView imageView = rootView.findViewById(R.id.sticker);
float[] viewPosition = new float[2];
transformToAncestor(viewPosition, parentView, imageView);
Bitmap originalBitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
Matrix adjustMatrix = new Matrix();
adjustMatrix.postTranslate(viewPosition[0], viewPosition[1]);
adjustMatrix.postScale(
rootView.getScaleX(),
rootView.getScaleY(),
rootView.getWidth() / 2,
rootView.getHeight() / 2);
adjustMatrix.postRotate(rootView.getRotation(),
rootView.getWidth() / 2,
rootView.getHeight() / 2);
canvas.drawBitmap(originalBitmap, adjustMatrix, paint);
}
transformToAncestor function is from here.
public static void transformToAncestor(float[] point, final View ancestor, final View descendant) {
final float scrollX = descendant.getScrollX();
final float scrollY = descendant.getScrollY();
final float left = descendant.getLeft();
final float top = descendant.getTop();
final float px = descendant.getPivotX();
final float py = descendant.getPivotY();
final float tx = descendant.getTranslationX();
final float ty = descendant.getTranslationY();
final float sx = descendant.getScaleX();
final float sy = descendant.getScaleY();
point[0] = left + px + (point[0] - px) * sx + tx - scrollX;
point[1] = top + py + (point[1] - py) * sy + ty - scrollY;
ViewParent parent = descendant.getParent();
if (descendant != ancestor && parent != ancestor && parent instanceof View) {
transformToAncestor(point, ancestor, (View) parent);
}
}
(author wrote a note that his function does not support rotation, but there's not much rotation in my example so I don't think that important for now).
My problem is:
First image is generated via saving the parent view state. Second one is generated by translating views position, rotation and scale onto canvas.
As you can see, on the canvas, not scaled stickers are positioned properly, but scaled are incorrectly positioned.
How to position those scaled views properly?
I've managed to fix the issue myself.
It turned out my solution was nearly OK but I did not took into consideration that my manipulation of a matrix does change the arrangement of the original points, so my
rootView.getWidth() / 2,
rootView.getHeight() / 2
is no longer applicable as a center of the view after calling Matrix.postScale or Matrix.postRotation.
I wanted to:
apply scale with pivot on top left corner,
apply rotation with pivot on the center of the view.
Given the assumptions, here's the working code:
// setup variables for sizing and transformation
float position[] = new float[2];
transformToAncestor(position, rootView, imageView);
float desiredRotation = imageView.getRotation();
float sizeDeltaX = imageView.getMeasuredWidth() / (float) imageBitmap.getWidth();
float sizeDeltaY = imageView.getMeasuredHeight() / (float) imageBitmap.getHeight();
float desiredScaleX = imageView.getScaleX() * sizeDeltaX * scaleX;
float desiredScaleY = imageView.getScaleY() * sizeDeltaY * scaleY;
float imageViewWidth = imageView.getMeasuredWidth() * imageView.getScaleX();
float imageViewHeight = imageView.getMeasuredHeight() * imageView.getScaleY();
float rootViewWidth = rootView.getMeasuredWidth();
float rootViewHeight = rootView.getMeasuredHeight();
float percentXPos = position[0] / rootViewWidth;
float percentYPos = position[1] / rootViewHeight;
float percentXCenterPos = (position[0] + imageViewWidth/2)
/ rootViewWidth;
float percentYCenterPos = (position[1] + imageViewHeight/2)
/ rootViewHeight;
float desiredPositionX = background.getWidth() * percentXPos;
float desiredPositionY = background.getHeight() * percentYPos;
float desiredCenterX = background.getWidth() * percentXCenterPos;
float desiredCenterY = background.getHeight() * percentYCenterPos;
// apply above variables to matrix
Matrix matrix = new Matrix();
float[] points = new float[2];
matrix.postTranslate(
desiredPositionX,
desiredPositionY);
matrix.mapPoints(points);
matrix.postScale(
desiredScaleX,
desiredScaleY,
points[0],
points[1]);
matrix.postRotate(
desiredRotation,
desiredCenterX,
desiredCenterY);
// apply matrix to bitmap, then draw it on canvas
canvas.drawBitmap(imageBitmap, matrix, paint);
As you can see, the mapPoints method was the answer for my question - it simply returns points after tranformation.

Create ListPopUpWindow as designed by Material in Android

I'm trying to create a select menu in Android instead of using Spinner, but i'm facing some problems with the layout. How can I create something like the image below using ListPopUpWindow?
Thanks
I really like these menus floating over selected items, so I created my own DropDown class which implements your case. It does pretty much what #uguboz wrote.
I'm using PopupWindow with a custom layout with a RecyclerView. Then I handle onClick, display that window and use overriden PopupWindow.update() to calculate correct window position.
The most interesting part would be this code:
public class DropDownMenu extends PopupWindow {
public boolean show(View anchor) {
mAnchorView = anchor;
super.showAtLocation(anchor, Gravity.START | Gravity.TOP, 0, 0);
update();
return true;
}
public void update() {
final Resources res = getContentView().getContext().getResources();
int margin = (int) res.getDimension(R.dimen.carbon_margin);
int itemHeight = (int) res.getDimension(R.dimen.carbon_listItemHeight);
int marginHalf = (int) res.getDimension(R.dimen.carbon_paddingHalf);
ArrayAdapter adapter = getAdapter();
Rect windowRect = new Rect();
mAnchorView.getWindowVisibleDisplayFrame(windowRect);
int hWindow = windowRect.bottom - windowRect.top;
int wWindow = windowRect.right - windowRect.left;
int[] location = new int[2];
mAnchorView.getLocationInWindow(location);
if (mode == DropDown.Mode.Over) {
int maxHeightAbove = location[1] - windowRect.top - marginHalf * 2;
int maxItemsAbove = maxHeightAbove / itemHeight;
int maxHeightBelow = hWindow - location[1] - marginHalf * 2;
int maxItemsBelow = maxHeightBelow / itemHeight;
int itemsBelow = Math.min(adapter.getItemCount() - selectedItem, maxItemsBelow);
int itemsAbove = Math.min(selectedItem, maxItemsAbove);
int popupX = location[0] - margin - marginHalf;
int popupY = location[1] - marginHalf * 2 - itemsAbove * itemHeight - (itemHeight - (mAnchorView.getHeight() - mAnchorView.getPaddingTop() -
mAnchorView.getPaddingBottom())) / 2 + mAnchorView.getPaddingTop();
int popupWidth = mAnchorView.getWidth() + margin * 2 + marginHalf * 2 - mAnchorView.getPaddingLeft() - mAnchorView.getPaddingRight();
int popupHeight = marginHalf * 4 + Math.max(1, itemsAbove + itemsBelow) * itemHeight;
popupWidth = Math.min(popupWidth, wWindow - marginHalf * 2);
popupX = Math.max(popupX, 0);
popupX = Math.min(popupX, wWindow - popupWidth);
LinearLayoutManager manager = (LinearLayoutManager) recycler.getLayoutManager();
manager.scrollToPositionWithOffset(selectedItem - itemsAbove, 0);
update(popupX, popupY, popupWidth, popupHeight);
} else {
// not interesting
}
super.update();
}
}
The code is way too long to paste it here with all details, so I'll serve you a link to the class: DropDownMenu. Use it as you wish. I hope you'll find the code useful.
And I've made a sample for that image from the guidelines. It can be found in the sample app under Guidelines -> Menus/Behavior

Jump forward in andengine Android

I am developing a Game in which sprite have to jump forward .
I already explore it and used MoveYModifier for up and then MoveYModifier for down again it is working right except on more then one tap on the screen.But I want to move him forward as well while jump.
final Entity playerEntity = move;
final float jumpDuration = 3;
final float startX = playerEntity.getX();
final float jumpHeight = 100;
final MoveXModifier moveUpModifier = new MoveXModifier(jumpDuration / 2, startX, startX - jumpHeight); // - since we want the sprite to go up.
final MoveXModifier moveDownModifier = new MoveXModifier(jumpDuration / 2,startX - jumpHeight, startX);
final SequenceEntityModifier modifier = new SequenceEntityModifier(moveUpModifier, moveDownModifier);
playerEntity.registerEntityModifier(modifier);
return true;
Instead of using MoveXModifier why not just use the MoveModifier?
https://github.com/nicolasgramlich/AndEngine/blob/GLES2/src/org/andengine/entity/modifier/MoveModifier.java
final MoveModifier jumpForward = new MoveModifier(pDuration, pFromX, pToX, pFromY, pToY);

Getting View's coordinates relative to the root layout

Can I get a View's x and y position relative to the root layout of my Activity in Android?
The Android API already provides a method to achieve that.
Try this:
Rect offsetViewBounds = new Rect();
//returns the visible bounds
childView.getDrawingRect(offsetViewBounds);
// calculates the relative coordinates to the parent
parentViewGroup.offsetDescendantRectToMyCoords(childView, offsetViewBounds);
int relativeTop = offsetViewBounds.top;
int relativeLeft = offsetViewBounds.left;
Here is the doc
This is one solution, though since APIs change over time and there may be other ways of doing it, make sure to check the other answers. One claims to be faster, and another claims to be easier.
private int getRelativeLeft(View myView) {
if (myView.getParent() == myView.getRootView())
return myView.getLeft();
else
return myView.getLeft() + getRelativeLeft((View) myView.getParent());
}
private int getRelativeTop(View myView) {
if (myView.getParent() == myView.getRootView())
return myView.getTop();
else
return myView.getTop() + getRelativeTop((View) myView.getParent());
}
Let me know if that works.
It should recursively just add the top and left positions from each parent container.
You could also implement it with a Point if you wanted.
Please use view.getLocationOnScreen(int[] location); (see Javadocs). The answer is in the integer array (x = location[0] and y = location[1]).
View rootLayout = view.getRootView().findViewById(android.R.id.content);
int[] viewLocation = new int[2];
view.getLocationInWindow(viewLocation);
int[] rootLocation = new int[2];
rootLayout.getLocationInWindow(rootLocation);
int relativeLeft = viewLocation[0] - rootLocation[0];
int relativeTop = viewLocation[1] - rootLocation[1];
First I get the root layout then calculate the coordinates difference with the view.
You can also use the getLocationOnScreen() instead of getLocationInWindow().
No need to calculate it manually.
Just use getGlobalVisibleRect like so:
Rect myViewRect = new Rect();
myView.getGlobalVisibleRect(myViewRect);
float x = myViewRect.left;
float y = myViewRect.top;
Also note that for the centre coordinates, rather than something like:
...
float two = (float) 2
float cx = myViewRect.left + myView.getWidth() / two;
float cy = myViewRect.top + myView.getHeight() / two;
You can just do:
float cx = myViewRect.exactCenterX();
float cy = myViewRect.exactCenterY();
You can use `
view.getLocationOnScreen(int[] location)
;` to get location of your view correctly.
But there is a catch if you use it before layout has been inflated you will get wrong position.
Solution to this problem is adding ViewTreeObserver like this :-
Declare globally the array to store x y position of your view
int[] img_coordinates = new int[2];
and then add ViewTreeObserver on your parent layout to get callback for layout inflation and only then fetch position of view otherwise you will get wrong x y coordinates
// set a global layout listener which will be called when the layout pass is completed and the view is drawn
parentViewGroup.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
//Remove the listener before proceeding
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
parentViewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
parentViewGroup.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// measure your views here
fab.getLocationOnScreen(img_coordinates);
}
}
);
and then use it like this
xposition = img_coordinates[0];
yposition = img_coordinates[1];
I wrote myself two utility methods that seem to work in most conditions, handling scroll, translation and scaling, but not rotation. I did this after trying to use offsetDescendantRectToMyCoords() in the framework, which had inconsistent accuracy. It worked in some cases but gave wrong results in others.
"point" is a float array with two elements (the x & y coordinates), "ancestor" is a viewgroup somewhere above the "descendant" in the tree hierarchy.
First a method that goes from descendant coordinates to ancestor:
public static void transformToAncestor(float[] point, final View ancestor, final View descendant) {
final float scrollX = descendant.getScrollX();
final float scrollY = descendant.getScrollY();
final float left = descendant.getLeft();
final float top = descendant.getTop();
final float px = descendant.getPivotX();
final float py = descendant.getPivotY();
final float tx = descendant.getTranslationX();
final float ty = descendant.getTranslationY();
final float sx = descendant.getScaleX();
final float sy = descendant.getScaleY();
point[0] = left + px + (point[0] - px) * sx + tx - scrollX;
point[1] = top + py + (point[1] - py) * sy + ty - scrollY;
ViewParent parent = descendant.getParent();
if (descendant != ancestor && parent != ancestor && parent instanceof View) {
transformToAncestor(point, ancestor, (View) parent);
}
}
Next the inverse, from ancestor to descendant:
public static void transformToDescendant(float[] point, final View ancestor, final View descendant) {
ViewParent parent = descendant.getParent();
if (descendant != ancestor && parent != ancestor && parent instanceof View) {
transformToDescendant(point, ancestor, (View) parent);
}
final float scrollX = descendant.getScrollX();
final float scrollY = descendant.getScrollY();
final float left = descendant.getLeft();
final float top = descendant.getTop();
final float px = descendant.getPivotX();
final float py = descendant.getPivotY();
final float tx = descendant.getTranslationX();
final float ty = descendant.getTranslationY();
final float sx = descendant.getScaleX();
final float sy = descendant.getScaleY();
point[0] = px + (point[0] + scrollX - left - tx - px) / sx;
point[1] = py + (point[1] + scrollY - top - ty - py) / sy;
}
Incase someone is still trying to figure this out. This is how you get the center X and Y of the view.
int pos[] = new int[2];
view.getLocationOnScreen(pos);
int centerX = pos[0] + view.getMeasuredWidth() / 2;
int centerY = pos[1] + view.getMeasuredHeight() / 2;
I just found the answer here
It says:
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. These methods both return the location of the view relative to its parent. 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.
so use:
view.getLeft(); // to get the location of X from left to right
view.getRight()+; // to get the location of Y from right to left
You can use the following the get the difference between parent and the view you interested in:
private int getRelativeTop(View view) {
final View parent = (View) view.getParent();
int[] parentLocation = new int[2];
int[] viewLocation = new int[2];
view.getLocationOnScreen(viewLocation);
parent.getLocationOnScreen(parentLocation);
return viewLocation[1] - parentLocation[1];
}
Dont forget to call it after the view is drawn:
timeIndicator.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
final int relativeTop = getRelativeTop(timeIndicator);
});

Categories

Resources