How to animate TextView height change - android

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.

Related

Android: LinearLayout slide off screen animation

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.

Circular reveal animation when switching between two views

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/

android - start animating a view immediately

i am making an android app and i am a newbie in development.
I created a startup animation for the app, using the app icon that is scaled from O% size to 100% size and rotated at the same time. This works allright. But i have a problem with it. In xml, the view visibility is set to gone initially. In code, i call image.setVisibility(View.VISIBLE) and then image.startAnimation(iconAnimation) . But the result is that i can see a flash of the ImageView before it starts animating, for like half a second or so. You can see the gif.
Gif:
see the gif
Can you help me with this? Thanks in advance.
My code:
runOnUiThread(new Runnable() {
#Override
public void run() {
// here
image.setVisibility(View.VISIBLE);
image.startAnimation(imageAnim);
//also found this somewhere, didn't help.
image.invalidate();
}
});
Layout xml:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="35sp"
android:text="Kytky"
android:layout_centerInParent="true"
android:id="#+id/welcome_text"
android:visibility="gone"/>
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:src="#mipmap/ic_launcher"
android:layout_centerInParent="true"
android:id="#+id/welcome_image"
android:visibility="gone"/>
You can use value animator instead, You can hack through the animation
imageView.setPivotX(viewCenterX);
imageView.setPivotY(viewCenterY);
imageView.setScaleX(0);
imageView.setScaleY(0);
final FloatEvaluator scaleEvaluator = new FloatEvaluator();
final FloatEvaluator rotationEvaluator = new FloatEvaluator();
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1f);
valueAnimator.setDuration(TimeUnit.SECONDS.toMillis(1));
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
float scale = scaleEvaluator.evaluate(fraction, 0f, 1f);
float rotation = rotationEvaluator.evaluate(fraction, 0f, 360f);
imageView.setScale(scale);
imageView.setRotation(rotation);
}
});
valueAnimator.start();
Start one more animator or use this same animator to slide the logo up. Using this animators you will have full control over your animation.
you can switch the view visibility when the animations starts
imageAnim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
image.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
image.startAnimation(imageAnim);
the blink happens because the view visibility far from starting the animations, so in the first frame it will have its real size, and after that starts the animation
You need to work with alpha as well...make it visible and set alpha to minimum value than set alpha gradually to maximum value with in handler.

CircularReveal animation doesn't work on first attempt

In android 5.0 i am trying to work with circular reveal animation
Problem
When i click on button to start reveal animation, on first click animation doesn't start
Second Click onwards it works normally
My Code
public class MainActivity extends ActionBarActivity {
Animator a;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View cardType = findViewById(R.id.cardtype);
cardType.setVisibility(View.GONE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
a = ViewAnimationUtils.createCircularReveal(cardType,
cardType.getWidth(),
cardType.getHeight(),
0,
cardType.getHeight() * 2)
.setDuration(2500);
a.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
cardType.setVisibility(View.VISIBLE);
}
});
a.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
cardType.setVisibility(View.GONE);
}
});
findViewById(R.id.icon_first_activity).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
a.start();
}
});
}
}}
I haven't tried your code, but I think you have a small ordering problem. I think you just need to set the cardType visible before you start the animation.
Edited to add:
... and you should be setting your button View.INVISIBLE, not View.GONE.
Here: This code works.
Edited once more to add:
Yes. Your problem is that you set the view GONE initially. That means it has 0 size. Then you use cardType.getHeight and cardType.getWidth as reveal coordinates. They are 0. You are going to want to set the view INVISIBLE, initially, and then use width/2 and height/2 as the center of the reveal.
Basically what others answers say, it's correct, but the problem is if you want visibility GONE (because your layout requires it GONE!) you have to set visibility INVISIBLE in the xml with height 0dp (and/or width 0dp as well) and programmatically set the correct LayoutParams even inside the click event it will work. For example my code:
...
expandButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//To not have empty scroll, the container is INVISIBLE with 0dp height.
//Otherwise the Reveal effect will not work at the first click.
//Here I set the parameters programmatically.
viewContainer.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
if (viewContainer.getVisibility() == View.VISIBLE) {
expandButton.animate().rotation(0f).setDuration(duration).start();
Utils.unReveal(viewContainer, 0, 0);
} else {
expandButton.animate().rotation(180f).setDuration(duration).start();
Utils.reveal(viewContainer, viewContainer.getWidth(), 0);
}
}
});
...
#TargetApi(VERSION_CODES.LOLLIPOP)
public static void reveal(final View view, int cx, int cy) {
if (!hasLollipop()) {
view.setVisibility(View.VISIBLE);
return;
}
//Get the final radius for the clipping circle
int finalRadius = Math.max(view.getWidth(), view.getHeight());
//Create the animator for this view (the start radius is zero)
Animator animator =
ViewAnimationUtils.createCircularReveal(view, cx, cy, 0, finalRadius);
//Make the view VISIBLE and start the animation
view.setVisibility(View.VISIBLE);
animator.start();
}
#TargetApi(VERSION_CODES.LOLLIPOP)
public static void unReveal(final View view, int cx, int cy) {
if (!hasLollipop()) {
view.setVisibility(View.GONE);
return;
}
//Get the initial radius for the clipping circle
int initialRadius = view.getWidth();
//Create the animation (the final radius is zero)
Animator animator =
ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius, 0);
//Make the view GONE when the animation is done
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.GONE);
}
});
//Start the animation
animator.start();
}
If you set only GONE in the xml, the first time will never work because height/width/x/y/etc.. are 0. Also, if you just set INVISIBLE before the call to the animation it will not work as well, but if you start with visibility INVISIBLE it will initialize the layout params.
what i did is, Like i have two view with same height,As we now visibility gone returns 0 {height and width} than i am giving visible view height every time and its work for me.
The solution is don't get values directly into code
Either put the animation code on click and the values outside onclick
or get the values from other activity
By values i mean cardType.getWidth() and cardType.getHeight()

clickable imageview location change with animation - android

I have an imageview that's included inside a RelativeLayout. When the imageview is clicked, I animate the entire RelativeLayout with a translate animation to move it down.
when I click the imageview again (in it's new location) it's supposed to move it back up, but it does not. However, if I click where the imageview started, it does move the entire "panel" back up. Why is the imageview not moving with the relativelayout...at least insofar as the clickability of it. The actual image is moving, but the spot where it's clickable is not.
here is my xml for the layout:
<RelativeLayout
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_width="fill_parent"
android:layout_height="120px"
android:layout_marginTop="-106px"
android:id="#+id/chatbox"
android:visibility="invisible">
<TextView
android:layout_width="fill_parent"
android:layout_height="106px"
android:background="#000000"
android:id="#+id/chattext" />
<ImageView
android:layout_width="20px"
android:layout_height="14px"
android:id="#+id/chatbubble"
android:layout_below="#id/chattext"
android:src="#drawable/chatbubble" />
</RelativeLayout>
Edit: I should add that I'm using Animation.setFillAfter(true) to hold the panel in place after the animation completes.
This is because animations actually do not affect views positions, they just draw them. So to handle click at new place you have to place something there (i.e. invisible FrameLayout). Or you can change your views margins on animation complete, so the views actually will move to the place.
I had the same problem and the best solution I found is to add an AnimationListener to your animation and move the view yourself in that listener as Konstantin Burov said :
animation.setFillAfter(false);
AnimationListener animListener = new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
RelativeLayout.LayoutParams relativeLayoutParams = (LayoutParams) yourView.getLayoutParams();
//To avoid the flicker
yourView.clearAnimation();
relativeLayoutParams.setMargins(0, 0, 0, Utils.dpToPx(activity, newPositionDp);
yourView.setLayoutParams(relativeLayoutParams);
}
};
animation.setAnimationListener(animListener);
The Utils.dpToPx may be useful too :
public final static int dpToPx(Context context, int dp) {
return ((int)((dp * context.getResources().getDisplayMetrics().density) + 0.5));
}

Categories

Resources