com.sinyuk.jianyimaterial.widgets.FloatingToolbar.java Source code

Java tutorial

Introduction

Here is the source code for com.sinyuk.jianyimaterial.widgets.FloatingToolbar.java

Source

/*
 * Copyright 2016 Rben Sousa
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sinyuk.jianyimaterial.widgets;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.DrawableRes;
import android.support.annotation.MenuRes;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.LinearLayoutCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;

import com.sinyuk.jianyimaterial.R;
import com.sinyuk.jianyimaterial.utils.LogUtils;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@CoordinatorLayout.DefaultBehavior(FloatingToolbar.Behavior.class)
public class FloatingToolbar extends LinearLayoutCompat implements View.OnClickListener, View.OnLongClickListener {

    private static final int FAB_MORPH_DURATION = 200;
    private static final int FAB_UNMORPH_DURATION = 200;
    private static final int FAB_UNMORPH_DELAY = 300;
    private static final int CIRCULAR_REVEAL_DURATION = 300;
    private static final int CIRCULAR_UNREVEAL_DURATION = 200;
    private static final int CIRCULAR_REVEAL_DELAY = 125;
    private static final int CIRCULAR_UNREVEAL_DELAY = 150;
    private static final int MENU_ANIMATION_DELAY = 200;
    private static final int MENU_ANIMATION_DURATION = 300;
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

    private FloatingActionButton mFab;
    private Menu mMenu;
    private View mCustomView;

    @MenuRes
    private int mMenuRes;

    @DrawableRes
    private int mItemBackground;

    private boolean mMorphed;
    private boolean mMorphing;
    private View mRoot;
    private float mFabOriginalX;
    private float mFabOriginalY;
    private float mOriginalX;
    private ItemClickListener mClickListener;
    private LinearLayoutCompat mMenuLayout;

    public FloatingToolbar(Context context) {
        this(context, null, 0);
    }

    public FloatingToolbar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FloatingToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FloatingToolbar, 0, 0);

        TypedValue outValue = new TypedValue();

        // Set colorAccent as default color
        /*    if (getBackground() == null) {
        getContext().getTheme().resolveAttribute(R.attr.colorAccent, outValue, true);
        setBackgroundResource(outValue.resourceId);
            }*/

        setBackgroundColor(getResources().getColor(R.color.transparent));

        getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);

        mItemBackground = a.getResourceId(R.styleable.FloatingToolbar_floatingItemBackground, outValue.resourceId);

        mMenuRes = a.getResourceId(R.styleable.FloatingToolbar_floatingMenu, 0);

        int customView = a.getResourceId(R.styleable.FloatingToolbar_floatingCustomView, 0);

        if (customView != 0) {
            mCustomView = LayoutInflater.from(context).inflate(customView, this, true);
        }

        a.recycle();

        if (customView == 0) {
            mMenuLayout = new LinearLayoutCompat(context, attrs, defStyleAttr);

            LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);

            mMenuLayout.setId(genViewId());
            addView(mMenuLayout, layoutParams);
        }

        if (!isInEditMode()) {
            setVisibility(View.INVISIBLE);
        }

        if (mMenuRes != 0 && customView == 0) {
            addMenuItems();
        }

        // Set elevation to 6dp
        //        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        //            setElevation(dpToPixels(6));
        //        }

        setOrientation(HORIZONTAL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mRoot = getRootView();
        LogUtils.simpleLog(FloatingToolbar.class, "onMeasure");
        if (mFab != null && mFabOriginalX == 0 && mFabOriginalY == 0) {
            mFabOriginalY = mFab.getY();
            mFabOriginalX = mFab.getX();
            mOriginalX = getX();
        }
    }

    public boolean isShowing() {
        return mMorphed;
    }

    public void setClickListener(ItemClickListener listener) {
        mClickListener = listener;
    }

    public View getCustomView() {
        return mCustomView;
    }

    public void setCustomView(View view) {
        removeAllViews();
        mCustomView = view;
        addView(view);
    }

    public void setMenu(@MenuRes int menuRes) {
        removeAllViews();
        Menu menu = new MenuBuilder(getContext());
        new SupportMenuInflater(getContext()).inflate(menuRes, menu);
        setMenu(menu);
    }

    public void setMenu(Menu menu) {
        mMenu = menu;
        removeAllViews();
        addMenuItems();
    }

    public void attachFab(FloatingActionButton fab) {
        mFab = fab;
        mFab.setOnClickListener(v -> {
            if (!mMorphed) {
                show();
            }
        });
    }

    public void attachRecyclerView(RecyclerView recyclerView) {
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (dy >= dpToPixels(8)) {
                    hide();
                }
                if (Math.abs(dy) >= dpToPixels(24)) {
                    if (!mMorphed && !mMorphing) {
                        if (dy > 0) {
                            if (null != mFab && mFab.getVisibility() == VISIBLE) {
                                mFab.hide();
                            }
                        } else {
                            if (null != mFab && mFab.getVisibility() != VISIBLE) {
                                mFab.show();
                            }
                        }
                    }
                }
            }
        });
    }

    public void show() {
        if (mFab == null) {
            throw new IllegalStateException(
                    "FloatingActionButton not attached." + "Please, use attachFab(FloatingActionButton fab).");
        }

        if (mMorphing) {
            return;
        }

        mMorphed = true;
        mMorphing = true;

        if (mMenuLayout != null) {
            mMenuLayout.setAlpha(0f);
            mMenuLayout.setScaleX(0.7f);
        }

        if (mCustomView != null) {
            mCustomView.setAlpha(0f);
            mCustomView.setScaleX(0.7f);
        }

        /**
         * Place view a bit closer to the fab
         */
        float startRevealX;

        if (mFabOriginalX > getWidth() / 2f) {
            startRevealX = mOriginalX + (mFabOriginalX - mOriginalX) / 4f;
        } else {
            startRevealX = mOriginalX - (mFabOriginalX - mOriginalX) / 4f;
        }

        setX(startRevealX);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            showLollipopImpl();
        } else {
            showDefaultImpl();
        }

        if (mMenuLayout != null) {
            mMenuLayout.animate().alpha(1).scaleX(1f).setDuration(MENU_ANIMATION_DURATION)
                    .setStartDelay(MENU_ANIMATION_DELAY).setInterpolator(new AccelerateDecelerateInterpolator());
        }
        if (mCustomView != null) {
            mCustomView.animate().alpha(1).scaleX(1).setDuration(MENU_ANIMATION_DURATION)
                    .setStartDelay(MENU_ANIMATION_DELAY).setInterpolator(new AccelerateDecelerateInterpolator());
        }

        /**
         * Move FloatingToolbar to the original position
         */
        animate().x(mOriginalX).setStartDelay(CIRCULAR_REVEAL_DELAY).setDuration(CIRCULAR_REVEAL_DURATION / 2)
                .setInterpolator(new AccelerateDecelerateInterpolator());
    }

    public void hide() {
        if (mFab == null) {
            throw new IllegalStateException(
                    "FloatingActionButton not attached." + "Please, use attachFab(FloatingActionButton fab).");
        }

        if (mMorphed && !mMorphing) {
            mMorphed = false;

            float x = getX();

            if (mFabOriginalX > x) {
                x = x + (mFabOriginalX - x) / 4f;
            } else {
                x = x - (mFabOriginalX - x) / 4f;
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                hideLollipopImpl();
            } else {
                hideDefaultImpl();
            }

            animate().x(x).setDuration(CIRCULAR_UNREVEAL_DURATION).setStartDelay(CIRCULAR_UNREVEAL_DELAY)
                    .setInterpolator(new AccelerateDecelerateInterpolator());

            if (mMenuLayout != null) {
                mMenuLayout.animate().alpha(0f).scaleX(0.7f).setStartDelay(CIRCULAR_UNREVEAL_DELAY)
                        .setDuration(MENU_ANIMATION_DURATION / 2);
            }
            if (mCustomView != null) {
                mCustomView.animate().alpha(0f).scaleX(0.7f).setStartDelay(CIRCULAR_UNREVEAL_DELAY)
                        .setDuration(CIRCULAR_UNREVEAL_DURATION);
            }
        }
    }

    private void addMenuItems() {

        if (mMenu == null) {
            mMenu = new MenuBuilder(getContext());
            new SupportMenuInflater(getContext()).inflate(mMenuRes, mMenu);
        }

        LinearLayoutCompat.LayoutParams layoutParams = new LinearLayoutCompat.LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT, 1);

        setWeightSum(mMenu.size());

        for (int i = 0; i < mMenu.size(); i++) {
            MenuItem item = mMenu.getItem(i);
            AppCompatImageButton imageButton = new AppCompatImageButton(getContext());
            imageButton.setId(genViewId());
            imageButton.setBackgroundResource(mItemBackground);
            imageButton.setImageDrawable(item.getIcon());
            imageButton.setOnClickListener(this);
            imageButton.setOnLongClickListener(this);
            mMenuLayout.addView(imageButton, layoutParams);
        }

    }

    @Override
    public void onClick(View v) {
        if (!mMorphed) {
            return;
        }

        hide();

        if (mClickListener != null) {
            MenuItem item = mMenu.getItem(mMenuLayout.indexOfChild(v));
            mClickListener.onItemClick(item);
        }
    }

    @Override
    public boolean onLongClick(View v) {
        if (!mMorphed) {
            return false;
        }
        if (mClickListener != null) {
            MenuItem item = mMenu.getItem(mMenuLayout.indexOfChild(v));
            mClickListener.onItemLongClick(item);
            return true;
        } else {
            return false;
        }
    }

    private void showDefaultImpl() {
        int rootWidth = mRoot.getWidth();
        setScaleX(0f);

        float endFabX;

        if (mFabOriginalX > rootWidth / 2f) {
            endFabX = rootWidth / 2f + (mFabOriginalX - rootWidth / 2f) / 4f;
        } else {
            endFabX = rootWidth / 2f - (mFabOriginalX - rootWidth / 2f) / 4f;
        }

        if (mFab != null) {
            PropertyValuesHolder xProperty = PropertyValuesHolder.ofFloat(X, endFabX);
            PropertyValuesHolder yProperty = PropertyValuesHolder.ofFloat(Y, mFabOriginalY * 1.05f);
            PropertyValuesHolder scaleXProperty = PropertyValuesHolder.ofFloat(SCALE_X, 0);
            PropertyValuesHolder scaleYProperty = PropertyValuesHolder.ofFloat(SCALE_Y, 0);

            ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mFab, xProperty, yProperty,
                    scaleXProperty, scaleYProperty);
            animator.setDuration(FAB_MORPH_DURATION);
            animator.setInterpolator(new AccelerateInterpolator());
            animator.start();
        }

        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "scaleX", 1f);
        objectAnimator.setDuration(CIRCULAR_REVEAL_DURATION);
        objectAnimator.setStartDelay(CIRCULAR_REVEAL_DELAY);
        objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        objectAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                setVisibility(View.VISIBLE);
                mFab.setVisibility(View.INVISIBLE);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mMorphing = false;
            }
        });
        objectAnimator.start();
    }

    private void hideDefaultImpl() {
        ViewCompat.animate(mFab).x(mFabOriginalX).y(mFabOriginalY).scaleX(1f).scaleY(1f)
                .setStartDelay(FAB_UNMORPH_DELAY).setDuration(FAB_UNMORPH_DURATION)
                .setInterpolator(new AccelerateInterpolator())
                .setListener(new ViewPropertyAnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(View view) {
                        super.onAnimationStart(view);
                        mFab.setVisibility(View.VISIBLE);
                        ViewCompat.animate(mFab).setListener(null);
                    }
                });

        ViewCompat.animate(this).scaleX(0f).setDuration(CIRCULAR_UNREVEAL_DURATION)
                .setStartDelay(CIRCULAR_UNREVEAL_DELAY).setInterpolator(new AccelerateDecelerateInterpolator())
                .setListener(new ViewPropertyAnimatorListenerAdapter() {

                    @Override
                    public void onAnimationEnd(View view) {
                        super.onAnimationEnd(view);
                        setVisibility(View.INVISIBLE);
                        ViewCompat.animate(FloatingToolbar.this).setListener(null);
                        mMorphing = false;
                    }
                });
    }

    @TargetApi(21)
    private void showLollipopImpl() {
        int rootWidth = mRoot.getWidth();

        float endFabX;
        float controlX;

        if (mFabOriginalX > rootWidth / 2f) {
            endFabX = rootWidth / 2f + (mFabOriginalX - rootWidth / 2f) / 4f;
            controlX = mFabOriginalX * 0.98f;
        } else {
            endFabX = rootWidth / 2f - (mFabOriginalX - rootWidth / 2f) / 4f;
            controlX = mFabOriginalX * 1.02f;
        }

        /**
         * Animate FAB movement
         */
        final Path path = new Path();
        path.moveTo(mFab.getX(), mFab.getY());
        final float x2 = controlX;
        final float y2 = getY();
        path.quadTo(x2, y2, endFabX, getY());
        ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, View.X, View.Y, path);
        anim.setInterpolator(new AccelerateDecelerateInterpolator());
        anim.setDuration(FAB_MORPH_DURATION);
        anim.start();

        /**
         * Fade FAB drawable
         */
        Drawable drawable = mFab.getDrawable();
        if (drawable != null) {
            anim = ObjectAnimator.ofPropertyValuesHolder(drawable, PropertyValuesHolder.ofInt("alpha", 0));
            anim.setInterpolator(new AccelerateDecelerateInterpolator());
            anim.setDuration((long) (FAB_MORPH_DURATION / 3f));
            anim.start();
        }

        /**
         * Animate FAB elevation to 8dp
         */
        anim = ObjectAnimator.ofFloat(mFab, View.TRANSLATION_Z, dpToPixels(2));
        anim.setInterpolator(new AccelerateDecelerateInterpolator());
        anim.setDuration(FAB_MORPH_DURATION);
        anim.start();

        /**
         * Create circular reveal
         */
        Animator toolbarReveal = ViewAnimationUtils.createCircularReveal(this, getWidth() / 2, getHeight() / 2,
                (float) mFab.getWidth() / 2f, (float) (Math.hypot(getWidth() / 2, getHeight() / 2)));

        toolbarReveal.setDuration(CIRCULAR_REVEAL_DURATION);
        toolbarReveal.setTarget(this);
        toolbarReveal.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                mFab.setVisibility(View.INVISIBLE);
                setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mMorphing = false;
            }
        });

        toolbarReveal.setInterpolator(new AccelerateInterpolator());
        toolbarReveal.setStartDelay(CIRCULAR_REVEAL_DELAY);
        toolbarReveal.start();

        /**
         * Animate FloatingToolbar elevation to 8dp
         */
        anim = ObjectAnimator.ofFloat(this, View.TRANSLATION_Z, dpToPixels(2));
        anim.setDuration(CIRCULAR_REVEAL_DURATION);
        anim.setStartDelay(CIRCULAR_REVEAL_DELAY);
        anim.start();
    }

    @TargetApi(21)
    private void hideLollipopImpl() {
        int rootWidth = mRoot.getWidth();

        float controlX;

        if (mFabOriginalX > rootWidth / 2f) {
            controlX = mFabOriginalX * 0.98f;
        } else {
            controlX = mFabOriginalX * 1.02f;
        }

        final Path path = new Path();
        path.moveTo(mFab.getX(), mFab.getY());
        final float x2 = controlX;
        final float y2 = getY();
        path.quadTo(x2, y2, mFabOriginalX, mFabOriginalY + getTranslationY());
        ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, View.X, View.Y, path);
        anim.setInterpolator(new AccelerateDecelerateInterpolator());
        anim.setDuration(FAB_UNMORPH_DURATION);
        anim.setStartDelay(FAB_UNMORPH_DELAY);
        anim.start();

        /**
         * Animate FAB elevation back to 6dp
         */
        anim = ObjectAnimator.ofFloat(mFab, View.TRANSLATION_Z, 0);
        anim.setInterpolator(new AccelerateDecelerateInterpolator());
        anim.setDuration(FAB_UNMORPH_DURATION);
        anim.setStartDelay(FAB_UNMORPH_DELAY);
        anim.start();

        /**
         * Restore alpha of FAB drawable
         */
        Drawable drawable = mFab.getDrawable();
        if (drawable != null) {
            anim = ObjectAnimator.ofPropertyValuesHolder(drawable, PropertyValuesHolder.ofInt("alpha", 255));
            anim.setInterpolator(new AccelerateDecelerateInterpolator());
            anim.setDuration(FAB_UNMORPH_DURATION);
            anim.setStartDelay(FAB_UNMORPH_DELAY);
            anim.start();
        }

        Animator toolbarReveal = ViewAnimationUtils.createCircularReveal(this, getWidth() / 2, getHeight() / 2,
                (float) (Math.hypot(getWidth() / 2, getHeight() / 2)), (float) mFab.getWidth() / 2f);

        toolbarReveal.setTarget(this);
        toolbarReveal.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                setVisibility(View.INVISIBLE);
                mFab.setVisibility(View.VISIBLE);
                mMorphing = false;
            }
        });
        toolbarReveal.setDuration(CIRCULAR_UNREVEAL_DURATION);
        toolbarReveal.setInterpolator(new AccelerateInterpolator());
        toolbarReveal.setStartDelay(CIRCULAR_UNREVEAL_DELAY);
        toolbarReveal.start();

        /**
         * Animate FloatingToolbar animation back to 6dp
         */
        anim = ObjectAnimator.ofFloat(this, View.TRANSLATION_Z, 0);
        anim.setDuration(CIRCULAR_UNREVEAL_DURATION);
        anim.setStartDelay(CIRCULAR_UNREVEAL_DELAY);
        anim.start();
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState state = new SavedState(superState);
        state.morphed = mMorphed;
        return state;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        SavedState savedState = (SavedState) state;

        super.onRestoreInstanceState(savedState.getSuperState());

        if (savedState.morphed) {
            post(new Runnable() {
                @Override
                public void run() {
                    mRoot = getRootView();
                    mOriginalX = getX();
                    mFabOriginalY = mFab.getY();
                    mFabOriginalX = mFab.getX();
                    show();
                }
            });
        }
    }

    private float dpToPixels(int dp) {
        return Resources.getSystem().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT * dp;
    }

    // Generate unique view ids to save state properly
    private int genViewId() {
        if (Build.VERSION.SDK_INT < 17) {
            for (;;) {
                final int result = sNextGeneratedId.get();
                int newValue = result + 1;
                if (newValue > 0x00FFFFFF) {
                    newValue = 1;
                }
                if (sNextGeneratedId.compareAndSet(result, newValue)) {
                    return result;
                }
            }
        } else {
            return View.generateViewId();
        }
    }

    public interface ItemClickListener {
        void onItemClick(MenuItem item);

        void onItemLongClick(MenuItem item);
    }

    // FloatingActionButton.Behavior adapted
    public static class Behavior extends CoordinatorLayout.Behavior<FloatingToolbar> {

        private float mTranslationY;
        private ValueAnimator mTranslationYAnimator;

        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, FloatingToolbar child, View dependency) {
            return dependency instanceof Snackbar.SnackbarLayout;
        }

        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingToolbar child, View dependency) {

            if (dependency instanceof Snackbar.SnackbarLayout) {
                updateTranslationForSnackbar(parent, child);
            }
            return false;
        }

        private void updateTranslationForSnackbar(CoordinatorLayout parent, final FloatingToolbar layout) {

            final float targetTransY = getTranslationYForSnackbar(parent, layout);
            if (mTranslationY == targetTransY) {
                return;
            }

            final float currentTransY = ViewCompat.getTranslationY(layout);

            if (mTranslationYAnimator != null && mTranslationYAnimator.isRunning()) {
                mTranslationYAnimator.cancel();
            }

            if (Math.abs(currentTransY - targetTransY) > (layout.getHeight() * 0.667f)) {
                if (mTranslationYAnimator == null) {
                    mTranslationYAnimator = new ValueAnimator();
                    mTranslationYAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
                    mTranslationYAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animator) {
                            ViewCompat.setTranslationY(layout, animator.getAnimatedFraction());
                        }
                    });
                }
                mTranslationYAnimator.setFloatValues(currentTransY, targetTransY);
                mTranslationYAnimator.start();
            } else {
                ViewCompat.setTranslationY(layout, targetTransY);
            }

            mTranslationY = targetTransY;
        }

        private float getTranslationYForSnackbar(CoordinatorLayout parent, FloatingToolbar layout) {
            float minOffset = 0;
            final List<View> dependencies = parent.getDependencies(layout);
            for (int i = 0, z = dependencies.size(); i < z; i++) {
                final View view = dependencies.get(i);
                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(layout, view)) {
                    minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - view.getHeight());
                }
            }

            return minOffset;
        }
    }

    static class SavedState extends BaseSavedState {

        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
        public boolean morphed;

        public SavedState(Parcel source) {
            super(source);
            morphed = source.readByte() == 0x01;
        }

        public SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeByte((byte) (morphed ? 0x01 : 0x00));
        }
    }

}