/* * Copyright (c) 2016 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com> * All Rights Reserved. */ package me.zhanghai.android.douya.profile.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.support.v4.view.animation.FastOutLinearInInterpolator; import android.support.v4.view.animation.LinearOutSlowInInterpolator; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewGroup; import android.view.WindowInsets; import butterknife.BindColor; import butterknife.BindInt; import butterknife.ButterKnife; import me.zhanghai.android.douya.R; import me.zhanghai.android.douya.profile.util.ProfileUtils; import me.zhanghai.android.douya.ui.FlexibleSpaceLayout; import me.zhanghai.android.douya.ui.IntProperty; import me.zhanghai.android.douya.util.AppUtils; import me.zhanghai.android.douya.util.ColorUtils; import me.zhanghai.android.douya.util.StatusBarColorUtils; import me.zhanghai.android.douya.util.ViewUtils; public class ProfileLayout extends FlexibleSpaceLayout { @BindInt(android.R.integer.config_shortAnimTime) int mShortAnimationTime; @BindColor(R.color.system_window_scrim) int mStatusBarColor; @BindColor(R.color.dark_70_percent) int mBackgroundColor; private ViewGroup mOffsetContainer; private ProfileHeaderLayout mProfileHeaderLayout; private boolean mUseWideLayout; private boolean mExiting; private ColorDrawable mWindowBackground; private Listener mListener; public ProfileLayout(Context context) { super(context); init(); } public ProfileLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ProfileLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public ProfileLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { ButterKnife.bind(this); Context context = getContext(); mUseWideLayout = ProfileUtils.shouldUseWideLayout(context); ViewUtils.setLayoutFullscreen(this); mWindowBackground = new ColorDrawable(mBackgroundColor); AppUtils.getActivityFromContext(context).getWindow() .setBackgroundDrawable(mWindowBackground); } @Override protected void onFinishInflate() { super.onFinishInflate(); // HACK: Coupled with specific XML hierarchy. mOffsetContainer = (ViewGroup) getChildAt(0); mProfileHeaderLayout = (ProfileHeaderLayout) mOffsetContainer.getChildAt(0); } @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) @Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); mProfileHeaderLayout.setInsetTop(insets.getSystemWindowInsetTop()); return insets.consumeSystemWindowInsets(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height = MeasureSpec.getSize(heightMeasureSpec); int headerMaxHeight = mUseWideLayout ? height : height * 2 / 3; mProfileHeaderLayout.setMaxHeight(headerMaxHeight); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public int getOffset() { // View.offsetTopAndBottom causes transient invalid layout position when animating in. return (int) mOffsetContainer.getTranslationY(); } public void offsetTo(int offset) { if (offset < 0) { offset = 0; } if (getOffset() == offset) { return; } mOffsetContainer.setTranslationY(offset); updateStatusBarAndWindowBackground(offset); } public void offsetBy(int delta) { offsetTo(getOffset() + delta); } private void updateStatusBarAndWindowBackground(int offset) { float fraction = Math.max(0, 1 - (float) offset / getHeight()); int alpha = (int) (fraction * 0xFF); int statusBarColor = ColorUtils.blendAlphaComponent(mStatusBarColor, alpha); StatusBarColorUtils.set(statusBarColor, AppUtils.getActivityFromContext(getContext())); mWindowBackground.setAlpha(alpha); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (mExiting) { return false; } return super.onInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { if (mExiting) { return false; } return super.onTouchEvent(event); } @Override public boolean onGenericMotionEvent(MotionEvent event) { if (mExiting) { return false; } return super.onGenericMotionEvent(event); } @Override protected void onDrag(MotionEvent event, float delta) { if (delta > 0) { int oldScroll = getScroll(); scrollBy((int) -delta); delta += getScroll() - oldScroll; offsetBy((int) delta); } else { int oldOffset = getOffset(); offsetBy((int) delta); delta -= getOffset() - oldOffset; int oldScroll = getScroll(); scrollBy((int) -delta); delta += getScroll() - oldScroll; if (delta < 0) { pullEdgeEffectBottom(event, delta); } } } @Override protected void onDragEnd(boolean cancelled) { if (getOffset() > 0) { exit(); } else { super.onDragEnd(cancelled); } } @Override protected boolean shouldDrawEdgeEffectBottom() { return !mUseWideLayout; } public Listener getListener() { return mListener; } public void setListener(Listener listener) { mListener = listener; } public void enter() { ViewUtils.postOnPreDraw(this, new Runnable() { @Override public void run() { animateEnter(); } }); } private void animateEnter() { ObjectAnimator animator = ObjectAnimator.ofInt(this, OFFSET, getHeight(), 0); animator.setDuration(mShortAnimationTime); animator.setInterpolator(new LinearOutSlowInInterpolator()); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mListener != null) { mListener.onEnterAnimationEnd(); } } }); animator.start(); } public void exit() { if (mExiting) { return; } mExiting = true; abortScrollerAnimation(); recycleVelocityTrackerIfHas(); animateExit(); } private void animateExit() { int offset = getOffset(); int height = getHeight(); if (offset >= height) { mListener.onExitAnimationEnd(); return; } ObjectAnimator animator = ObjectAnimator.ofInt(this, OFFSET, offset, height); animator.setDuration(mShortAnimationTime); animator.setInterpolator(new FastOutLinearInInterpolator()); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mListener != null) { mListener.onExitAnimationEnd(); } } }); animator.start(); } public static final IntProperty<ProfileLayout> OFFSET = new IntProperty<ProfileLayout>("offset") { @Override public Integer get(ProfileLayout object) { return object.getOffset(); } @Override public void setValue(ProfileLayout object, int value) { object.offsetTo(value); } }; public interface Listener { void onEnterAnimationEnd(); void onExitAnimationEnd(); } }