Animation
and AnimationUtils
in Android SDK, declaring your animations from your own xml files.In this post, I will present this way to create an expandable layout with animation and apply it as a
RecyclerView
row.Creating animation from xml file
anim
folder under res
directory. If you don’t have anim
folder in your res directory create one. Because of these are expanding and collapsing animations, so I have 2 xml files called
slide_down.xml
and slide_up.xml
.Sliding animation uses
<scale>
tag only. Slide down is exactly opposite to slide down (expanding) animation. Just interchange android:fromYScale
and android:toYScale
values:
slide_down.xml
Slide up can be achieved by setting <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
android:duration="500"
android:fromXScale="1.0"
android:fromYScale="0.0"
android:interpolator="@android:anim/linear_interpolator"
android:toXScale="1.0"
android:toYScale="1.0" />
</set>
android:fromYScale="1.0"
and android:toYScale="0.0"
:
slide_up.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true" >
<scale
android:duration="500"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@android:anim/linear_interpolator"
android:toXScale="1.0"
android:toYScale="0.0" />
</set>
Usage in Activity
Animation
and AnimationUtils
, we can create an animation for our layout easily. Suppose we have this activity layout:
activity_ex_layout.xml
And we have this activity Java code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/content_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_green_dark"
android:clickable="true"
android:onClick="toggle_contents"
android:padding="10dp"
android:text="@string/title"
android:textColor="@android:color/white" />
<!--content to hide/show -->
<TextView
android:id="@+id/title_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_dark"
android:padding="10dp"
android:text="@string/long_content"
android:textColor="@android:color/white" />
</LinearLayout>
ExpandableLayoutActivity.java
Running this activity, we'll have this output:
package info.devexchanges.expandablelayout;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
public class ExpandableLayoutActivity extends AppCompatActivity {
private TextView txtContent;
private Animation animationUp;
private Animation animationDown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ex_layout);
txtContent = (TextView) findViewById(R.id.title_text);
TextView txtTitle = (TextView) findViewById(R.id.content_text);
txtContent.setVisibility(View.GONE);
animationUp = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_up);
animationDown = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_down);
txtTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(txtContent.isShown()){
txtContent.setVisibility(View.GONE);
txtContent.startAnimation(animationUp);
}
else{
txtContent.setVisibility(View.VISIBLE);
txtContent.startAnimation(animationDown);
}
}
});
}
}
Use as a RecyclerView row
RecyclerView
row, there are some problems with this widget structure, the collapsing animation will not work if you only call setVisibility(GONE)
and startAnimation(animationUp)
continuously like above, the layout will gone immediately and we won't see the animation. To avoid this matter, providing a CountdownTimer
and gone the view when it finished:
holder.contentLayout.startAnimation(animationUp);
CountDownTimer countDownTimerStatic = new CountDownTimer(COUNTDOWN_RUNNING_TIME, 16) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
holder.contentLayout.setVisibility(View.GONE);
}
};
countDownTimerStatic.start();
Our RecyclerView
adapter is simple like this:
RecyclerAdapter.java
package info.devexchanges.expandablelayout;
import android.content.Context;
import android.os.CountDownTimer;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.ImageView;
import android.widget.TextView;
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ReyclerViewHolder> {
private LayoutInflater layoutInflater;
private Animation animationUp, animationDown;
private Context context;
private final int COUNTDOWN_RUNNING_TIME = 500;
public RecyclerAdapter(Context context, Animation animationUp, Animation animationDown) {
this.layoutInflater = LayoutInflater.from(context);
this.animationDown = animationDown;
this.animationUp = animationUp;
this.context = context;
}
@Override
public ReyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View item = layoutInflater.inflate(R.layout.item_recycler_view, parent, false);
return new ReyclerViewHolder(item);
}
@Override
public void onBindViewHolder(final ReyclerViewHolder holder, int position) {
if (position % 3 == 0) {
holder.image.setImageResource(R.drawable.girl_1);
} else if (position % 3 == 1) {
holder.image.setImageResource(R.drawable.girl_2);
} else {
holder.image.setImageResource(R.drawable.girl_3);
}
holder.showMore.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (holder.contentLayout.isShown()) {
holder.contentLayout.startAnimation(animationUp);
CountDownTimer countDownTimerStatic = new CountDownTimer(COUNTDOWN_RUNNING_TIME, 16) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
holder.contentLayout.setVisibility(View.GONE);
}
};
countDownTimerStatic.start();
holder.showMore.setText(context.getString(R.string.show));
holder.showMore.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.arrow_down, 0);
} else {
holder.contentLayout.setVisibility(View.VISIBLE);
holder.contentLayout.startAnimation(animationDown);
holder.showMore.setText(context.getString(R.string.hide));
holder.showMore.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.arrow_up, 0);
}
}
});
}
@Override
public int getItemCount() {
return 10;
}
class ReyclerViewHolder extends RecyclerView.ViewHolder {
private ImageView image;
private TextView showMore;
private TextView contentLayout;
private ReyclerViewHolder(final View v) {
super(v);
image = (ImageView) v.findViewById(R.id.image);
contentLayout = (TextView) v.findViewById(R.id.content);
showMore = (TextView) v.findViewById(R.id.show_more);
}
}
}
Important Note
: COUNTDOWN_RUNNING_TIME
value must equal with animation duration in our xml file (here is 500 milliseconds).
Layout for each
RecyclerView
row:
item_recycler_view.xml
Source code for the activity:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp">
<ImageView
android:id="@+id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:contentDescription="@null"
android:scaleType="fitXY"
android:src="@drawable/girl_1" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/image"
android:padding="10dp"
android:text="@string/app_name"
android:textStyle="bold" />
<TextView
android:id="@+id/short_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/title"
android:layout_toRightOf="@+id/image"
android:paddingLeft="10dp"
android:text="@string/short_content"
android:textStyle="bold" />
<TextView
android:id="@+id/show_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/short_content"
android:layout_toRightOf="@+id/image"
android:drawablePadding="5dp"
android:drawableRight="@drawable/arrow_down"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:text="@string/show"
android:textStyle="bold" />
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/image"
android:background="@color/colorPrimary"
android:drawablePadding="5dp"
android:padding="5dp"
android:text="@string/long_content"
android:textColor="@android:color/white"
android:textStyle="bold"
android:visibility="gone" />
</RelativeLayout>
ListViewActivity.java
Running this activity, we'll have this output:
package info.devexchanges.expandablelayout;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
public class ListViewActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private Animation animationUp, animationDown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
animationUp = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_up);
animationDown = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_down);
RecyclerAdapter recyclerViewAdapter = new RecyclerAdapter(this, animationUp, animationDown);
recyclerView.setAdapter(recyclerViewAdapter);
}
}