One of my client recently came up with a strange inspiration, which we really wants to integrate in his application
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With