I can imagine two ways to achieve this effect:
One way is to use the elementary element activity transition. This will require the use of two types of activities: one with a recycler view, the second with a full screen view. Animation will automatically apply between switching between activity 1 and two actions. This solution will work and does not require a lot of code, but you will encounter the problem of synchronizing two actions (for example, the exact position of the RecyclerView). Customization is not impossible, but it can be difficult, as you rely heavily on structure.
The second way is to stay within the same action and use object animators to switch between the recycler’s viewing object and full-screen viewing. The trick is not to animate the view that is inside the RecyclerView, but to animate the full screen from the borders of the view that is inside the RecyclerView. Thus, you will not be limited to parental boundaries. I went ahead and implemented the second solution, as it is very customizable and gives you full control over all the animations.
This sample application includes translation and scaling animators. It will be animated from a small square position on the left side of the screen. This behavior can be easily changed.
Demo: https://dl.dropboxusercontent.com/u/87080012/device-2016-03-25-160611.mp4
Project repo link: https: // dkarmazi@bitbucket.org /dkarmazi/androidrecyclerviewanimation.git
Activities
public class MainActivity extends AppCompatActivity implements Adapter.ItemClickListener, CustomView.CloseButtonClickListener { public static final int ANIMATION_SPEED = 3000; private RecyclerView recyclerView; private CustomView customView; private RelativeLayout rootView; private Rect lastClickedRecyclerViewItemRect; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); rootView = (RelativeLayout) findViewById(R.id.root_view); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); customView = (CustomView) findViewById(R.id.custom_view); recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext())); recyclerView.setAdapter(new Adapter(getApplicationContext(), this, getSampleData())); } @Override public void onItemClicked(View clickedView, int position, String title) { lastClickedRecyclerViewItemRect = new Rect(); clickedView.getGlobalVisibleRect(lastClickedRecyclerViewItemRect); Rect targetViewRect = new Rect(); rootView.getGlobalVisibleRect(targetViewRect); AnimatorSet animatorSet = getViewToViewScalingAnimator(rootView, customView, lastClickedRecyclerViewItemRect, targetViewRect, ANIMATION_SPEED, 0); customView.setData(position, title, this); customView.setVisibility(View.VISIBLE); animatorSet.start(); } @Override public void onCloseButtonClicked(int position) { Rect clickedViewRect = new Rect(); customView.getGlobalVisibleRect(clickedViewRect); AnimatorSet animatorSet = getViewToViewScalingAnimator(rootView, customView, clickedViewRect, lastClickedRecyclerViewItemRect, ANIMATION_SPEED, 0); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { // no op } @Override public void onAnimationEnd(Animator animation) { customView.setVisibility(View.GONE); } @Override public void onAnimationCancel(Animator animation) { // no op } @Override public void onAnimationRepeat(Animator animation) { // no op } }); animatorSet.start(); } public static AnimatorSet getViewToViewScalingAnimator(final RelativeLayout parentView, final View viewToAnimate, final Rect fromViewRect, final Rect toViewRect, final long duration, final long startDelay) { // get all coordinates at once final Rect parentViewRect = new Rect(), viewToAnimateRect = new Rect(); parentView.getGlobalVisibleRect(parentViewRect); viewToAnimate.getGlobalVisibleRect(viewToAnimateRect); viewToAnimate.setScaleX(1f); viewToAnimate.setScaleY(1f); // rescaling of the object on X-axis final ValueAnimator valueAnimatorWidth = ValueAnimator.ofInt(fromViewRect.width(), toViewRect.width()); valueAnimatorWidth.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // Get animated width value update int newWidth = (int) valueAnimatorWidth.getAnimatedValue(); // Get and update LayoutParams of the animated view RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) viewToAnimate.getLayoutParams(); lp.width = newWidth; viewToAnimate.setLayoutParams(lp); } }); // rescaling of the object on Y-axis final ValueAnimator valueAnimatorHeight = ValueAnimator.ofInt(fromViewRect.height(), toViewRect.height()); valueAnimatorHeight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // Get animated width value update int newHeight = (int) valueAnimatorHeight.getAnimatedValue(); // Get and update LayoutParams of the animated view RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) viewToAnimate.getLayoutParams(); lp.height = newHeight; viewToAnimate.setLayoutParams(lp); } }); // moving of the object on X-axis ObjectAnimator translateAnimatorX = ObjectAnimator.ofFloat(viewToAnimate, "X", fromViewRect.left - parentViewRect.left, toViewRect.left - parentViewRect.left); // moving of the object on Y-axis ObjectAnimator translateAnimatorY = ObjectAnimator.ofFloat(viewToAnimate, "Y", fromViewRect.top - parentViewRect.top, toViewRect.top - parentViewRect.top); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setInterpolator(new DecelerateInterpolator(1f)); animatorSet.setDuration(duration); // can be decoupled for each animator separately animatorSet.setStartDelay(startDelay); // can be decoupled for each animator separately animatorSet.playTogether(valueAnimatorWidth, valueAnimatorHeight, translateAnimatorX, translateAnimatorY); return animatorSet; } private static List<String> getSampleData() { List<String> dataList = new ArrayList<>(); dataList.add("zero"); dataList.add("one"); dataList.add("two"); dataList.add("three"); dataList.add("four"); dataList.add("five"); dataList.add("six"); dataList.add("seven"); dataList.add("eight"); dataList.add("nine"); dataList.add("ten"); dataList.add("eleven"); dataList.add("twelve"); dataList.add("thirteen"); dataList.add("fourteen"); dataList.add("fifteen"); dataList.add("sixteen"); dataList.add("seventeen"); dataList.add("eighteen"); dataList.add("nineteen"); dataList.add("twenty"); return dataList; } }
Action layout
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root_view" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white"/> <com.dkarmazi.android.myapplication.CustomView android:id="@+id/custom_view" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> </RelativeLayout>
Custom view to be displayed in full screen
public class CustomView extends FrameLayout { public interface CloseButtonClickListener { void onCloseButtonClicked(int position); } private TextView positionView; private TextView titleView; private View closeView; private CloseButtonClickListener closeButtonClickListener; private int position; public CustomView(Context context) { super(context); init(); } public CustomView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { inflate(getContext(), R.layout.custom_view, this); positionView = (TextView) findViewById(R.id.custom_view_position); titleView = (TextView) findViewById(R.id.custom_view_title); closeView = findViewById(R.id.custom_view_close_button); closeView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(closeButtonClickListener != null) { closeButtonClickListener.onCloseButtonClicked(position); } } }); } public void setData(int position, String title, CloseButtonClickListener closeButtonClickListener) { this.position = position; this.positionView.setText("" + position); this.titleView.setText(title); this.closeButtonClickListener = closeButtonClickListener; } }
Layout for custom view
<?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="match_parent" android:background="@android:color/holo_red_dark"> <ImageView android:id="@+id/custom_view_close_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@android:drawable/ic_menu_close_clear_cancel" android:layout_alignParentTop="true" android:layout_alignParentRight="true"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:layout_marginTop="50dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="20sp" android:gravity="center" android:layout_gravity="top" android:text="Position:" /> <TextView android:id="@+id/custom_view_position" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="25sp" android:gravity="center" android:layout_gravity="top" android:paddingBottom="100dp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="20sp" android:gravity="center" android:layout_gravity="top" android:text="Title:" /> <TextView android:id="@+id/custom_view_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" android:gravity="center" android:textSize="25sp" android:layout_gravity="center"/> </LinearLayout> </RelativeLayout>
RecyclerView Adapter
public class Adapter extends RecyclerView.Adapter { public interface ItemClickListener { void onItemClicked(View v, int position, String title); } private Context context; private ItemClickListener itemClickListener; private List<String> dataList; public Adapter(Context context, ItemClickListener itemClickListener, List<String> dataList) { this.context = context; this.itemClickListener = itemClickListener; this.dataList = dataList; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.recycler_view_item, null, false); return new MyViewHolder(view, new OnRecyclerItemClickListener()); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ((MyViewHolder) holder).onRecyclerItemClickListener.updatePosition(position); ((MyViewHolder) holder).position.setText("" + position); ((MyViewHolder) holder).title.setText(dataList.get(position)); } @Override public int getItemCount() { return dataList.size(); } private class MyViewHolder extends RecyclerView.ViewHolder { private OnRecyclerItemClickListener onRecyclerItemClickListener; private TextView position; private TextView title; public MyViewHolder(View itemView, OnRecyclerItemClickListener onRecyclerItemClickListener) { super(itemView); itemView.setOnClickListener(onRecyclerItemClickListener); this.onRecyclerItemClickListener = onRecyclerItemClickListener; this.position = (TextView) itemView.findViewById(R.id.position); this.title = (TextView) itemView.findViewById(R.id.title); } } private class OnRecyclerItemClickListener implements View.OnClickListener { private int position = -1; public void updatePosition(int position) { this.position = position; } @Override public void onClick(View v) { if(itemClickListener != null) { itemClickListener.onItemClicked(v.findViewById(R.id.position), position, dataList.get(position)); } } } }
Recycle View Element Layout
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/recycler_view_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp"> <TextView android:id="@+id/position" android:layout_width="30dp" android:layout_height="50dp" android:textColor="@android:color/white" android:gravity="center" android:background="@android:color/holo_green_light" android:layout_alignParentLeft="true"/> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="50dp" android:textColor="@android:color/white" android:gravity="center" android:background="@android:color/holo_green_dark" android:layout_toRightOf="@id/position" android:layout_alignParentRight="true"/> </RelativeLayout>