Anyway to programmatically animate layout weight property of linear layout - android

I have two views in a linear layout, I programmatically change their layout_weight property. Is there a way I could animate this change in weight so when the weight is changed views slides towards a new size?

You can simply use ObjectAnimator.
ObjectAnimator anim = ObjectAnimator.ofFloat(
viewToAnimate,
"weight",
startValue,
endValue);
anim.setDuration(2500);
anim.start();
The one problem is that View class has no setWeight() method (which is required by ObjectAnimator). To address this I wrote simple wrapper which helps archive view weight animation.
public class ViewWeightAnimationWrapper {
private View view;
public ViewWeightAnimationWrapper(View view) {
if (view.getLayoutParams() instanceof LinearLayout.LayoutParams) {
this.view = view;
} else {
throw new IllegalArgumentException("The view should have LinearLayout as parent");
}
}
public void setWeight(float weight) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
params.weight = weight;
view.getParent().requestLayout();
}
public float getWeight() {
return ((LinearLayout.LayoutParams) view.getLayoutParams()).weight;
}
}
Use it in this way:
ViewWeightAnimationWrapper animationWrapper = new ViewWeightAnimationWrapper(view);
ObjectAnimator anim = ObjectAnimator.ofFloat(animationWrapper,
"weight",
animationWrapper.getWeight(),
weight);
anim.setDuration(2500);
anim.start();

I have been looking at this as well. Eventually I solved it by animating the weightsum property of the parent, which works very nice if you have two views in a LinearLayout.
see:
Animating weightSum property using ObjectAnimator
In the example below, if you animate the weightSum from 1.0 to 2.0, Screen 2 will animate nicely into view.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/dual_pane"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
android:weightSum="1.0">
<!-- Screen 1 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#ff0000"
android:layout_weight="1">
</LinearLayout>
<!-- Screen 2 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#ff6600"
android:layout_weight="1">
</LinearLayout>
</LinearLayout>

Note: I am not sure that this is the best way, but I tried it and it's working fine
Simply using ValueAnimator
ValueAnimator m1 = ValueAnimator.ofFloat(0.2f, 0.5f); //fromWeight, toWeight
m1.setDuration(400);
m1.setStartDelay(100); //Optional Delay
m1.setInterpolator(new LinearInterpolator());
m1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
((LinearLayout.LayoutParams) viewToAnimate.getLayoutParams()).weight = (float) animation.getAnimatedValue();
viewToAnimate.requestLayout();
}
});
m1.start();
More About ValueAnimator

Another way is to use old Animation class, as described in https://stackoverflow.com/a/20334557/2914140. In this case you can simultaneously change weights of several Views.
private static class ExpandAnimation extends Animation {
private final View[] views;
private final float startWeight;
private final float deltaWeight;
ExpandAnimation(View[] views, float startWeight, float endWeight) {
this.views = views;
this.startWeight = startWeight;
this.deltaWeight = endWeight - startWeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float weight = startWeight + (deltaWeight * interpolatedTime);
for (View view : views) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) view.getLayoutParams();
lp.weight = weight;
view.setLayoutParams(lp);
}
views[0].getParent().requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}

All of the answers above weren't working for me (they would simply "snap" and not animate), but after I added weight_sum="1" to the parent layout, it started working. Just in case someone else comes up with the same issue.

Related

Expanding animation on view with Nine-Patch as background

I'm trying to create an animation that expands a View from top to bottom.
Like this:
I can't use a ScaleAnimation with pivotY set to 0 because this stretches the Nine-Patch like a normal Bitmap. I need to modify the height of my View.
My current solution: (test is the View reference)
ValueAnimator anim = ValueAnimator.ofInt(test.getMeasuredHeight(), 800);
anim.setInterpolator(new FastOutSlowInInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams params = test.getLayoutParams();
params.height = (int) animation.getAnimatedValue();
test.setLayoutParams(params);
}
});
anim.setDuration(250).start();
This works perfectly, but it expands the view to both sides:
Does anyone know a way to create such animation? Any help is appreciated.
A solution is to keep your implementation but limit the expansion to bottom by having an element above that won't let your view grow up:
Example:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
...
android:layout_height="100dp"/> //assuming your view starts 100dps from top
<YourView
...
.../>
</LinearLayout>
Since your container is a LinearLayout and the first View(FrameLayout) is 100dp height and above the view you wish to expand, it won't expand it's Height up because it can't overlap an element of the same hierarchy, so it will expand down
It also works if you use RelativeLayout by adding android:below="#id/framelayout_id" to YourView which will also guarantee that YourView doesn't expand up
Add an extra variable that holds initial position on Y axis.
expands top
public void expandTop(final View test) {
final float bottomAnchor = test.getY() + test.getHeight();
final ValueAnimator anim = ValueAnimator.ofInt(test.getMeasuredHeight(), 800);
anim.setInterpolator(new FastOutSlowInInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams params = test.getLayoutParams();
params.height = (int) animation.getAnimatedValue();
test.setY(bottomAnchor - (int) animation.getAnimatedValue());
test.setLayoutParams(params);
}
});
anim.setDuration(2500).start();
}
expandBottom
public void expandBottom(final View test) {
final float topAnchor = test.getY();
final ValueAnimator anim = ValueAnimator.ofInt(test.getMeasuredHeight(), 800);
anim.setInterpolator(new FastOutSlowInInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams params = test.getLayoutParams();
params.height = (int) animation.getAnimatedValue();
test.setY(topAnchor);
test.setLayoutParams(params);
}
});
anim.setDuration(2500).start();
}

Android 7.0 Button ripple effect not updating with button layout changes

I am making a productivity app with events that are represented as buttons holding basic information about the event.
The button is contained in a RelativeLayout alongside TextViews, an options menu and a switch. The parent layout's height is set to WRAP_CONTENT. It looks like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:id="#+id/event_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="#+id/relativeLayout"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="2dp"
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:minHeight="130dp"
android:elevation="0dp"
android:textSize="#dimen/event_item_title_fontSize"
android:visibility="visible"
/>
<include
android:id="#+id/relativeLayout"
layout="#layout/_event_layout_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</include> </RelativeLayout>
You can click the button to expand it and show additional information. The expand animation is achieved using a custom animation which updates the button's getLayoutParams().height and calls getParent().requestLayout() every time applyTransformation(...) is called. This works well on an API level 19 (KitKat) device.
Animation code:
//The current state of the button which gets updated on animation finish
boolean expanded = false;
#Override
public void onClick(final View v) {
//Getting references to views
final RelativeLayout layout = (RelativeLayout) button.getParent();
final View separator = layout.findViewById(R.id.separator), list =
layout.findViewById(R.id.notes_list);
final Button button = (Button) v;
final RelativeLayout content = (RelativeLayout) layout.findViewById(R.id.relativeLayout);
final RelativeLayout.LayoutParams lpl = (RelativeLayout.LayoutParams) list.getLayoutParams(),
lpsw = (RelativeLayout.LayoutParams) layout.findViewById(R.id.event_switch).getLayoutParams(),
lp = (RelativeLayout.LayoutParams) button.getLayoutParams();
//Finished getting references to views
final int startHeight = lp.height = button.getHeight();
//Prevents button from automatically resizing to fit its parent when requestLayout is called
lp.removeRule(RelativeLayout.ALIGN_BOTTOM);
layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
layout.removeOnLayoutChangeListener(this);
final int goalHeight = GetExpectedHeight(expanded);
Animation animation = new Animation() {
float alpha1 = (expanded ? startHeight : goalHeight) - separator.getTop(),
alpha2 = (expanded ? startHeight : goalHeight) - list.getTop();
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime < 1) {
//Updates the button's height
lp.height = (int) (startHeight + (goalHeight - startHeight) * interpolatedTime);
separator.setAlpha((lp.height - separator.getTop()) / alpha1);
list.setAlpha((lp.height - list.getTop()) / alpha2);
list.setScaleX(list.getAlpha());
list.setScaleY(list.getAlpha());
layout.requestLayout();
return;
}
//Animation finish
if (expanded) {
separator.setVisibility(View.GONE);
list.setVisibility(View.GONE);
}
lp.height = lp.MATCH_PARENT;
lp.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.relativeLayout);
//A custom method that basically requests layout for whole activity
instance.get().Invalidate();
expanded = !expanded;
this.cancel();
}
};
animation.setInterpolator(new DecelerateInterpolator());
animation.setDuration(400);
layout.startAnimation(animation);
}
});
float alpha = expanded ? 1 : 0;
separator.setAlpha(alpha);
list.setAlpha(alpha);
//Kicks off the onLayoutChange method above
if (!expanded) {
separator.setVisibility(View.VISIBLE);
list.setVisibility(View.VISIBLE);
}
else
layout.requestLayout();
}
However, on an Android 7.0 Nougat (API 24), there is an accompanying ripple effect which overlays the button with a rectangle that doesn't update its height to match the button's height during the animation. I don't know what it looks like on other Android versions because those are the only two devices I can test on. Is there a way to update the ripple effect's rectangle to scale with the button? I would prefer to keep the ripple effect if at all possible. So far I have tried calling performClick() on the button each frame of the animation but to no avail.
Here is a screenshot of the button during expand animation:
And a video of the animation:
https://streamable.com/vehf3

Animate layout_weight change

Is it possible to animate a layout_weight change of a View?
I have tried:
ObjectAnimator anim = ObjectAnimator.ofFloat(headerRoot,
"layout_weight", ws, 1f);
anim.setDuration(1500);
anim.addUpdateListener(this);
anim.start();
This has no effect on my layout. Is the objectAnimator able to manipulate the layout_weight property of a view?
Just use a class provided here: https://stackoverflow.com/a/20334557/1763138
private class ExpandAnimation extends Animation {
private final float mStartWeight;
private final float mDeltaWeight;
public ExpandAnimation(float startWeight, float endWeight) {
mStartWeight = startWeight;
mDeltaWeight = endWeight - startWeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContent.getLayoutParams();
lp.weight = (mStartWeight + (mDeltaWeight * interpolatedTime));
mContent.setLayoutParams(lp);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
and change mContent to your headerRoot
This has no effect on my layout. Is the objectAnimator able to manipulate the layout_weight property of a view?
No, it is not. ObjectAnimator uses the setter method of an object to update its value. A LayoutParam has not setWeight method, it only has the weight property you can change.
It's all explained here: http://developer.android.com/guide/topics/graphics/prop-animation.html#object-animator.

Change the weight of layout with an animation

In my main layout file, I have a RelativeLayout, with a weight of 1 (basically to display a map) above a LinearLayout with a weight of 2, declared this way :
<LinearLayout
android:id="#+id/GlobalLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:id="#+id/UpLayout"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1" >
</RelativeLayout>
<LinearLayout
android:id="#+id/DownLayout"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="2"
android:orientation="vertical" >
</LinearLayout>
</LinearLayout>
DownLayout contains a list of items, when I click on an item, I would like to change the weight of DownLayout for 4, so the upper layout (the map) takes only 1/5 of the screen instead of 1/3.
I have managed to do it by changing the LayoutParams :
LinearLayout linearLayout = (LinearLayout) mActivity.findViewById(R.id.DownLayout);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
params.weight = 4.0f;
linearLayout.setLayoutParams(params);
It works but I'm not satisfied, the change is too immediate, there is no transition while I would like it to be smooth. Is there a way to use animation for that ?
I found some examples with ObjectAnimator to change the weightSum, but it does not do want I want (if I change only this property, I have some free space below my down layout) :
float ws = mLinearLayout.getWeightSum();
ObjectAnimator anim = ObjectAnimator.ofFloat(mLinearLayout, "weightSum", ws, 5.0f);
anim.setDuration(3000);
anim.addUpdateListener(this);
anim.start();
Is there a way to use ObjectAnimator (or something else) to do that ?
Thanks !
I recently came across a similar problem and solved it using a standard Animation (I have to target API 10 so couldn't use ObjectAnimator). I used a combination of the answer here with slight alterations to take into account weight instead of height.
My custom animation class looks as follows...
private class ExpandAnimation extends Animation {
private final float mStartWeight;
private final float mDeltaWeight;
public ExpandAnimation(float startWeight, float endWeight) {
mStartWeight = startWeight;
mDeltaWeight = endWeight - startWeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContent.getLayoutParams();
lp.weight = (mStartWeight + (mDeltaWeight * interpolatedTime));
mContent.setLayoutParams(lp);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
And its called by this method...
public void toggle() {
Animation a;
if (mExpanded) {
a = new ExpandAnimation(mExpandedWeight, mCollapsedWeight);
mListener.onCollapse(mContent);
} else {
a = new ExpandAnimation(mCollapsedWeight, mExpandedWeight);
mListener.onExpand(mContent);
}
a.setDuration(mAnimationDuration);
mContent.startAnimation(a);
mExpanded = !mExpanded;
}
Hopefully this will help you out, if you need more details or have questions about something let me know.

Animation in changing LayoutParams in LinearLayout

In my app there is a LinearLayout which has 0 Layout height. When I click the button this layout height should be LayoutParams.WRAP_CONTENT. This is the code I use in onclicklistner.
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
slider.setLayoutParams(lp);
I want to animate this. How can I set animation to slider.
I think you just want to animate a view from 0 height to its final height, you can do this with a custom Animation:
public class ShowAnim extends Animation {
int targetHeight;
View view;
public ShowAnim(View view, int targetHeight) {
this.view = view;
this.targetHeight = targetHeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (targetHeight * interpolatedTime);
view.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
And do this in your code to start the animation:
Animation ani = new ShowAnim(headerView, 100/* target layout height */);
ani.setDuration(2000/* animation time */);
headerView.startAnimation(ani);
use animateLayoutChanges xml and enableTransitionType in java or kotlin
1. add animateLayoutChanges to root layout xml
<LinearLayout
android:id="#+id/mainLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<android.support.v7.widget.AppCompatEditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="0dp" />
</LinearLayout>
2. in java
LayoutTransition layoutTransition = mainLinearLayout.layoutTransition;
layoutTransition.setDuration(5000); // Change duration
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
editText.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; // you can set number, example: 300
editText.requestLayout();
3.in kotlin
val layoutTransition = mainLinearLayout.layoutTransition
layoutTransition.setDuration(5000) // Change duration
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
editText.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT // you can set number, example: 300
editText.requestLayout()
Since we have the layout transitions in android since JELLYBEAN we can use that instead of using the animation object.
The article below explains it in detail.
https://proandroiddev.com/the-little-secret-of-android-animatelayoutchanges-e4caab2fddec
In short we would only need this code -
tView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
tView.setLayoutParams(lp);
Here lp would be the layout params
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams
(RelativeLayout.LayoutParams.MATCH_PARENT, newHeight);
One more thing to add would be to add this line in the layout file, to the layout that would be doing the transition.
android:animateLayoutChanges="true"
To animate change in Layout params of a Linear layout and its child you can use LayoutTransition.
Its important that you define and attach transition to Linear layout parent for eg: llRoot.setLayoutTransition(layoutTransition) in below code snippet before c hanging LayoutParams of child.
Support for LayoutTransition is above Android JellyBean
private var AnimationDuration = 1100f
#RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
private fun fadeOutControls() {
var layoutTransition = LayoutTransition()
layoutTransition.setDuration(AnimationDuration.toLong()) // Change duration
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
layoutTransition.addTransitionListener(object : LayoutTransition.TransitionListener {
override fun startTransition(transition: LayoutTransition, container: ViewGroup, view: View, transitionType: Int) {
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
override fun endTransition(transition: LayoutTransition, container: ViewGroup, view: View, transitionType: Int) {
//Change this line of code to below one
transition.disableTransitionType(LayoutTransition.CHANGING)
}
})
// set transition to Linear layout
llRoot.setLayoutTransition(layoutTransition)
// change Layout params of child now to animate Transition
val lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)
lp.weight = 10f
mediaRoot.setLayoutParams(lp)
leftControl.visibility = View.GONE
rightControl.visibility = View.GONE
}
If you want to implement the slider animation for your layout then see this demo.
UPDATED
Also see http://www.inter-fuser.com/2009/07/android-transistions-slide-in-and-slide.html
Hope it will help you.
If not then let me know.
Thanks.
Enjoy. :)

Categories

Resources