Java tutorial
/** * Copyright 2016 YouVersion * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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 io.nuclei.cyto.ui.view; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.support.AnimationUtils; import android.support.ValueAnimatorCompat; import android.support.ViewUtils; import android.support.v4.view.MotionEventCompat; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.support.ViewOffsetHelper; @Deprecated public class BottomSheetView extends FrameLayout { public static final int STATE_UNKNOWN = 0; public static final int STATE_IDLE = 1 << 1; public static final int STATE_MOVING = 1 << 2; public static final int STATE_LOCKED = 1 << 3; public static final int STATE_OPENED = 1 << 4; public static final int STATE_CLOSED = 1 << 5; public static final int STATE_PARTIAL_OPENED = 1 << 6; static final int DEFAULT_BACKGROUND_COLOR = Color.parseColor("#88000000"); View mBackground; FrameLayout mContent; int mMinFlingVelocity; int mTouchSlop; int mLastState = STATE_UNKNOWN; int mState = STATE_UNKNOWN; int mMaxPosition = 0; int mBackgroundColor; boolean mReady; ViewOffsetHelper mOffsetHelper; ValueAnimatorCompat mAnimator; VelocityTracker mVelocityTracker; OnStateChangedListener mOnStateChangedListener; OnCanScrollListener mOnCanScrollListener; public BottomSheetView(Context context) { super(context); init(context); } public BottomSheetView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } @TargetApi(11) public BottomSheetView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } @TargetApi(21) public BottomSheetView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context); } private void init(Context context) { ViewConfiguration viewConfiguration = ViewConfiguration.get(context); mMinFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity(); mTouchSlop = viewConfiguration.getScaledTouchSlop(); setClickable(true); mBackground = new View(context); mBackground.setBackgroundColor(mBackgroundColor = DEFAULT_BACKGROUND_COLOR); mBackground.setLayoutParams( new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mBackground.setVisibility(GONE); mBackground.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if ((mState & STATE_MOVING) != STATE_MOVING) { if ((mState & STATE_LOCKED) == STATE_LOCKED) removeState(STATE_LOCKED); setState(STATE_CLOSED); } } }); addView(mBackground); FrameLayout content = new FrameLayout(context); content.setLayoutParams( new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); content.setMinimumHeight(1); content.setBackgroundDrawable(new ColorDrawable(Color.WHITE)); content.setClickable(true); addView(content); mContent = content; mOffsetHelper = new ViewOffsetHelper(mContent); onPrepare(); } public void setBackgroundColor(int color) { mBackgroundColor = color; mBackground.setBackgroundColor(color); } public boolean isReady() { return mReady; } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); onPrepare(); } private void onPrepare() { if (getVisibility() == VISIBLE && !mReady) { if (getHeight() == 0) mContent.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { onReady(); return true; } }); else onReady(); } } private void onReady() { if (!mReady) { mReady = true; if (mState == STATE_UNKNOWN) { mOffsetHelper.setTopAndBottomOffset(getHeight()); mState = STATE_IDLE | STATE_CLOSED; } if (mOnStateChangedListener != null) mOnStateChangedListener.onStateChanged(mState); } } public void setOnStateChangedListener(OnStateChangedListener listener) { mOnStateChangedListener = listener; } public void setOnCanScrollListener(OnCanScrollListener listener) { mOnCanScrollListener = listener; } @Override public void addView(View child, int index) { if (mContent == null) super.addView(child, index); else mContent.addView(child, index); } @Override public void addView(View child) { if (mContent == null) super.addView(child); else mContent.addView(child); } @Override public void addView(View child, int width, int height) { if (mContent == null) super.addView(child, width, height); else mContent.addView(child, width, height); } @Override public void addView(View child, ViewGroup.LayoutParams params) { if (mContent == null) super.addView(child, params); else mContent.addView(child, params); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { if (mContent == null) super.addView(child, index, params); else mContent.addView(child, index, params); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mVelocityTracker = VelocityTracker.obtain(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mAnimator = null; } public void setLocked(boolean locked) { if (locked) { if ((mState & STATE_MOVING) == STATE_MOVING) throw new IllegalStateException("Can't lock while moving"); if ((mState & STATE_LOCKED) != STATE_LOCKED) { mLastState = mState; addState(STATE_LOCKED); if (mOnStateChangedListener != null) mOnStateChangedListener.onStateChanged(mState); } } else { if ((mState & STATE_MOVING) == STATE_MOVING) throw new IllegalStateException("Can't unlock while moving"); if ((mState & STATE_LOCKED) == STATE_LOCKED) { mLastState = mState; removeState(STATE_LOCKED); if (mOnStateChangedListener != null) mOnStateChangedListener.onStateChanged(mState); } } } private void addState(int state) { if ((mState & state) != state) mState |= state; updateBackground(); } private void replaceState(int remove, int add) { removeState(remove); addState(add); } private void removeState(int state) { if ((mState & state) == state) mState ^= state; updateBackground(); } private void updateBackground() { if (mBackground != null) { if ((mState & STATE_CLOSED) == STATE_CLOSED && mBackground.getVisibility() == VISIBLE) { if (Build.VERSION.SDK_INT >= 12) { mBackground.animate().alpha(0).setDuration(300).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mBackground.setVisibility(GONE); if (Build.VERSION.SDK_INT >= 11) mBackground.setAlpha(1); if (mOnStateChangedListener != null) mOnStateChangedListener.onBackgroundChanged(false); } }); } else { mBackground.setVisibility(GONE); if (mOnStateChangedListener != null) mOnStateChangedListener.onBackgroundChanged(false); } } else if ((mState & STATE_CLOSED) != STATE_CLOSED && mBackground.getVisibility() != VISIBLE) { mBackground.setBackgroundColor(mBackgroundColor); mBackground.setVisibility(VISIBLE); if (Build.VERSION.SDK_INT >= 12) { if (mBackground.getAlpha() == 1) mBackground.setAlpha(0); mBackground.animate().alpha(1).setDuration(300).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mOnStateChangedListener != null) mOnStateChangedListener.onBackgroundChanged(true); } }); } else { if (mOnStateChangedListener != null) mOnStateChangedListener.onBackgroundChanged(true); } } } } public int getState() { return mState; } public void setState(int state) { if (state == STATE_OPENED) setPosition(0); else if (state == STATE_CLOSED) setPosition(getHeight()); else throw new IllegalArgumentException("Invalid state, only OPENED and CLOSED supported"); } public void setPositionToContent(final float maxHeightPercent, final boolean lock) { mContent.requestLayout(); mContent.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { mContent.getViewTreeObserver().removeOnPreDrawListener(this); int maxHeight = (int) (getHeight() * maxHeightPercent); int height = mContent.getHeight(); height = getHeight() - Math.min(maxHeight, height); setPosition(height, lock); return true; } }); } public void setPositionToContent(final int maxHeight, final boolean lock) { mContent.requestLayout(); mContent.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { mContent.getViewTreeObserver().removeOnPreDrawListener(this); int height = mContent.getHeight(); height = getHeight() - Math.min(maxHeight, height); setPosition(height, lock); return true; } }); } public void setMaxPosition(int maxPosition) { mMaxPosition = maxPosition; } public void setPosition(float heightPercent) { setPosition((int) (getHeight() - getHeight() * heightPercent)); } public void setPosition(float heightPercent, boolean lock) { setPosition((int) (getHeight() - getHeight() * heightPercent), lock); } public void setPosition(int height) { setPosition(height, false); } public void setPosition(int height, boolean lock) { if ((mState & STATE_LOCKED) == STATE_LOCKED) { throw new IllegalStateException("Can't change position while locked"); } if ((mState & STATE_MOVING) == STATE_MOVING) return; if (height < 0) height = 0; if (height > getHeight()) height = getHeight(); if (mMaxPosition != 0) height = Math.max(mMaxPosition, height); final int distance = Math.abs(mOffsetHelper.getTopAndBottomOffset() - height); if (distance == 0) return; if (mAnimator == null) { mAnimator = ViewUtils.createAnimator(); mAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimatorCompat animator) { mOffsetHelper.setTopAndBottomOffset(animator.getAnimatedIntValue()); } }); mAnimator.setListener(new ValueAnimatorCompat.AnimatorListener() { @Override public void onAnimationStart(ValueAnimatorCompat animator) { } @Override public void onAnimationEnd(ValueAnimatorCompat animator) { if ((mState & STATE_MOVING) == STATE_MOVING) { replaceState(STATE_MOVING, STATE_IDLE); final int top = mOffsetHelper.getTopAndBottomOffset(); if (top == 0 || top == mMaxPosition) { removeState(STATE_CLOSED); replaceState(STATE_PARTIAL_OPENED, STATE_OPENED); } else if (top == getHeight()) { removeState(STATE_OPENED); replaceState(STATE_PARTIAL_OPENED, STATE_CLOSED); } if (mOnStateChangedListener != null) mOnStateChangedListener.onStateChanged(mState); } } @Override public void onAnimationCancel(ValueAnimatorCompat animator) { } }); } else { mAnimator.cancel(); } mLastState = mState; if (lock) addState(STATE_LOCKED); removeState(STATE_OPENED); removeState(STATE_CLOSED); replaceState(STATE_IDLE, STATE_MOVING); if (mOnStateChangedListener != null) mOnStateChangedListener.onStateChanged(mState); final float density = getResources().getDisplayMetrics().density; final float duration = Math.abs(mOffsetHelper.getTopAndBottomOffset() - height) / density; int time = 300; if (mOffsetHelper.getTopAndBottomOffset() < height) time *= 2; mAnimator.setDuration(Math.round(duration * 1000 / time)); mAnimator.setIntValues(mOffsetHelper.getTopAndBottomOffset(), height); mAnimator.start(); } float mStartY; float mLastY; boolean mInterceptTouchEvents; @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (getVisibility() != VISIBLE) return false; if (event.getY() < mOffsetHelper.getTopAndBottomOffset()) { return false; } if ((mState & STATE_LOCKED) == STATE_LOCKED) return false; switch (MotionEventCompat.getActionMasked(event)) { case MotionEvent.ACTION_DOWN: mLastY = mStartY = event.getY(); mInterceptTouchEvents = false; if (mAnimator != null) mAnimator.cancel(); break; case MotionEvent.ACTION_MOVE: if (Math.abs(event.getY() - mStartY) > mTouchSlop) { mInterceptTouchEvents = true; } break; case MotionEvent.ACTION_CANCEL: mVelocityTracker.clear(); mInterceptTouchEvents = false; break; } if ((mState & STATE_OPENED) == STATE_OPENED) { if (mLastY > event.getY() || canScrollUp()) { return false; } } return mInterceptTouchEvents; } protected boolean canScrollUp() { if (mOnCanScrollListener != null) return mOnCanScrollListener.canScrollUp(); int len = getChildCount(); for (int i = 0; i < len; i++) { if (ViewUtils.canScrollUp(getChildAt(i))) return true; } return false; } @Override public boolean onTouchEvent(MotionEvent event) { if ((mState & STATE_LOCKED) == STATE_LOCKED) return false; if (mAnimator != null && mAnimator.isRunning()) return false; boolean velocityTracked = false; switch (MotionEventCompat.getActionMasked(event)) { case MotionEvent.ACTION_DOWN: mVelocityTracker.clear(); if (mAnimator != null) mAnimator.cancel(); mLastY = mStartY = event.getY(); break; case MotionEvent.ACTION_UP: mVelocityTracker.computeCurrentVelocity(1000); float velocityY = mVelocityTracker.getYVelocity(); if (Math.abs(velocityY) > mMinFlingVelocity) { float dist = mStartY - event.getY(); if (dist > 0) setState(STATE_OPENED); else setState(STATE_CLOSED); } break; case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); velocityTracked = true; float diff = event.getY() - mLastY; mLastY = event.getY(); int offset = (int) (mOffsetHelper.getTopAndBottomOffset() + diff); int height = getHeight(); if (offset < 0) offset = 0; if (offset > height) offset = height; if (mMaxPosition != 0) offset = Math.max(mMaxPosition, offset); mOffsetHelper.setTopAndBottomOffset(offset); mLastState = mState; if (offset == 0 || offset == mMaxPosition) { removeState(STATE_CLOSED); replaceState(STATE_PARTIAL_OPENED, STATE_OPENED); } else if (offset == height) { removeState(STATE_OPENED); replaceState(STATE_PARTIAL_OPENED, STATE_CLOSED); } else { removeState(STATE_OPENED); removeState(STATE_CLOSED); addState(STATE_PARTIAL_OPENED); } if (mState != mLastState && mOnStateChangedListener != null) mOnStateChangedListener.onStateChanged(mState); break; case MotionEvent.ACTION_CANCEL: mVelocityTracker.clear(); mInterceptTouchEvents = false; break; } if (!velocityTracked) { mVelocityTracker.addMovement(event); } return true; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mOffsetHelper.onViewLayout(); } public interface OnStateChangedListener { void onStateChanged(int state); void onBackgroundChanged(boolean visible); } public interface OnCanScrollListener { boolean canScrollUp(); } }