Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to connect multiple recyclerview or fragment for animation like shown

One of my client recently came up with a strange inspiration, which we really wants to integrate in his application

Prototype Animation Here

I am trying my hard to achieve this but I am really confused. Can somebody just guide me the steps to achieve this type of transition with animation.

Let me just ask specific question for you to easily answer it

  1. What should we use here fragments or different views elements?
  2. How to interact both of them? Like when the salon category is clicked how to take that Category Section to the top section and keep it selected?
  3. How to convert them also to pager numbers as well?
like image 440
Hammad Avatar asked Apr 21 '17 12:04

Hammad


1 Answers

gif

Note: Full working code is available at https://github.com/mwajeeh/AnimationsPlayground

More explanation in this post: https://medium.com/p/implementing-complex-animations-in-android-full-working-code-41979cc2369e

The card expand animation can be done using Shared element transition. I am going to give you enough material to start implementation of opening of categories and collapsing of pager indicators.

I am assuming that category headings are in RecyclerView. When a category is tapped, open the detail activity with shared element transitions like this:

int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
List<Pair<View, String>> pairs = new ArrayList<Pair<View, String>>();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; i++) {
     ViewHolder holderForAdapterPosition = (ViewHolder) list.findViewHolderForAdapterPosition(i);
     View itemView = holderForAdapterPosition.image;
     pairs.add(Pair.create(itemView, "tab_" + i));
}
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, pairs.toArray(new Pair[]{})).toBundle();
//launch detail activity on tap
Intent intent = new Intent(context, DetailActivity.class);
context.startActivity(intent, bundle);

In the next activity I have modified version of https://github.com/JakeWharton/ViewPagerIndicator/blob/master/library/src/com/viewpagerindicator/IconPageIndicator.java

Only these two methods are modified :

public void notifyDataSetChanged() {
    mIconsLayout.removeAllViews();
    IconPagerAdapter iconAdapter = (IconPagerAdapter) mViewPager.getAdapter();
    int count = iconAdapter.getCount();
    LayoutInflater inflater = LayoutInflater.from(getContext());
    for (int i = 0; i < count; i++) {
        View parent = inflater.inflate(R.layout.indicator, mIconsLayout, false);
        ImageView view = (ImageView) parent.findViewById(R.id.icon);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // must be same as off previous activity
            view.setTransitionName("tab_" + i);
        }
        view.setImageResource(iconAdapter.getIconResId(i));
        mIconsLayout.addView(parent);
    }
    if (mSelectedIndex > count) {
        mSelectedIndex = count - 1;
    }
    setCurrentItem(mSelectedIndex);
    requestLayout();
}

@Override
public void setCurrentItem(int item) {
    if (mViewPager == null) {
        throw new IllegalStateException("ViewPager has not been bound.");
    }
    mSelectedIndex = item;
    mViewPager.setCurrentItem(item);

    int tabCount = mIconsLayout.getChildCount();
    for (int i = 0; i < tabCount; i++) {
        View child = mIconsLayout.getChildAt(i);
        boolean isSelected = (i == item);
        child.setSelected(isSelected);
        View foreground = child.findViewById(R.id.foreground);
        if (isSelected) {
            animateToIcon(item);
            foreground.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), R.drawable.fg_white));
        } else {
            foreground.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), R.drawable.fg_gray));
        }
    }
}

and this method is added in IconPageIndicator to support collapsing of indicator:

public void collapse(float top, float total) {
    //do not scale to 0
    float newTop = top / 1.2F;
    float scale = (total - newTop) / (float) total;
    ViewCompat.setScaleX(this, scale);
    ViewCompat.setScaleY(this, scale);
    int tabCount = mIconsLayout.getChildCount();
    float alpha = (total - top) / (float) total;
    for (int i = 0; i < tabCount; i++) {
        View parent = mIconsLayout.getChildAt(i);
        View child = parent.findViewById(R.id.foreground);
        ViewCompat.setAlpha(child, 1 - alpha);
    }
}

in onCreate() of DetailActivity, postpone transition until indicator is laid out.

supportPostponeEnterTransition();
indicator.post(new Runnable() {
        @Override
        public void run() {
            supportStartPostponedEnterTransition();
        }
});

Other related important files:

activity_detail.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/black"
        app:elevation="0dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:elevation="0dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">
            <!--dummy view-->
            <View
                android:layout_width="match_parent"
                android:layout_height="200dp"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@android:color/transparent"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:elevation="0dp"
                app:layout_collapseMode="pin"/>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <com.example.mwajeeh.animations.IconPageIndicator
        android:id="@+id/indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:centered="true"
        app:layout_behavior="com.example.mwajeeh.animations.CollapsingIndicatorBehaviour"/>

<!--add fragments in this view pager with RecyclerView as root and app:layout_behavior="@string/appbar_scrolling_view_behavior"-->
    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>

CollapsingIndicatorBehaviour.java

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.WindowInsetsCompat;
import android.util.AttributeSet;
import android.view.View;

public class CollapsingIndicatorBehaviour extends ViewOffsetBehavior<IconPageIndicator> {

    private static final String TAG = "CollapsingIndicatorBehaviour";
    private WindowInsetsCompat lastInsets;

    public CollapsingIndicatorBehaviour() {
    }

    public CollapsingIndicatorBehaviour(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, IconPageIndicator child, View dependency) {
        return dependency instanceof AppBarLayout;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, IconPageIndicator child, View dependency) {
        //keep child centered inside dependency respecting android:fitsSystemWindows="true"
        int systemWindowInsetTop = 0;
        if (lastInsets != null) {
            systemWindowInsetTop = lastInsets.getSystemWindowInsetTop();
        }
        int bottom = dependency.getBottom();
        float center = (bottom - systemWindowInsetTop) / 2F;
        float halfChild = child.getHeight() / 2F;
        setTopAndBottomOffset((int) (center + systemWindowInsetTop - halfChild));
        if (dependency instanceof AppBarLayout) {
            float totalScrollRange = ((AppBarLayout) dependency).getTotalScrollRange();
            child.collapse(-dependency.getTop(), totalScrollRange);
        }
        return true;
    }

    @NonNull
    @Override
    public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, IconPageIndicator child, WindowInsetsCompat insets) {
        lastInsets = insets;
        return super.onApplyWindowInsets(coordinatorLayout, child, insets);
    }
}

Copy android.support.design.widget.ViewOffsetBehavior.java and android.support.design.widget.ViewOffsetHelper into your code from design library. These files are not public but we need them for our behaviour to work.

like image 135
M-WaJeEh Avatar answered Nov 08 '22 19:11

M-WaJeEh