androidandroid-recyclerviewactivity-transition

How to get out a childview from RecyclerView?


[EDITED AT THE BOTTOM]

I'm trying to code manually these types of animations:

Google Calendar

If you carefully see those views, they belong to a List or RecyclerView, but they are animated (size animation, translation animation) out of the parent's bounds.

If I try to do that, the result is that my view goes under my parent's bounds.

https://drive.google.com/file/d/0B-V0KHNRjbE_bkJEekExNGNLbDA/view?usp=sharing


This is one frame, carefully stopped just to see that the child view has been taken from the parent, and is starting expanding to the whole view:

enter image description here

and this is where it's almost 100% expanded:

enter image description here


I just wanted to re-point this in another way. Is this a stuff in relation with Activity Transitions ? Because if so, I don't have any idea of how to do it.


Solution

  • I can think of two ways to achieve this effect:

    One way is using Shared Element Activity Transition. It will require using 2 activities: one with recycler view, second with full screen view. Animation will be automatically applied between switching between activity one and activity two. This solution will work and does not require much code, but you will run into a problem of keeping two activities in sync (such as exact position of RecyclerView). Customization is not impossible, but could be difficult as you are heavily relying on the framework.

    Second way, is staying within the same activity and using object animators to transition between your recycler view item and full screen view. The trick is not to animate the view that is located inside of RecyclerView, but animate your full screen view from the boundaries of the view that is located inside the RecyclerView. This way, you will not be limited by parent's boundaries. I went ahead and implemented second solution as it is highly customizable and gives you a full control over all the animations.

    This sample app includes translation and scaling animators. It will animate from little square position view 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

    Link to project repo: https://dkarmazi@bitbucket.org/dkarmazi/androidrecyclerviewanimation.git

    Activity

    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;
        }
    }
    

    Activity 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 that will be shown 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 the 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));
                }
            }
        }
    }
    

    Recycler view item 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>