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.
Related
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);
}
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/
I have a TextView inside a LinearLayout. My goal is to animate the TextView height change when its text changes and need more or less lines.
I split the work this way :
Fade out the old text
Animate the TextView new height (and the parent LinearLayout)
Fade in the new text
The fade in / fade out part is easy, but I struggle for the height change animation.
Here's my simplified layout :
<LinearLayout
android:id="#+id/fragment_tooltip_tooltip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/tooltip_blue_bg"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="#+id/fragment_tooltip_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
And here is what I tried :
final int currentHeight = tvMessage.getHeight();
tvMessage.setText(toTooltip.getMessage());
tvMessage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
TooltipFragment.this.tvMessage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
final int newHeight = tvMessage.getHeight();
ResizeAnimation resizeAnimation = new ResizeAnimation(tvMessage, tvMessage.getWidth(), tvMessage.getWidth(),
currentHeight, newHeight);
resizeAnimation.setDuration(ANIM_DURATION_TRANSITION_TEXT_RESIZE);
resizeAnimation.setInterpolator(new FastOutSlowInInterpolator());
resizeAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
// No-op
}
#Override
public void onAnimationEnd(Animation animation) {
// Finally we show the new content
...
}
#Override
public void onAnimationRepeat(Animation animation) {
// No-op
}
});
tvMessage.startAnimation(resizeAnimation);
}
});
The problem with this solution is that the tvMessage.setText(toTooltip.getMessage()); line used to measure the new height expand immediately the TextView and LinearLayout, then the animation resize the view to its previous size before applying the resize the animation, producing an ugly visual effect.
I believe a TextSwitcher will not animate the height change and so is not a solution, but I didn't tried it.
The solution for my problem was to use a dummy TextView and measure it with the new text instead of using the actual TextView. This way my TextView is not resized before the animation, and I just have to set the text at the end of the animation.
See this answer for details : Getting height of text view before rendering to layout
You could use a handler that runs recursively for this, looks a bit cleaner too. So, for example, use this:
Call the function startTextAnimation()
Have a Handler animHandler = new Handler(); in your onCreate()
Then use this function:
public void startTextAnimation() {
textView.setHeight(textView.getHeight() + 1);
if (textView.getHeight < *whatheightyouwant*) {
aninHandler.postDelayed(new Runnable() {
#Override
public void run() {
startTextAnimation();
}
}, 200);
}
}
You can set parameters accordingly, it should do the job. Hope it helps.
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 an imageButton inside a RelativeLayaut.
My imageButton is 300x350px and positioned outside the screen (on top) -300px onClick the button go down 300px and go back to the initial position when click again. The effect is like a popup window.
I could obtain this working code.
XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#drawable/pag1" android:id="#+id/relativeLayout1"
android:drawingCacheQuality="high" android:layout_width="1024px"
android:layout_height="600px">
<ImageButton android:id="#+id/imageButton8"
android:layout_width="300px"
android:layout_height="350px"
android:layout_marginLeft="720px"
android:background="#drawable/popup"
android:layout_marginTop="-300px">
</ImageButton>
</RelativeLayout>
CODE
import android.widget.RelativeLayout.LayoutParams;
...
//activity declarations
protected static final int WIDTH = 300;
protected static final int HEIGHT = 350;
int count=0;
...
///click
final ImageButton pop=(ImageButton) findViewById (R.id.imageButton8);
pop.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
count++;
if (count==1){
LayoutParams lp = new LayoutParams(WIDTH,HEIGHT);
lp.setMargins(720, -20, 4, 0);
pop.setLayoutParams(lp);
}
else{
LayoutParams lp = new LayoutParams(WIDTH,HEIGHT);
lp.setMargins(720, -300, 4, 0);
pop.setLayoutParams(lp);
count=0;
}
}
});
NOW I wont to add a smooth transition to the final position. I think in a FOR cycle using a sleep function. Your HELP is welcome
I think you should start with deciding which platform you are targeting as there is a new animation framework in Honeycomb. Have a look at this article.
If however you are targeting pre-3.0 versions then the simplest way is to define your animation in anim.xml and load it in your activity by using android.view.animation.AnimationUtils. Then once you've set your new layout params on the view, simply call public void startAnimation (Animation animation) on the view and it will animate it for you. Two lines in Java and a few more in XML.
Take a look at Animation Resources too.