I need to animate a TextView when i click a button. The height of the TextView is wrap_content. This TextView is inside a RecyclerView row and i need to expand it from visibility gone to his real height with content. I used ValueAnimator.
private ValueAnimator slideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = tvAdditional.getLayoutParams();
layoutParams.height = value;
tvAdditional.setLayoutParams(layoutParams);
}
});
return animator;
}
private void expand(View v) {
v.setVisibility(View.VISIBLE);
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ValueAnimator mAnimator = slideAnimator(0, v.getMeasuredHeight());
mAnimator.start();
}
private void collapse(final View v) {
int finalHeight = v.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animator) {
v.setVisibility(View.GONE);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
mAnimator.start();
}
In debug i noticed that when i use getMeasuredHeight() method on view in expand, the value is always 76, also if i added items with more than one row.
P.s. i call expand and collapse inside the click listener of the row.
Screenshot:
SOLVED
I changed my expand method adding a listener in which i set the height to wrap_content at the end of the animation:
private void expand(final View v) {
v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
final int targetHeight = v.getMeasuredHeight();
ValueAnimator mAnimator = slideAnimator(0, targetHeight);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
v.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
v.requestLayout();
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
mAnimator.start();
}
When you have the Visibility as GONE, you cannot know it's measured height until it's actually drawn. In order to have that, you need to have GlobalLayoutListener on that TextView, so you can get it's measured height when it's drawn.
In your expand method, I see that you set the Visibility to Visible and then try to measure it, which won't be drawn immediately, so you cannot get the height.
So what you can do to get the actual height of the TextView if it's visibility is initially gone, first you should change the initial visibility to VISIBLE, second, set a Global Layout Listener and as third step, store the TextView's height in a global value after you measure it, and as the last step, you can set the Visibility of TextView as GONE.
This process is normally really quick, so you won't be able to see the TextView's visibility changing from VISIBLE to GONE with your eyes.
Related
I have a search bar that expands when icon is clicked in toolbar. Now, everything is cool when it is expanded, everything works fine, the thing that is not good is that the height isnt good when the search bar is collapsed. The toolbar upon collapsing search bar has bigger height than it had. Here is the code:
private void expand(View view) {
//set Visible
view.setVisibility(View.VISIBLE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(widthSpec, heightSpec);
ValueAnimator mAnimator = slideAnimator(0, view.getMeasuredHeight(), view);
mAnimator.start();
}
private ValueAnimator slideAnimator(int start, int end, final View view) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//Update Height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = value;
view.setLayoutParams(layoutParams);
}
});
return animator;
}
private void collapse(final View view) {
int finalHeight = view.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0, view);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animator) {
//Height=0, but it set visibility to GONE
view.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
mAnimator.start();
}
Everything is good here, check where you call method if you are changing background or any other property of the toolbar.
I have a layout that I want to expand after button click, as shown below:
The problem is I need to use Animation, so I decided to use View.animate.translationY(). Here is my code:
private void showBottomThreeLines(boolean show){
if(show)
mShiftContainer.animate().translationY(0);
else
mShiftContainer.animate().translationY(-(mFifthLineContainer.getHeight() * 3));
}
However, this is what I get after testing:
The current height is still the same as the previous height! The view's height is using MATCH_PARENT. I even tried to change it to 1000dp, but it still has the same height. How do I update view's height during translationY() animation?
After doing some research, I find out that defining view height after its translation will not increase its height at all. It appears that the sum of the entire view's height CANNOT go exceed its parent layout's height. Which means, if you set parent layout's height as MATCH_PARENT and your screen size is 960 dp, your child view's maximum height will be 960 dp, even if you define its height, e.g. android:layout_height="1200dp".
Therefore, I decided to dynamically re-size parent layout's height, and make the footer layout's height MATCH_PARENT. By default, my parent layout's height is MATCH_PARENT, but I call below method on onCreateView():
private void adjustParentHeight(){
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
ViewGroup.LayoutParams params = mView.getLayoutParams();
mFifthLineContainer.measure(0, 0);
params.height = metrics.heightPixels + (mFifthLineContainer.getMeasuredHeight() * 3);
mView.setLayoutParams(params);
}
This will make my footer layout become off-screen. Then I tried to use View.animate().translationY(), but then I got another problem! There is a bug in Android animation that causes flicker when you call View.setY() on onAnimationEnd(). It seems the cause is onAnimationEnd() is being called before the animation truly ends. Below are references that I use to solve this problem:
Android Animation Flicker
Android Flicker when using Animation and onAnimationEnd Listener
Therefore, I changed my showBottomThreeLines() method:
private void showBottomThreeLines(boolean show){
if(show){
TranslateAnimation translateAnimation = new TranslateAnimation(0, 0, -(mFifthLineContainer.getHeight() * 3), 0);
translateAnimation.setDuration(300);
translateAnimation.setFillAfter(true);
translateAnimation.setFillEnabled(true);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
mShiftContainer.setY(mShiftContainer.getY() + mFifthLineContainer.getHeight() * 3);
}
#Override
public void onAnimationEnd(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
mShiftContainer.startAnimation(translateAnimation);
} else{
TranslateAnimation translateAnimation = new TranslateAnimation(0, 0, mFifthLineContainer.getHeight() * 3, 0);
translateAnimation.setDuration(300);
translateAnimation.setFillAfter(true);
translateAnimation.setFillEnabled(true);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
mShiftContainer.setY(mFifthLineContainer.getY());
}
#Override
public void onAnimationEnd(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
mShiftContainer.startAnimation(translateAnimation);
}
}
Use this:
private ActionMode mActionMode;
...
private void expandView(View summary, int height, final boolean isSearch) {
if (isSearch) summary.setVisibility(View.VISIBLE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.EXACTLY);
summary.measure(widthSpec, height);
Animator animator = slideAnimator(summary.getHeight(), height, summary);
animator.start();
}
private void collapseView(final View summary, int height, final boolean isSearch) {
int finalHeight = summary.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, height, summary);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.EXACTLY);
summary.measure(widthSpec, height);
Animator animator = slideAnimator(summary.getHeight(), height, summary);
animator.start();
mAnimator.start();
}
/**
* Slide animation
*
* #param start start animation from position
* #param end end animation to position
* #param summary view to animate
* #return valueAnimator
*/
private ValueAnimator slideAnimator(int start, int end, final View summary) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//Update Height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = summary.getLayoutParams();
layoutParams.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, value, getResources().getDisplayMetrics());//value;
summary.setLayoutParams(layoutParams);
}
});
return animator;
}
private ActionMode.Callback mActionModeCallBack = new ActionMode.Callback() {
//Contextual action menu. Shows different options in action bar when a list item is long clicked!
#Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.contextual_menu_options, menu);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
mActionMode.finish();
return true;
}
#Override
public void onDestroyActionMode(ActionMode actionMode) {
mActionMode = null;
}
};
Hope this will help you!
Problem
I'm trying to animate the ListView header height. I can get the animation right, but after the animation completes the ListView flickers.
Tried and failed
Use AnimationSet.setFillAfter() without changing the layout params. The animation works fine, but when you start scrolling the list, header jumps back to original position.
Use AnimationSet.setFillAfter() with new layout params applied at onAnimationEnd(). After the animation ends the header jumps to twice the required height (animated height plus the height set in layout params). When you start scrolling the list, header snaps to the required height.
Code
if (mSearchAdapter.getCount() > 0 && mListView.getChildAt(0) == mHeaderPlaceholder) {
Log.i(TAG, "Animating list view to make room for info bar");
AnimationSet slideAnimation = new AnimationSet(true);
TranslateAnimation translate = new TranslateAnimation(0, 0, 0, newHeight);
translate.setDuration(mInfoBarAnimationDuration);
translate.setInterpolator(new DecelerateInterpolator());
slideAnimation.addAnimation(translate);
slideAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
isAnimatingViewTransition = true;
}
#Override
public void onAnimationEnd(Animation animation) {
isAnimatingViewTransition = false;
final AbsListView.LayoutParams layoutParams = (AbsListView.LayoutParams) mHeaderPlaceholder.getLayoutParams();
layoutParams.height = newHeight;
mHeaderPlaceholder.setLayoutParams(layoutParams);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
mListView.startAnimation(slideAnimation);
} else {
Log.i(TAG, "Adjusting list view header to make room for info bar");
mHeaderPlaceholder.getLayoutParams().height = newHeight;
}
I think the flicker can be avoided by listening for and overriding the events onPreDraw() or onGlobalLayout() of the ViewTreeObserver of the ListView. But I don't know exactly how I can achieve it.
Any help is much appreciated!
Instead of animating ListView header I opted to used ValueAnimator to achieve the same effect. Here is code:
if (mSearchAdapter.getCount() > 0 && mListView.getChildAt(0) == mHeaderPlaceholder) {
ValueAnimator mSlideListViewAnimator = ObjectAnimator.ofInt(from, to);
mSlideListViewAnimator.setDuration(mInfoBarAnimationDuration);
mSlideListViewAnimator.setInterpolator(new DecelerateInterpolator());
mSlideListViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer animatedYValue= (Integer) animation.getAnimatedValue();
final AbsListView.LayoutParams layoutParams = (AbsListView.LayoutParams) mHeaderPlaceholder.getLayoutParams();
layoutParams.height = animatedYValue;
mHeaderPlaceholder.requestLayout();
}
});
mSlideListViewAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
isAnimatingViewTransition = true;
}
#Override
public void onAnimationEnd(Animator animation) {
isAnimatingViewTransition = false;
}
});
mSlideListViewAnimator.start();
} else {
Log.i(TAG, "Adjusting list view header to make room for info bar");
mHeaderPlaceholder.getLayoutParams().height = newHeight;
}
I want to implement zoom feature on an ImageButton by Property Animation. For example, when I click the button, it will zoom out. And when I click it again, it will zoom in.
Here is part of my code:
OnClickListener clickPlayButtonHandler = new OnClickListener() {
#Override
public void onClick(View v) {
final ImageButton clickedButton = (ImageButton) v;
if((Boolean) v.getTag()) {
// zoom out
clickedButton.animate().setInterpolator(new AnticipateInterpolator()).setDuration(500).scaleXBy(-0.4f).scaleYBy(-0.4f).setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
clickedButton.setImageResource(R.drawable.bg_pause);
System.out.println(clickedButton.getWidth()); // output the width of the button for checking
}
#Override
public void onAnimationEnd(Animator animation) {
clickedButton.setTag(false);
int d = clickedButton.getWidth();
System.out.println(clickedButton.getWidth());// output the width of the button for checking
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) { }
});
} else {
// process zoom in
}
}
};
I printed the width of the button before the animation start and the animation finished. However, I found they were the same. I thought when the zoom out animation finished the button width should be small than before. But it didn't.
Could not change view size by ViewPropertyAnimator?
There won't be changes in clickedButton.getWidth(), because the width of a view is not affected by scaling. You can think of getWidth() as a way to get unscaled width of a view. To change the width of a View requires a new measure/layout pass.
ViewPropertyAnimator doesn't change View's width/height or anything that could potentially trigger another layout pass. This is simply because layout passes are expensive and therefore could cause frame skipping, which is the last thing we want see during an Animation.
If you need to get scaled width of the button, you can do getScaleX() * clickedButton.getWidth()
Try the ObjectAnimator:
ObjectAnimator xAnimator =
ObjectAnimator.ofFloat(clickedButton, "scaleX", 1.0f, -0.4f);
ObjectAnimator yAnimator =
ObjectAnimator.ofFloat(clickedButton, "scaleY", 1.0f, -0.4f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(500);
animatorSet.playTogether(xAnimator, yAnimator);
animatorSet.setInterpolator(new AnticipateInterpolator());
animatorSet.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
clickedButton.setImageResource(R.drawable.bg_pause);
System.out.println(clickedButton.getWidth());
}
#Override
public void onAnimationEnd(Animator animation) {
clickedButton.setTag(false);
int d = clickedButton.getWidth();
System.out.println(clickedButton.getWidth());
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animatorSet.start();
I am trying to create a padding left animation in RelativeLayout. I wrote code which can animate left in click listener.
This is my code, but it is not working completely.
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
holder = (UserHolder) view.getTag();
layoutParams = (RelativeLayout.LayoutParams) holder.layoutmain
.getLayoutParams();
if (holder.layout.getVisibility() != View.VISIBLE) {
ValueAnimator varl = ValueAnimator.ofInt(-170);
varl.setDuration(1000);
varl.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
layoutParams.setMargins(
(Integer) animation.getAnimatedValue(), 0,
0, 0);
holder.layoutmain.setLayoutParams(layoutParams);
holder.layout.setVisibility(View.VISIBLE);
}
});
varl.start();
}
else
{
ValueAnimator varl = ValueAnimator.ofInt(0);
varl.setDuration(1000);
varl.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(
ValueAnimator animation) {
layoutParams.setMargins(0, 0,
(Integer) animation.getAnimatedValue(),
0);
// lp.setMargins(left, top, right, bottom)
holder.layoutmain.setLayoutParams(layoutParams);
holder.layout.setVisibility(View.INVISIBLE);
}
});
varl.start();
}
}
});
The first time, it works (I can animate padding left), but the second time I click the animation is not working. What am I doing wrong?
Through ValueAnimator class you can update margin or padding of any with animation. Please go through the following code :
ValueAnimator animator = ValueAnimator.ofInt(0,50);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator){
frameLayout.setPadding(0, (Integer) valueAnimator.getAnimatedValue(),0,0);
}
});
animator.setInterpolator(new DecelerateInterpolator(2));
animator.start();
Let me know in case of more explanation in comments.
You should try using ValueAnimator.ofInt(fromInt,toInt)
//first
ValueAnimator.ofInt(-70,0)
//second
ValueAnimator.ofInt(0,-70)
Otherwise the value animator doesn't know from or to which value it should animate.