Android Studio 2.0 beta 6
I am trying to use ViewPropertyAnimator to move a ImageView (ivSettings) inside a toolbar so that it is 20dp from the right and 20dp from the top, from is current location. And move the ImageView (ivSearch) 20dp from the left and top.
The imageViews are contained in a Toolbar.
This is the initial state and I want to move the icons into the upper corners inside the toolbar.
The code I am using is this to get the width and then subtract a value to get the ivSettings to be 20dp from the right.
final DisplayMetrics displayMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
final float widthPx = displayMetrics.widthPixels;
ivSearch.animate()
.setInterpolator(new AccelerateInterpolator())
.x(20)
.y(20)
.setDuration(250)
.start();
ivSettings.animate()
.setInterpolator(new AccelerateInterpolator())
.x(widthPx - 160)
.y(20)
.setDuration(250)
.start();
However, having tried this on different screen size I can't get the exact width calculation. Is there a better way of doing this?
Many thanks for any suggestions
You should be able to use the translationX and translationY properties to achieve the effect you want. These properties act as offsets from the original X and Y coordinates of the View.
Essentially translationX will displace the a View towards the right for a positive value and left for a negative value from it's X coordinate. Similarly translationY displaces the View towards the bottom for positive and top for negative values from it's Y coordinate.
Coming to your question, I am assuming that the final position you want to reach is the top left corner for the search icon, and the top right corner for the settings icon with zero padding for each view.
For the search icon I suggest you start by placing it in the top left corner of the toolbar. Then set both the translationX and translationY to 20p. That should place the search icon in the same place as your image. To move your search icon to the top left all you have to do is animate translationX & Y from 20dp to 0 dp.
Repeat the same for the settings icon, but set translationX to -20dp and translationY to 20dp. This way it'll be placed at the same position as your image. Animate both values to 0 to achieve your desired animation.
Here's the animation code for reference.
ivSearch.animate()
.setInterpolator(new AccelerateInterpolator())
.translationX(0) // Takes value in pixels. From 20dp to 0dp
.translationY(0) // Takes value in pixels. From 20dp to 0dp
.setDuration(250)
.start();
ivSettings.animate()
.setInterpolator(new AccelerateInterpolator())
.translationX(0) // Takes value in pixels. From -20dp to 0dp
.translationY(0) // Takes value in pixels. From 20dp to 0dp
.setDuration(250)
.start();
// To get pixels from dp
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
The great thing about this approach is that you no longer need to know the dimensions of the parent. All you care about is the offsets that you have specified via translationX and translationY.
I believe the problem is that the Animator methods are expecting the values to be in raw pixel values. It looks like the code would only work as you intend on a medium density screen. E.g. A hdpi screen would require 30px instead of 20px to be located at the same location. On higher density screens the code as it exists would push the icons closer to the edges...
That means we need to translate the expected dp value into raw pixel values and use those for animation. There is a handy method for converting dp to px so that it's correct on every device. I'm assuming that you want the left side of the gear to be 160dp from the right(Note I'm specifically saying the left side of the gear because the 160dp value should also include the width of the gear image as the x property is from the left of the image). So 160dp = gear image width + desired right margin distance.
Say you have a resource called ic_gear:
Drawable drawable = getResources().getDrawable(R.drawable.ic_gear);
float bitmapWidth = getIntrinsicWidth();
float rawPX20DPValue = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
float rawPXLeftOffset = rawPX20DPValue + bitmapWidth;
ivSearch.animate()
.setInterpolator(new AccelerateInterpolator())
.x(rawPX20DPValue)
.y(rawPX20DPValue)
.setDuration(250)
.start();
ivSettings.animate()
.setInterpolator(new AccelerateInterpolator())
.x(widthPx - rawPXLeftOffset)
.y(rawPX20DPValue)
.setDuration(250)
.start();
Also check on the offsets(padding) of the gear and search icon before the move as that will likely affect the end result as well.
Related
I have an image 3264x1836 and I display it in a custom AppCompatImageView having dimensions 1280x720. I want to preserve the image size so I use ScaleType.CENTER, but I also want that its top-left corner is positioned at the coordinates 0,0 of my custom view, so I set the padding left and top accordingly.
Now, to achieve that I had to use 3264-1280 (the difference between the widths) as left padding and 1836-720 (the difference between the heights) as top padding while, in my opinion, these values should be both divided by 2.
Can somebody explain why?
It is likely that you are breaking the AppCompatImageView code by asking it to do something that wasn't anticipated or tested.
While the above could still be true, the padding that you are applying is correct. Here are the calculations:
For simplicity, let's take a look at the left padding needed to shift the graphic. The same sort of calculations will work for the top padding.
The width of the graphic is greater than the width of the ImageView.
d = wgraphic - wimageview
If the graphic is centered in the ImageView, then the overhang on the left and right sides will be 1/2 the difference in widths.
s = d / 2
The graphic is centered within the padding of the ImageView. The amount of padding that has to be specified must be just enough to shift the center of the graphic by the amount s.
The initial center of the ImageView without padding is
ci = wimageview / 2
The shifted center with left padding ( p ) is
cs = p + (wimageview - p) / 2
So,
s = cs - ci = p + (wimageview - p) / 2 - (wimageview / 2)
Solving for the padding needed for a shift of s we get p = 2s = d. In other words, the padding we need is twice the shift required which is what you are seeing.
A fix that doesn't involve padding would be to specify
android:scaleType="matrix"
You should remove the padding. The new scale type will apply the identity matrix to the image and place it in the top/left corner without resizing.
I want to move and resize a TextView from the center of the screen to the upper left corner with the default 16dp margin. I used the code:
val pos = convertDpToPixel(16.0f, context)
testNameTextView
.animate()
.scaleX(0.5f)
.scaleY(0.5f)
.x(pos)
.y(pos)
It does not work because the final margin depends on the length of the text.
If I do not resize the view everything works fine.
I have also tried to do this:
testNameTextView
.animate()
.scaleX(0.5f)
.scaleY(0.5f)
.withEndAction {
testNameTextView
.animate()
.x(pos)
.y(pos)
}
but it did not make any difference. How can I resize and move the text?
I used:
testNameTextView
.animate()
.scaleX(SCALE)
.scaleY(SCALE)
.x(posTestName - (testNameTextView.width - testNameTextView.width * SCALE) / 2 )
.y(posTestName - (testNameTextView.height - testNameTextView.height * SCALE) / 2 )
So what happens is that the text is repositioned and then scaled keeping the center of the text fixed, so if you just do what I had done the left margin is incorrect because the text was shrunk.
You need to subtract the difference between half the width of the text before the scale and half the width of the text after the scaling.
PS
During the animation the translation and the resizing are done at the same time but the effect is the same as if the text was first translated and then scaled keeping the center of the text fixed
I have a view that I want to animate- scale it to a bigger size and at the same time move to the very top of the screen, just below the notification bar. The problem is however, at least I think, that the translationY complies with initial view scale and moves it to the top of the screen, but as the view grows, it gets cut off at top.
This is the code I'm using
image.animate()
.alpha(1f)
.translationY(0)
.scaleX(6f)
.scaleY(6f)
.start()
Any simple solution to this? A way I could calculate the real value I should set translationY to (based on scale) in order to be positioned correctly? Any help appreciated!
I am writing a drag and drop application and got really confused because of some parameters.
Please help to figure out.
First of all, I read the documentation for the View class and got the following explanations.
getX() : The visual x position of this view, in pixels.
getY() : The visual y position of this view, in pixels.
getWidth() : Return the width of the your view.
getHeight() : Return the width of the your view.
getTop() : Top position of this view relative to its parent.
getLeft() : Left position of this view relative to its parent.
Now when we finished with the official documentation, let's see what do we have.
I have an image with original size 500x500 called circle.
And here's the actual screenshot of my application
Here is the xml for the layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:id="#+id/imageView1"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/circle" />
</LinearLayout>
Now what am I concerned about. When I watch my locals, I get the following, which really confuses me.
I don't see any problem with the getX() and getY() functions, because they actually show me where does the image begin.
As the documentation states, the getWidth() and getHeight methods return the width and height of the view but the watch window tells me that my getWidth() and getHeight are 300, which I really can't understand, because in my XML I've set them 100dp each, so do the functions return me them in a different measurement, and how do I convert it to dp.
And finally, it tells me that getTop() and getLeft are 700 and 300, and as the documentation says, they are the position of the image relative to it's parent. But isn't my parent the Linear Layout, so what do this numbers mean in sense of screen positioning?
This is a supplemental answer for future visitors.
Left, Top: When a parent view lays out a subview, left is the distance from the left side of the parent to the left side of the subview. Likewise, top is the distance from the top of the parent to the top of the subview. Thus, getLeft() and getTop() return the coordinates of the top left corner of the view relative to its parent view (not the absolute coordinates on the screen).
X, Y: Usually getX() and getY() will return the same thing as getLeft() and getTop(). However, sometimes it is useful to move the view a little after it has already been laid out. This can be done with setTranslationX() and setTranslationY(). If these have been set then x and y will be different from left and top, where
x = left + translationX
y = top + translationY
Width, Height: You can find the width and the height of the view with getWidth() and getHeight(). This is not affected by a translation.
The above values are all in pixel dimensions.
All these measurement methods return sizes in pixels( px ), not density-pixels ( dp ). If you want to convert it you can get the density by calling:
float density = getResources().getDisplayMetrics().density;
And then divide the values you get with the provided density, for example:
int widthDp = (int)(img.getWidth() / density);
You can get pixels from dp with
float ht_px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ht, getResources().getDisplayMetrics());
float wt_px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, wt, getResources().getDisplayMetrics());
As of the positioning question.
getTop and getLeft are relative values and are based on your parent. Since the only parent of your ImageView is LinearLayout you are effectively positioning your ImageView directly below the ActionBar/ToolBar
Also don't use an image for a circle, you can draw it easily with canvas.drawCircle it takes much less memory.
Let's say the following coordinates are relative to (0, 0) being the top left corner of the phone's screen with increasing positive x values going to the right and increasing positive y values going down as diagrammed here: http://t.cyol.com/cache/temp/img/2011/02/1000/119/img/img_1297675862_0.jpg
I'd like to do a "pan out" animation where there is a little box whose top left corner is at (x, y), and it has width w and height h where x, y, w, and h are greater than 0. Everywhere inside that box is some content. Everywhere outside that box is black.
Over 500 milliseconds, the box's top left corner should move to (0, 0) and its width and height will grow to fill the entire screen. That is, over half a second, the box pans out to fullscreen.
The content inside the box is a WebView.
How do I achieve this animation? I tried scaling, but that's not what I want to achieve, because the content inside the box shouldn't get squished. Translating only works if the box starts out in a corner.
In order to achieve this effect, the whole view must be relayouted at each frame of the animation. You'll need to change the layout params of the view and call requestLayout(). Check this link:
Android Animate Scale Image to fit screen width and height