In code - after first part of animation pivot of view is changing and...view position too!(it is strange behavior)
Here's code(stipulation - one ValueAnimator):
ValueAnimator animator = ValueAnimator.ofFloat(0,180);
float firstAnimLineX = ((47.5f * Resources.getSystem().getDisplayMetrics().density));
float firstAnimLineY = ((2.5f * Resources.getSystem().getDisplayMetrics().density));
float secondAnimLineX = ((47.5f * Resources.getSystem().getDisplayMetrics().density));
float secondAnimLineY = ((47.5f * Resources.getSystem().getDisplayMetrics().density));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setPivotX((Float) animator.getAnimatedValue()>180/2?firstAnimLineX : secondAnimLineX);
view.setPivotY((Float) animator.getAnimatedValue()>180/2?firstAnimLineY : secondAnimLineY);
view.setRotation((Float) animator.getAnimatedValue());
}
});
XML file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/frameLayout">
<ImageView
android:layout_width="50dp"
android:layout_height="5dp"
android:layout_gravity="center_horizontal"
android:id="#+id/line"
/>
</FrameLayout>
</RelativeLayout>
And here is what i want to do(left part; right part - it's what realy happens)(yellow point - pivot):
I watched source code of setPivotX but it doesn't says me anything.
Maybe i should call someone of invalidate-methods of view?
I recreated this code and ran it on my Android phone. It gives the exact animation you are looking for.
I created an Image to match your 'match-stick' image.
I made it 400 px wide by 40 high for an example.
I gave it the id: 'bar'
ImageView bar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bar = findViewById(R.id.bar);
bar.getLayoutParams().width = 400; // I set the dimensions here.
bar.getLayoutParams().height = 40; // I set the dimensions here.
bar.setX(0); bar.setY(0); // Then I positioned it in upper left corner.
bar.setPivotX(380); // I set the pivot a little inside the image so that it looks kinda like it is pivoting on a nail.
bar.setPivotY(20);
bar.animate().rotation(-90).setDuration(2000); // Here it animates from your first image above, to your second image in 2 seconds.
// Here I used a postDelay to allow the first animation to finish its' 2 seconds 'before' calling the second animation. No need for an animate() - listener now.
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
bar.setY(360); // This re-positions the original image to the
// location where the animation ended.
bar.setRotation(90); // Because the original image was horizontal, this turns it vertical.
// Now we finish the animation to your 3rd image above.
bar.animate().rotation(0).setDuration(2000);
}
},2100);
}
Related
I am trying to implement TapToFocus feature using the CameraX Api .I have implemented it successfully but not able to know how to draw a circle on the preview describing the Location pressed by the user .
I want to have a circle in the preview like the image has
The are many ways to draw and animate a focus ring around a tap position on PreviewView, some fancier than others. One simple way of doing so is to:
Create a Drawable of the ring, something like this for instance.
Layout an ImageView containing the Drawable on top of PreviewView, and initially hide it.
<FrameLayout ...>
<androidx.camera.view.PreviewView
... />
<ImageView
android:id="#+id/focusRing"
android:src="#drawable/focus_ring"
android:visibility="invisible"
... />
</FrameLayout>
Set up a touch listener on PreviewView. On a touch event, use the event's coordinates to show the ring around it.
private void animateFocusRing(float x, float y) {
ImageView focusRing = findViewById(R.id.focusRing);
// Move the focus ring so that its center is at the tap location (x, y)
float width = focusRing.getWidth();
float height = focusRing.getHeight();
focusRing.setX(x - width / 2);
focusRing.setY(y - height / 2);
// Show focus ring
focusRing.setVisibility(View.VISIBLE);
focusRing.setAlpha(1F);
// Animate the focus ring to disappear
focusRing.animate()
.setStartDelay(500)
.setDuration(300)
.alpha(0F)
.setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
focusRing.setVisibility(View.INVISIBLE);
}
// The rest of AnimatorListener's methods.
});
}
I have two layouts (green on top, red on bottom) in a vertical LinearLayout (parent) looking similar to this:
.
When focus goes from the green to red, I would like the green to slide up off the screen and have the red simultaneously slide up with it and fill the whole screen. And when focus moves from red back up I want the green to slide back into the screen and return to the original configuration. I have tried looking at many other questions but none have had the solution I need. I tried just changing visibility between gone and visible but I want it to be a smooth animation. I've tried using parentLayout.animate().translationY(greenLayout.getHeight()) on the outer LinearLayout and that does give the animation I want but then the red does not expand to fill the screen, like this:
.
I know this question is similar to this one but that question is really old and only had one answer which didn't work for me.
My solution has a lot of different pieces, so I'll start with the full XML and java code, and then talk about the important bits:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="#+id/green"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#0f0" />
<View
android:id="#+id/red"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#f00"/>
</LinearLayout>
In the XML, the only really important part is that the red view uses a height of 0dp and weight of 1. This means it takes up all extra vertical space, which will be important when we get rid of the green view.
public class MainActivity extends AppCompatActivity {
private int originalHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View green = findViewById(R.id.green);
final View red = findViewById(R.id.red);
green.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
green.getViewTreeObserver().removeGlobalOnLayoutListener(this);
originalHeight = green.getHeight();
}
});
green.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, originalHeight, 0);
}
});
red.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, 0, originalHeight);
}
});
}
private void animateHeightOfView(final View view, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int height = (int) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = height;
view.setLayoutParams(params);
}
});
animator.start();
}
}
In the Java, the two main parts are the ViewTreeObserver.OnGlobalLayoutListener and the animateHeightOfView() method.
The OnGlobalLayoutListener exists to capture the green view's original height. We have to use a listener to do this instead of just writing originalHeight = green.getHeight() inside onCreate() because the green view isn't actually laid out at that point, so getHeight() would return 0 if we tried that.
The animateHeightOfView() method leverages the ValueAnimator class to animate the height of whatever view you pass to it. Since there's no direct setter for a view's height, we can't use simpler methods like .animate(). We set up the ValueAnimator to produce int values on every frame, and then we use a ValueAnimator.AnimatorUpdateListener to modify the view's LayoutParams to set the height.
Feel free to play with it. I'm using click listeners to trigger the animation, and you mentioned focus, but you should be able to call animateHeightOfView() in a different way if it suits you.
What I'm trying to achieve
I have a group of buttons in a GridLayout. One of the buttons toggles the function of the other buttons between rounding up and rounding down.
I am trying to implement an animation that changes the color of the buttons in a ripple like effect. The circular reveal animation is the effect I want where the new colour spreads from the touch point on the toggling button to the rest of the other buttons. I only want the animation to grow outward from the touch point in both cases, switching from rounding up to rounding down and back.
Problem
The animation is only working in the first instance from rounding up to rounding down (my app starts with rounding up as the default). There is no animation when switching from rounding down back to rounding up; all the buttons just changes in colour and the toggle button changes icon.
This is my first app so I am having difficulty trying to work out where I've gone wrong.
My methodology and code
In my activity_main.xml layout file, I've created two sets of GridLayouts (each with child buttons) and layered them one on top of the other by wrapping both in a FrameLayout. One set is given a different colour to the other set; I also have a different icon for the toggling button for each set to differentiate between the two rounding method. I've set the visibility attribute on the second GridLayout parent to invisible.
<FrameLayout
<GridLayout
android:id="#+id/roundUp"
<ImageButton
android:id="#+id/buttonU1"
... />
<Button ... />
<Button ... />
</GridLayout>
<GridLayout
android:id="#+id/roundDown"
android:visibility="invisible"
<ImageButton
android:id="#+id/buttonD1"
... />
<Button ... />
<Button ... />
</GridLayout>
</FrameLayout>
In my MainActivity.java file:
import ...
boolean roundingMode;
public class MainActivity extends AppCompatActivity {
GridLayout roundUp, roundDown;
ImageButton buttonU1, buttonD1;
Button ...
#Override;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
roundUp = (GridLayout) findViewById(R.id.roundUp);
buttonU1 = (ImageButton) findViewById(R.id.buttonU1);
roundDown = (GridLayout) findViewById(R.id.roundDown);
buttonD1 = (ImageButton) findViewById(R.id.buttonD1);
roundingMode = true;
buttonU1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
buttonCircularRevealAnim(roundDown);
roundingMode = false;
}
});
buttonD1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
buttonCircularRevealAnim(roundUp);
roundingMode = true;
}
});
}
public void buttonCircularRevealAnim(View view) {
int originX = roundUp.getWidth() / 2;
int originY = roundUp.getHeight() / 2;
float finalRadius = (float) Math.hypot(originX,originY);
Animator anim;
if (roundingMode) {
anim = ViewAnimationUtils.createCircularReveal(roundDown, originX, originY, 0, finalRadius);
roundDown.setVisibility(View.VISIBLE);
anim.start();
anim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
roundUp.setVisibility(View.INVISIBLE);
}
});
} else {
anim = ViewAnimationUtils.createCircularReveal(roundUp, originX, originY, 0, finalRadius);
roundUp.setVisibility(View.VISIBLE);
anim.start();
anim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
roundDown.setVisibility(View.INVISIBLE);
}
});
}
}
Ignore the fact that I currently have the origin of the animation in the center of the GridLayout. I just want to get the basics working first.
I think the problem lies in the logic and/or the set up of the animation?
I've tried moving the originX, originY and finalRadius calculations into the if statement and using the roundUp (for roundingMode true) and roundDown GridLayouts (for roundingMode false) but that didn't work - so I figure it doesn't matter if it is invisible (in the scenario where you're switching from rounding down to rounding up).
Eventually understood that the circular reveal animation works to reveal an overlapping view that is initially invisible. So in order to use it to change the colour of a view back and forth you have to apply the new colour (from the revealed view) to the view underneath after the animation has ended and then make the revealed view invisible again with the other colour applied to it.
My original code was trying to reveal the view underneath which is of course impossible.
I found the following page very helpful:
http://myhexaville.com/2016/12/19/keep-your-app-material/
So, I have a Layout that contains a Button and an ImageView. When you press the button the ImageView should slide out from the button like I just pulled down a rolldown curtain (bushing other views below it down). Basically what the image below show. When you press the button again the ImageView should, unlike the gif, smoothly animates up again.
.
Using this SO question I've managed to animate the height from 0 to full size but in the wrong direction. I set the scaleType to "Matrix" and the default behaviour when setting the height is to show the part from the top down to [height].
For the animation I'll need the opposite. So if I would set the height to 50dp it would show the bottom 50dp. Then I can move the ImageView down at the same time it's being revealed, thus giving the rolldown curtain effect.
I've looked throught all the different layout and view options and found nothing that seems to do this. So I'm guessing I need to specify the transformation matrix. I looked through the android.graphics.Matrix class but it's a little but too complicated for me. I simply have no idea how to use it.
If there is another, easier, way to do this then that would be fantastic but if not then I really need help with the matrix.
I'm also including the code here:
The Rolldown View XML
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/sliding_accordion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/acc_image"
android:contentDescription="#string/accord"
android:scaleType="matrix"
android:layout_below="#+id/acc_button"
android:layout_marginTop="-10dp"
android:layout_centerHorizontal="true"/>
<Button
android:id="#+id/acc_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
The implementation in code.
(Note, the MyCustomAnimation class is a copy-paste version of the class found here)
//Called from all constructors
private void create()
{
final Context context = getContext();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
RelativeLayout layout = (RelativeLayout) inflater.inflate(R.layout.widget_accordion, this, false);
final Button theButton = (Button) layout.findViewById(R.id.topic_button);
final ImageView accordionView = (ImageView) layout.findViewById(R.id.sliding_accordion);
accordionView.setVisibility(INVISIBLE);
theButton.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
if (accordionView.getVisibility() == View.VISIBLE)
{
MyCustomAnimation a = new MyCustomAnimation(accordionView, 1000, MyCustomAnimation.COLLAPSE);
height = a.getHeight();
accordionView.startAnimation(a);
}
else
{
MyCustomAnimation a = new MyCustomAnimation(accordionView, 1000, MyCustomAnimation.EXPAND);
a.setHeight(height);
accordionView.startAnimation(a);
}
}
});
this.addView(layout);
}
This took a long time perfect. But I managed to do it after a lot of experimenting.
I animate the margins of the drawer but because of the unexpected behavior of negative margins the button that opens the drawer can not be positioned on top.
When the drawer is closed the XML looks like so:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/accordion"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.animationtest.drawer.Drawer
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/topic_drawer"
android:layout_centerHorizontal="true"
android:layout_marginTop="0dp"
android:visibility="invisible"/>
<com.animationtest.drawer.Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/topic_btn"
android:layout_marginTop="58dp"/>
</RelativeLayout>
Then when the button is pressed the top_margin of the drawer is increased until it has come to whatever position is needed (in this case drawerHeight - someOffset).
I used android.view.animation.Animation to animate the widget my applyTransformation function looks something like this (Note that mLayoutParams are the drawer params):
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int valueDifference = Math.abs(startValue - endValue);
float valueChange = interpolatedTime * valueDifference;
if(currentState.equals(State.COLLAPSED)) {
// is closed and I want to open it
mLayoutParams.topMargin = Math.round(interpolatedTime * valueDifference);
}
else {
// is opened and I want to close it
mLayoutParams.topMargin = valueDifference - Math.round(interpolatedTime * valueDifference);
}
drawerView.requestLayout(); //this is my drawer
}
Finally, to hide the top of the drawer as it moves, I overrode my DrawerView's dispatchDraw method to looks like so:
#Override
protected void dispatchDraw(Canvas canvas) {
float height = getHeight();
float top = height - ((LayoutParams) getLayoutParams()).topMargin;
Path path = new Path();
RectF rectF = new RectF(0.0f, top, getWidth(), height);
path.addRoundRect(rectF, 0.0f, 0.0f, Path.Direction.CW);
canvas.clipPath(path);
super.dispatchDraw(canvas);
}
One final note:
Because of the Button's position one would need to set the widgets margin as a negative number for it to align correctly in a list or layout. In this case it would have to be -58dp.
i have two image views which translates on click.
the animation works properly for one view but for second image view , my animation is not according to coordinates provided.
when i click top image view (img1) it animates properly toward bottom image view (img2) . But when i click the bottom image view, it animates from somewhere down and move towards image view 2 initial position only. though the expected behaviour is, it should animate from its position to top image view (img1) initial position.
My xml is
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/letter_f"
android:layout_alignParentTop="true"
android:id="#+id/imgview1"
android:background="#drawable/chart"/>
<ImageView android:layout_height="100dp"
android:layout_width="100dp"
android:id="#+id/imgview2"
android:src="#drawable/letter_g"
android:background="#drawable/chart"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
and my java class file is
public class AnimationDemo extends Activity implements OnClickListener
{
private ImageView img1;
private ImageView img2;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
img1 = (ImageView)findViewById(R.id.imgview1);
img2 = (ImageView)findViewById(R.id.imgview2);
img1.setOnClickListener(this);
img2.setOnClickListener(this);
}
#Override
public void onClick(View arg0)
{
int x1,y1; // Coordinates of first image view
int x2,y2; //Coordinates of second image view
ImageView img = (ImageView)arg0;
x1 = img1.getLeft();
y1 = img1.getTop();
x2 = img2.getLeft();
y2 = img2.getTop();
TranslateAnimation slide;
if(arg0 == img1)
{
//translate from img view 1 to img view 2
slide = new TranslateAnimation(Animation.ABSOLUTE,x1,Animation.ABSOLUTE, x2,Animation.ABSOLUTE, y1,Animation.ABSOLUTE,y2 );
}
else
{
// translate from img view 2 to img view 1
slide = new TranslateAnimation(Animation.ABSOLUTE,x2,Animation.ABSOLUTE, x1,Animation.ABSOLUTE, y2,Animation.ABSOLUTE,y1);
}
slide.setDuration(1000);
slide.setFillAfter(true);
img.startAnimation(slide);
}
}
Your troubles are due to your locations. I believe when animations are moved with absolute pixels it is relative to itself. So on your second animation you were in essence moving it from x2=220 to x1=0, and y2=419 to y1=0. So it was moving from (currentX+220, currentY+419) to (currentX +0, currentY +0) which = itself
To solve this instance simply negate and switch the values of the second slide declaration like so:
TranslateAnimation slide;
if(arg0 == img1)
{
//translate from img view 1 to img view 2
slide = new TranslateAnimation(Animation.ABSOLUTE,x1,Animation.ABSOLUTE, x2,Animation.ABSOLUTE, y1,Animation.ABSOLUTE,y2 );
}
else
{
// translate from img view 2 to img view 1
// slide = new TranslateAnimation(Animation.ABSOLUTE,x2,Animation.ABSOLUTE, x1,Animation.ABSOLUTE,y2,Animation.ABSOLUTE,y1);
slide = new TranslateAnimation(Animation.ABSOLUTE,0,Animation.ABSOLUTE, (-x2),Animation.ABSOLUTE,0,Animation.ABSOLUTE, (-y2));
}
This only happens because your top left sprite is at 0,0 though. You have to seriously rethink how you're moving your sprites around. Just remember, the TranslateAnimation moves them relative to their current positions, basically setting the sprites original location to (0,0).
Could be wrong, but hope it helps. It worked for me...
Sorry it took so long to get back to you, I lost your post and couldn't find it again for some reason. Glad you had commented earlier!