io.nuclei.cyto.ui.view.BottomSheetView.java Source code

Java tutorial

Introduction

Here is the source code for io.nuclei.cyto.ui.view.BottomSheetView.java

Source

/**
 * 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();
    }

}