package com.koushikdutta.boilerplate; import android.animation.Animator; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.widget.AbsListView; import android.widget.FrameLayout; import com.koushikdutta.boilerplate.recyclerview.IHeaderRecyclerView; import com.koushikdutta.boilerplate.tint.TintHelper; /** * Created by koush on 4/4/15. */ public class ScrollingToolbarLayout extends FrameLayout { public ScrollingToolbarLayout(Context context) { super(context); init(context, null, 0); } public ScrollingToolbarLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0); } public ScrollingToolbarLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs, defStyle); } private void init(Context context, AttributeSet attrs, int defStyle) { colorPrimary = TintHelper.getColorPrimary(context); colorFaded = 0; colorPrimaryDark = TintHelper.getStyledColor(context, R.attr.colorPrimaryDark); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) currentStatusBarColor = colorPrimaryDark; else currentStatusBarColor = colorPrimary; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (scrollOffEnabled) return; if (getChildCount() == 0) throw new RuntimeException(getClass().getSimpleName() + " must contain at least one child View."); if (getChildCount() > 3) throw new RuntimeException(getClass().getSimpleName() + " ay only contain a maxmimum of 3 Views. Backdrop, Content, and Toolbar, in that order."); View toolbar = getChildAt(getChildCount() - 1); int contentIndex = getChildCount() == 3 ? 1 : 0; View content = getChildAt(contentIndex); int height = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height - toolbar.getMeasuredHeight(), heightMode); content.measure(widthMeasureSpec, newHeightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (getChildCount() == 0) throw new RuntimeException(getClass().getSimpleName() + " must contain at least one child View."); if (getChildCount() > 3) throw new RuntimeException(getClass().getSimpleName() + " ay only contain a maxmimum of 3 Views. Backdrop, Content, and Toolbar, in that order."); View toolbar = getChildAt(getChildCount() - 1); toolbar.layout(l, t, r, t + toolbar.getMeasuredHeight()); int newt; if (scrollOffEnabled) newt = t; else newt = t + (toolbar != null ? toolbar.getMeasuredHeight() : 0); int curChild = 0; if (getChildCount() == 3) { View child = getChildAt(curChild++); child.layout(l, newt, r, child.getMeasuredHeight()); } View content = getChildAt(curChild); content.layout(l, newt, r, b); } boolean scrollOffEnabled; public void enableToolbarScrollOff(final IHeaderRecyclerView headerRecyclerView, final Fragment fragment) { scrollOffEnabled = true; int extra; View paddingView; if (getChildCount() == 3) paddingView = getChildAt(0); else paddingView = getChildAt(getChildCount() - 1); if (paddingView.getLayoutParams().height > 0) { extra = paddingView.getLayoutParams().height; } else { // apparently this is the max size allowed final int SIZE_MAX = 1073741823; paddingView.measure(MeasureSpec.makeMeasureSpec(SIZE_MAX, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(SIZE_MAX, MeasureSpec.AT_MOST)); extra = paddingView.getMeasuredHeight(); } AbsListView.LayoutParams lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, extra); FrameLayout frameLayout = new FrameLayout(getContext()); frameLayout.setLayoutParams(lp); headerRecyclerView.addHeaderView(0, frameLayout); headerRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView absListView, int scrollState) { // when scrolling stops, and the toolbar is only partially scrolled off, force to scroll bar in completely if (scrollState != RecyclerView.SCROLL_STATE_IDLE) return; if (absListView.getChildCount() < 1) return; int firstVisibleItem = headerRecyclerView.findFirstVisibleItemPosition(); if (firstVisibleItem != 0) return; final View toolbarContainer = getChildAt(getChildCount() - 1); if (toolbarContainer.getTranslationY() <= -toolbarContainer.getHeight() || (existingToolbarYAnimation != null && existingToolbarYEnd <= toolbarContainer.getHeight())) return; toolbarScrollIn(); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (recyclerView.getChildCount() < 1) return; if (fragment != null && !fragment.getUserVisibleHint()) return; // cancelToolbarScroll(); final View firstView = recyclerView.getChildAt(0); final View toolbarContainer = getChildAt(getChildCount() - 1); final View backdrop; if (getChildCount() == 3) backdrop = getChildAt(0); else backdrop = null; final int toolbarHeight = toolbarContainer.getHeight(); int firstVisibleItem = headerRecyclerView.findFirstVisibleItemPosition(); if (backdrop != null) { int newBackdropHeight; int backdropHeight = getResources().getDimensionPixelSize(R.dimen.icon_list_drawer_activity_backdrop_height); if (firstVisibleItem >= 1) { newBackdropHeight = toolbarHeight; } else { newBackdropHeight = firstView.getTop() + backdropHeight; } newBackdropHeight = Math.max(newBackdropHeight, toolbarHeight); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) backdrop.getLayoutParams(); lp.height = newBackdropHeight; // another option is to use y translation to not do parallax backdrop.setLayoutParams(lp); if (newBackdropHeight / (float) backdropHeight < .5f) { toolbarFadeToPrimary(); } else { toolbarFadeToTranslucent(); } } if (firstVisibleItem == 0) { int remainder = firstView.getHeight() + firstView.getTop(); // if there's less than toolbar height left, start scrolling off. if (remainder < toolbarHeight) { remainder = toolbarHeight - remainder; if (existingToolbarYAnimation == null) { float diff = -remainder - toolbarContainer.getTranslationY(); if (false && Math.abs(diff) > toolbarHeight / 4) { if (toolbarContainer.getTranslationY() < -remainder) { // scrolling down toolbarScrollIn(); } else { // scrolling up toolbarScrollOut(); } } else { toolbarContainer.setTranslationY(-remainder); } } if (backdrop != null) backdrop.setTranslationY(-remainder); } else { cancelToolbarScroll(); toolbarContainer.setTranslationY(0); // toolbarScrollIn(); if (backdrop != null) backdrop.setTranslationY(0); } return; } if (firstVisibleItem == 1) { cancelToolbarScroll(); toolbarContainer.setTranslationY(-toolbarHeight); } else { toolbarScrollOut(); } if (backdrop != null) backdrop.setTranslationY(-toolbarHeight); } }); if (getChildCount() == 3) toolbarFadeToTranslucent(); } boolean isPrimary = true; int colorPrimary; int colorPrimaryDark; int colorFaded; int currentStatusBarColor; ObjectAnimator existingToolbarColorAnimation; ValueAnimator existingStatusBarAnimation; ViewPropertyAnimator existingToolbarYAnimation; float existingToolbarYEnd; public void toolbarScrollOut() { final View toolbarContainer = getChildAt(getChildCount() - 1); toolbarScrollTo(-toolbarContainer.getHeight()); } public void toolbarScrollIn() { toolbarScrollTo(0); } void cancelToolbarScroll() { if (existingToolbarYAnimation != null) { existingToolbarYAnimation.cancel(); existingStatusBarAnimation = null; } } void toolbarScrollTo(int yTrans) { final View toolbarContainer = getChildAt(getChildCount() - 1); if (toolbarContainer.getTranslationY() == yTrans || (existingToolbarYAnimation != null && existingToolbarYEnd == yTrans)) return; cancelToolbarScroll(); existingToolbarYEnd = yTrans; existingToolbarYAnimation = toolbarContainer.animate().translationY(yTrans); existingToolbarYAnimation.setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { existingToolbarYAnimation = null; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); existingToolbarYAnimation.start(); } void toolbarFadeToPrimary() { if (isPrimary) return; toolbarFadeToColor(colorPrimary); isPrimary = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) statusBarFadeToColor(colorPrimaryDark); } void toolbarFadeToTranslucent() { if (!isPrimary) return; toolbarFadeToColor(colorFaded); isPrimary = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) statusBarFadeToColor(0x9A000000); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) void statusBarFadeToColor(int color) { existingStatusBarAnimation = WindowChromeUtils.statusBarFadeToColor(getContext(), existingStatusBarAnimation, color); if (existingStatusBarAnimation == null) return; existingStatusBarAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { currentStatusBarColor = (int) animation.getAnimatedValue(); } }); } void toolbarFadeToColor(int color) { if (existingToolbarColorAnimation != null) existingToolbarColorAnimation.cancel(); View toolbarContainer = getChildAt(getChildCount() - 1); int existingColor = ((ColorDrawable)toolbarContainer.getBackground()).getColor(); existingToolbarColorAnimation = ObjectAnimator.ofInt(toolbarContainer, "backgroundColor", existingColor, color); existingToolbarColorAnimation.setEvaluator(new ArgbEvaluator()); existingToolbarColorAnimation.setDuration(500); existingToolbarColorAnimation.start(); } }