I am working on morphing a floating action button (FAB) to a toolbar and things work smoothly and perfectly with the following code:
layout file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="sg.com.saurabh.designlibraryexpirements.ToolbarMorphActivity">
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|right"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:layout_marginRight="#dimen/activity_vertical_margin"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:src="#drawable/ic_add" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
style="#style/ToolBarTheme"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="top"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:visibility="invisible"
tools:visibility="visible" />
</FrameLayout>
activity:
package sg.com.saurabh.designlibraryexpirements;
import android.animation.Animator;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
public class ToolbarMorphActivity extends AppCompatActivity {
Toolbar toolbar;
FloatingActionButton fab;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toolbar_morph);
toolbar = (Toolbar) findViewById(R.id.toolbar);
fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(mFabClickListener);
}
private View.OnClickListener mFabClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
fab.animate()
.rotationBy(45)
.setInterpolator(new AnticipateOvershootInterpolator())
.setDuration(250)
.start();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
fab.setVisibility(View.GONE);
}
},50);
revealToolbar();
}
};
private void revealToolbar() {
toolbar.setVisibility(View.VISIBLE);
int x = (int)fab.getX() + fab.getWidth()/2;
int y = (int)fab.getY() + fab.getHeight()/2;
Animator animator = ViewAnimationUtils.createCircularReveal(toolbar, x, y, 0, toolbar.getWidth())
.setDuration(400);
animator.setInterpolator(new FastOutLinearInInterpolator());
animator.start();
}
private void dismissToolbar() {
int x = (int)fab.getX() + fab.getWidth()/2;
int y = (int)fab.getY() + fab.getHeight()/2;
Animator animator = ViewAnimationUtils.createCircularReveal(toolbar, x, y, toolbar.getWidth(), 0)
.setDuration(400);
animator.setInterpolator(new LinearOutSlowInInterpolator());
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
toolbar.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
fab.setVisibility(View.VISIBLE);
fab.animate()
.rotationBy(-45)
.setInterpolator(new AccelerateInterpolator())
.setDuration(100)
.start();
}
},200);
}
#Override
public void onBackPressed() {
if(toolbar.getVisibility() == View.VISIBLE) {
dismissToolbar();
}
else
super.onBackPressed();
}
}
The circular reveal works as expected for the above layout. However thing break up when I change the layout_gravity of the fab and toolbar to bottom instead of top. The rotate animation works and then the toolbar just appears without the circular reveal animation. I am completely stumped by how that breaks the circular reveal animation.
The fix for you would be to replace:
private void revealToolbar() {
....
int x = (int)fab.getX() + fab.getWidth()/2;
int y = (int)fab.getY() + fab.getHeight()/2;
....
}
by
private void revealToolbar() {
...
int x = (int)fab.getX() + fab.getWidth()/2;
int y = fab.getHeight()/2;
...
}
The reason is that createCircularReveal is taking parameters centerY and centerX as coordinates of the center of the animating circle, relative to view (i.e. Toolbar, in our case).
See method ViewAnimationUtils.createCircularReveal definition:
........
* #param view The View will be clipped to the animating circle.
* #param centerX The x coordinate of the center of the animating circle, relative to
* <code>view</code>.
* #param centerY The y coordinate of the center of the animating circle, relative to
* <code>view</code>.
* #param startRadius The starting radius of the animating circle.
* #param endRadius The ending radius of the animating circle.
*/
Related
I would like to expand/collapse an ImageView but start from 50% picture to expand at 100%, collapse to 50% not under.
I already took a look at some popular questions and answers on SO but I didn't find how to manage only half. I also want to modify on the height of view, not the width.
What I tried :
public static void expand(final View v) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final int targtetHeight = v.getMeasuredHeight();
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
Animation a = new Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int)(targtetHeight * interpolatedTime);
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration((int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
public static void collapse(final View v) {
final int initialHeight = v.getMeasuredHeight();
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1) {
v.setVisibility(View.VISIBLE);
} else {
v.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime);
v.requestLayout();
}
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration((int) (initialHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
as I said it's not what I want because it make disappeared totally and it change the width.
I also tried this snippet but there is no animation :
mImageDrawable = (ClipDrawable) pic.getDrawable();
mImageDrawable.setLevel(5000);//use set level to expand or collapse manually but no animation.
clip:
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:clipOrientation="vertical"
android:drawable="#drawable/test_pic"
android:gravity="top" />
Use Transition API which is available in support package (androidx). Just call TransitionManager.beginDelayedTransition then change height of view. TransitionManager will handle this changes and it will provide transition which will change imageView with animation.
scaleType of ImageView here is centerCrop thats why image scales when collapse and expand. Unfortunetly there is no "fill width and crop bottom" scaleType, so if you need it I think it can be done throught scaleType = matrix .
import androidx.appcompat.app.AppCompatActivity;
import androidx.transition.TransitionManager;
public class MainActivity extends AppCompatActivity {
private ImageView image;
private ViewGroup parent;
boolean collapse = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
image = findViewById(R.id.image);
parent = findViewById(R.id.parent);
findViewById(R.id.btn).setOnClickListener(view -> {
collapse = !collapse;
collapse();
});
}
private void collapse() {
TransitionManager.beginDelayedTransition(parent);
//change layout params
int height = image.getHeight();
LayoutParams layoutParams = image.getLayoutParams();
layoutParams.height = !collapse ? height / 2 : height * 2;
image.requestLayout();
}
}
Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="#+id/btn"
android:text="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="300dp"
android:scaleType="centerCrop"
android:src="#drawable/qwe" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="random text"
android:layout_margin="8dp"/>
</LinearLayout>
UPDATE:
There is beginDelayedTransition(ViewGroup, Transtion) method. beginDelayedTransition(ViewGroup) by default use AutoTransition as transition.
So if you need handle start/end of transition you can do it like this:
AutoTransition transition = new AutoTransition();
transition.addListener(new TransitionListenerAdapter(){
#Override
public void onTransitionStart(#NonNull Transition transition) {
//TODO
}
#Override
public void onTransitionEnd(#NonNull Transition transition) {
//TODO
}
});
TransitionManager.beginDelayedTransition(parent, transition);
I added Images view dynamically to my Linear layout,
I want to achieve
Here is sample code what I have done
MainActivity
package ksp.com.animationssample;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity {
private Button btn_expand;
private Button btn_collapse;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_expand = (Button) findViewById(R.id.btn_expand);
btn_collapse = (Button) findViewById(R.id.btn_collapse);
LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
final LinearLayout layout = (LinearLayout) findViewById(R.id.imageLayout);
for (int i = 0; i < 3; i++) {
final ImageView image = new ImageView(MainActivity.this);
image.setLayoutParams(vp);
if (i == 0)
image.setImageDrawable(getResources().getDrawable(R.drawable.item_image1));
else image.setImageDrawable(getResources().getDrawable(R.drawable.item_image2));
if (i == 2)
image.setVisibility(View.VISIBLE);
else
image.setVisibility(View.GONE);
layout.addView(image);
}
btn_expand.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
expandOrCollapse(layout.getChildAt(2), true, layout.getChildAt(0).getHeight() + layout.getChildAt(1).getHeight());
expandOrCollapse(layout.getChildAt(1), true, layout.getChildAt(0).getHeight());
expandOrCollapse(layout.getChildAt(0), true, 0);
}
});
btn_collapse.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
expandOrCollapse(layout.getChildAt(0), false, 0);
expandOrCollapse(layout.getChildAt(1), false, layout.getChildAt(0).getHeight());
expandOrCollapse(layout.getChildAt(2), false, layout.getChildAt(0).getHeight() + layout.getChildAt(1).getHeight());
}
});
}
public void expandOrCollapse(final View v, boolean is_expand, final int animheight) {
TranslateAnimation anim = null;
if (is_expand) {
anim = new TranslateAnimation(0.0f, 0.0f, -animheight, 0.0f);
v.setVisibility(View.VISIBLE);
Animation.AnimationListener expandedlistener = new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
}
};
anim.setAnimationListener(expandedlistener);
} else {
anim = new TranslateAnimation(0.0f, 0.0f, 0.0f, -animheight);
Animation.AnimationListener collapselistener = new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
v.setVisibility(View.GONE);
}
};
anim.setAnimationListener(collapselistener);
}
anim.setDuration(1500);
anim.setInterpolator(new AccelerateInterpolator(0.5f));
v.startAnimation(anim);
}
}
activity_mainn.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:orientation="vertical"
android:background="#android:color/holo_blue_dark"
tools:context="ksp.com.animationssample.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:id="#+id/btn_expand"
android:text="Expand"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/btn_collapse"
android:text="Collopse"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:id="#+id/imageLayout"
android:gravity="center"
android:orientation="vertical"
android:layout_height="wrap_content">
</LinearLayout>
<!--android:animateLayoutChanges="true"-->
</ScrollView>
</RelativeLayout>
when I click expand button at the first time is not showing the animation from the second time it's working.
enable Visible the item after click on collapse button.
What to do next :
When I selected any View item it has to show select animation then collapse animation, after collapse it has to appear as top View like I mention in Image above. Currently I am hard code the count of the views and wrote static animations for each view with static heights of animations i.e (expandOrCollapse(view, height_of_view))
I search a while for this, but no luck.
I follow the Vie expand/collapse and smooth expand / collapse but they can't help me expand all views in Linear layout.
Can we do this List views are recycler view or something ?
for time saving I have added my sample to git hub you can try it Animation Sample Github
Suggest me, please.
I have made separate class for it. Check if it works for you:
public class DropDownAnim extends Animation {
private final int sourceHeight;
private final int targetHeight;
private final View view;
private final boolean down;
public DropDownAnim(View view, int sourceHeight, int targetHeight, boolean down) {
this.view = view;
this.sourceHeight = sourceHeight;
this.targetHeight = targetHeight;
this.down = down;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight;
if (down) {
newHeight = sourceHeight + (int) (targetHeight * interpolatedTime);
} else {
newHeight = sourceHeight + targetHeight - (int) (targetHeight * interpolatedTime);//(int) (targetHeight * (1 - interpolatedTime));
}
view.getLayoutParams().height = newHeight;
view.requestLayout();
view.setVisibility(down ? View.VISIBLE : View.GONE);
}
#Override
public void initialize(int width, int height, int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
While working with this. For expand
public void expand(final View v) {
final int sourceHeight = v.getLayoutParams().height;
final int targetHeight = getResources().getDimensionPixelSize(R.dimen.notification_height);//v.getMeasuredHeight();
DropDownAnim a = new DropDownAnim(v,targetHeight,true);
a.setDuration((int) (targetHeight / v.getContext().getResources().getDisplayMetrics().density));
a.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
// Your code on end of animation
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
v.setVisibility(View.INVISIBLE);
v.startAnimation(a);
}
For collapse:
public void collapse(final View v) {
final int sourceHeight = v.getLayoutParams().height;
final int targetHeight = v.getMeasuredHeight();
DropDownAnim a = new DropDownAnim(v, targetHeight, false);
a.setDuration((int) (targetHeight / v.getContext().getResources().getDisplayMetrics().density));
a.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
// Your code on end of animation
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
v.startAnimation(a);
}
Update:
sourceHeight added to prevent view blinking.
Try to not hide your target view (now you set visibility GONE for all views after collapsed animation end) and do this:
targetView.bringToFront();
targetView.invalidate();
targetView.getParent().requestLayout();
I got swipe up and swipe down for a layout by using setOnTouchListener (For understanding see below images)
Images
But i want to swipe up and swipe down the layout when clicking on the button not when OnTouchListener. For this i tried almost all examples in the online but i didn't get any solution according to my requirement. So, please help me to make OnTouchListener event when clicking on the button
My Code
Activity
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.RelativeLayout;
public class SwipeUpActivity extends Activity {
RelativeLayout rlSwipeHolder, rlSwipe1, rlSwipe2;
private float startY;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_swipe_up);
rlSwipeHolder = (RelativeLayout) findViewById(R.id.rl_swipe_up_holder);
rlSwipe1 = (RelativeLayout) findViewById(R.id.rl_swipe_up_1);
rlSwipe2 = (RelativeLayout) findViewById(R.id.rl_swipe_up_2);
rlSwipe2.setVisibility(View.GONE);
rlSwipeHolder.setOnTouchListener(new OnTouchListener() {
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = event.getY();
break;
case MotionEvent.ACTION_UP: {
float endY = event.getY();
if (endY < startY) {
System.out.println("Move UP");
rlSwipeHolder.setVisibility(View.VISIBLE);
rlSwipe1.setVisibility(View.VISIBLE);
rlSwipe2.setVisibility(View.VISIBLE);
} else {
rlSwipeHolder.setVisibility(View.VISIBLE);
rlSwipe1.setVisibility(View.VISIBLE);
rlSwipe2.setVisibility(View.GONE);
}
}
}
return true;
}
});
}
}
Layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context="com.app.swipeup.SwipeUpActivity" >
<RelativeLayout
android:id="#+id/rl_swipe_up_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#0000FF"
android:padding="5dp" >
<RelativeLayout
android:id="#+id/rl_swipe_up_1"
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="#585858" >
</RelativeLayout>
<RelativeLayout
android:id="#+id/rl_swipe_up_2"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_below="#+id/rl_swipe_up_1"
android:background="#FE2E2E" >
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
Edit
Like this video i have to open the layout and close the layout (or swipe up the layout and swipe down the layout) when clicking the button
Create object of your onTouchListenr.
OnTouchListener ot = new OnTouchListener(){-------- YOUR CODE ---}
on enable button click
rlSwipeHolder.setOnTouchListener(ot);
on disable button clik
rlSwipeHolder.setOnTouchListener(null);
This is not the exact solution for this, but i'm posting here, because it may help others.
Answer
I saw lot of solutions for setOnTouchListener after setOnClickListener but i didn't get. So, i followed the animation for layout up and down as discussed in this link
and i did minute changes to the code as
Activity
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;
public class SwipeUpActivity extends Activity {
RelativeLayout mRelativeLayout;
RelativeLayout mRelativeLayoutHeader;
ValueAnimator mAnimator;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_swipe_up);
mRelativeLayout = (RelativeLayout) findViewById(R.id.expandable);
// mLinearLayout.setVisibility(View.GONE);
mRelativeLayoutHeader = (RelativeLayout) findViewById(R.id.header);
// Add onPreDrawListener
mRelativeLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mRelativeLayout.getViewTreeObserver().removeOnPreDrawListener(this);
mRelativeLayout.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mRelativeLayout.measure(widthSpec, heightSpec);
mAnimator = slideAnimator(0, mRelativeLayout.getMeasuredHeight());
return true;
}
});
mRelativeLayoutHeader.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mRelativeLayout.getVisibility() == View.GONE) {
expand();
} else {
collapse();
}
}
});
}
private void expand() {
// set Visible
mRelativeLayout.setVisibility(View.VISIBLE);
mAnimator.start();
}
private void collapse() {
int finalHeight = mRelativeLayout.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
// Height=0, but it set visibility to GONE
mRelativeLayout.setVisibility(View.GONE);
}
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
private ValueAnimator slideAnimator(int start, int end) {
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 = mRelativeLayout.getLayoutParams();
layoutParams.height = value;
mRelativeLayout.setLayoutParams(layoutParams);
}
});
return animator;
}
}
Layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<RelativeLayout
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="#FCF">
</RelativeLayout>
<RelativeLayout
android:id="#+id/expandable"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_below="#+id/header"
android:background="#FFF">
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
I try to make some tests with UX and animation. I want at a click on a button to expand it until the button has the size of his parent layout. Here is my very simple interface :
I succeed to create my own animation, it's just a simple class that takes the begin X, the end X, the begin Y and the end Y. Here is the class :
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
public class ExpandAnimation extends Animation {
protected final View view;
protected float perValueH, perValueW;
protected final int originalHeight, originalWidth;
public ExpandAnimation(View view, int fromHeight, int toHeight, int fromWidth, int toWidth) {
this.view = view;
this.originalHeight = fromHeight;
this.originalWidth = fromWidth;
this.perValueH = (toHeight - fromHeight);
this.perValueW = (toWidth - fromWidth);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (originalHeight + perValueH * interpolatedTime);
view.getLayoutParams().width = (int) (originalWidth + (perValueW * interpolatedTime)*2);
view.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
The animation is pretty good and works as expected :
Here is my main loop :
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button left;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
left = (Button) findViewById(R.id.left);
left.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
left.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
ViewGroup parent = (ViewGroup) view.getParent();
ExpandAnimation animation = new ExpandAnimation(view, view.getBottom(), parent.getTop(), view.getLeft(), parent.getRight());
animation.setDuration(3000);
animation.setFillAfter(true);
view.startAnimation(animation);
}
});
}
}
But I have 3 major issues :
First, the text is gone and I don't know why...
On the other hand, I want that my animation come over others views, but when I tried it wasn't working how can I achieve this ?
And last but not least, at the end of my animation, the button is gone and there is just an ugly artefact... How can I avoid that ?
Edit : my layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="MainActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="150dp"
android:weightSum="3"
android:padding="15dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:gravity="bottom">
<Button
android:id="#+id/left"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Retenter"
android:textSize="15dp"
android:textColor="#FFF"
android:background="#FF33CC44" />
</LinearLayout>
</RelativeLayout>
Edit2 : The artefact which appears at the end of the animation is due to the method setLayerType(View.LAYER_TYPE_SOFTWARE, null).
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (originalHeight + perValueH * interpolatedTime);
view.getLayoutParams().width = (int) (originalWidth + (perValueW * interpolatedTime));
view.requestLayout();
}
remove *2 will result below.
I am trying to animate a layout to hide itself (to later animate other layout to take its place also by animation). Must be SDK 8 (android 2.2) compatible.
What I have works great IF the movingLayout's parent (parent_of_moving_layout in my xml) is twice the movingLayout's height, but I want the parent to be the same height so the movingLayout hides itself below so that I can animate another movingLayout2 later to take its place.
I have been working on it for some long hours and I can't find a solution, hoping someone has an idea (tried setFillAfter and setFillEnabled).
Here is my code and xml to reproduce the result:
activity_my.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MyActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MOVE IT"
android:id="#+id/button"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:id="#+id/parent_of_moving_layout">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="50dp"
android:background="#ff93c049"
android:id="#+id/movinglayout"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Button"
android:id="#+id/testbutton"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
</RelativeLayout>
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="80dp"
android:layout_below="#+id/button"
android:layout_centerHorizontal="true"
android:layout_marginTop="83dp"></FrameLayout>
</RelativeLayout>
MyActivity.java
package coersum.com.testanimation;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.RelativeLayout;
public class MyActivity extends ActionBarActivity {
//Button clicked to make movingLayout move (mButton) and button to make sure the objects moved and not only their displays (testButton)
Button mButton, testButton;
//layout to move and its LayoutParams (relative seems to be the only one that worked on my tests)
RelativeLayout movingLayout;
RelativeLayout.LayoutParams params;
//used to direction of movement for testing
String direction = "down";
//just a counter to see the Log.d changes more clearly
int count = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(mButtonClickListener);
testButton = (Button) findViewById(R.id.testbutton);
testButton.setOnClickListener(testButtonClickListener);
movingLayout = (RelativeLayout) findViewById(R.id.movinglayout);
params = (RelativeLayout.LayoutParams) movingLayout.getLayoutParams();
}
private View.OnClickListener mButtonClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
TranslateAnimation animation;
if (direction.equals("down")) {
animation = new TranslateAnimation(0, 0, 0, 50);
} else {
animation = new TranslateAnimation(0, 0, 0, -50);
}
animation.setFillAfter(true);
animation.setFillEnabled(true);
animation.setDuration(500);
animation.setAnimationListener(mAnimationListener);
movingLayout.startAnimation(animation);
}
};
private View.OnClickListener testButtonClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
count++;
Log.d("Status", "Clicked on Test Button "+count+" "+direction);
}
};
private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
movingLayout.clearAnimation();
if (direction.equals("down")) {
direction = "up";
params.topMargin = params.topMargin + 50;
} else {
direction = "down";
params.topMargin = params.topMargin - 50;
}
movingLayout.setLayoutParams(params);
}
};
}
After a few more hours of trials, I found an easy and clean solution (it was really just sooo simple, animate down then set View.GONE and to bring it back, set View.VISIBLE before animating up)! Added a scale for DP and finally made the difference between margin and translation.
Here is an updated java file:
package coersum.com.testanimation;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.RelativeLayout;
public class MyActivity extends Activity {
//Button clicked to make movingLayout move (mButton) and button to make sure the objects moved and not only their displays (testButton)
Button mButton, testButton;
//layout to move and its LayoutParams (relative seems to be the only one that worked on my tests)
RelativeLayout movingLayout;
RelativeLayout.LayoutParams params;
//used to direction of movement for testing
String direction = "down";
//just a counter to see the Log.d changes more clearly
int count = 0;
int convertedSize;
float scale;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
scale = getResources().getDisplayMetrics().density;
convertedSize = (int) (50 * scale + 0.5f); // my layout is 50dp in height set it to (minus)-your_size to go up instead of down
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(mButtonClickListener);
testButton = (Button) findViewById(R.id.testbutton);
testButton.setOnClickListener(testButtonClickListener);
movingLayout = (RelativeLayout) findViewById(R.id.movinglayout);
params = (RelativeLayout.LayoutParams) movingLayout.getLayoutParams();
}
private View.OnClickListener mButtonClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
TranslateAnimation animation;
if (direction.equals("down")) {
animation = new TranslateAnimation(0, 0, 0, convertedSize);
} else {
movingLayout.setVisibility(View.VISIBLE); // show the layout before to animate it
animation = new TranslateAnimation(0, 0, convertedSize, 0);
}
animation.setDuration(500);
animation.setAnimationListener(mAnimationListener);
movingLayout.startAnimation(animation);
}
};
private View.OnClickListener testButtonClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
count++; // just for debugging (to make sure the button was gone and not just its display)
Log.d("Status", "Clicked on Test Button " + count + " Dir: " + direction + " Size: " + convertedSize);
}
};
private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
if (direction.equals("down")) {
movingLayout.setVisibility(View.GONE); // once it translated down, remove it so I'll be free to set other layout to visible to use etc
direction = "up";
} else {
direction = "down";
}
movingLayout.setLayoutParams(params);
movingLayout.clearAnimation();
}
};
public void testClick(View view) {
}
}
The setting of margin is good if you want to just move the layout inside the screen (and actually move the objects and not just their "display" which is what animations do below Honeycomb sdk, but that wasn't my case so this, so far seems to work perfectly.