ViewPicker

ViewPicker

package com.xunrui.camera.ui.commonui.viewpicker;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.OverScroller;

import androidx.core.view.ViewCompat;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FloatValueHolder;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import com.xunrui.camera.R;

public class ViewPicker extends ViewGroup implements View.OnClickListener {
    private static final float INFLEXION = 0.35f;
    private static final float mPhysicalCoeff = 9.80665f * 39.37f * 1 * 160 * 0.84f;
    private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
    private static final float OVER_DRAGGER_FRICTION = 0.2f;
    private static final float OVER_DRAGGER_DAMPING_RATIO = 1f;
    private static final float OVER_DRAGGER_STIFFNESS = 200f;

    static final double STANDARD_VAL = 1.0704514448933802d;

    public static final int HORIZONTAL = 0;
    public static final int VERTICAL = 1;

    private static final int SCROLL_STATUS_IDLE = 0;
    private static final int SCROLL_STATUS_TOUCH_DRAG = 1;
    private static final int SCROLL_STATUS_OVER_DRAG = 2;
    private static final int SCROLL_STATUS_INERTIA = 3;
    private static final int SCROLL_STATUS_SNAP = 4;
    private static final int SCROLL_STATUS_SMOOTH = 5;

    private static int VELOCITY_UNIT = 200;
    private static float SCROLL_FRICTION = 0.005F;
    private static float MILLISECONDS_PER_INCH = 100f;
    private static float SMOOTH_PARAM = 300f;
    private static int SCALED_TOUCH_SLOP;

    private final int ITEM_MEASURE_UNSPECIFIED_SPEC = MeasureSpec.makeMeasureSpec(4000, MeasureSpec.UNSPECIFIED);
    private final int ITEM_MEASURE_AT_MOST_SPEC = MeasureSpec.makeMeasureSpec(4000, MeasureSpec.AT_MOST);

    private float mCenterScale;
    private int mDivider;
    private final int mOverDragLen = 69;
    private int mOrientation;

    private float mDownX;
    private float mDownY;
    private int mScrollWhenDown;
    private int mLastMoveX;
    private int mLastMoveY;
    private final int mMinScroll = 0;
    private int mMaxScroll;
    private int[] mChildNormalSize = new int[0];
    private int[] mChildFocusSize = new int[0];
    private int mMaxItemSize = 0;
    private final SparseArray<Float> mScaleArray = new SparseArray<>(2);
    private int mFocusViewIndex = 0;
    private float mFocusWidth = 0;
    private float mFocusHeight = 0;

    private VelocityTracker mVelocityTracker;
    private int mMinFlingVelocity;
    private int mMaxFlingVelocity;
    private int mScrollPointerId;
    private boolean sIsInterrupt = false;
    private int mStatus = SCROLL_STATUS_IDLE;
    private boolean mIsClickedPicker = false;
    private boolean mIsClickingItem = false;
    private boolean mIsItemChanged = true;
    private int mFocusIndex;
    private int mLastFocusIndex;

    private ViewPickerAdapter mViewPickerAdapter = DEFAULT_ADAPTER;
    private ViewFlinger mViewFlinger = new ViewFlinger();
    private SpringAnimation mOverDragRecoverAnim;
    private OnViewPickerListener mOnViewPickerListener = DEFAULT_LISTENER;

    private static final ViewPickerAdapter DEFAULT_ADAPTER = new ViewPickerAdapter();
    private static final OnViewPickerListener DEFAULT_LISTENER = new OnViewPickerListener() {
    };

    public ViewPicker(Context context) {
        this(context, null);
    }

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

    public ViewPicker(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public ViewPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
        initParams(context);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewPicker);
        mCenterScale = ta.getFloat(R.styleable.ViewPicker_centerScale, 1f);
        mDivider = ta.getDimensionPixelSize(R.styleable.ViewPicker_itemDivider, 0);
        mOrientation = ta.getInt(R.styleable.ViewPicker_android_orientation, VERTICAL);
        ta.recycle();
    }

    private void initParams(Context context) {
        mVelocityTracker = VelocityTracker.obtain();
        ViewConfiguration vc = ViewConfiguration.get(context);
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();

        SCALED_TOUCH_SLOP = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

    public void setScrollConstByRecyclerView() {
        VELOCITY_UNIT = 1000;
        SCROLL_FRICTION = 0.015f;
        MILLISECONDS_PER_INCH = 100;
        SMOOTH_PARAM = 100;
        mMinFlingVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();

        mViewFlinger = new ViewFlinger();
    }

    public void setScrollConstSlowly() {
        VELOCITY_UNIT = 200;
        SCROLL_FRICTION = 0.005f;
        MILLISECONDS_PER_INCH = 100;
        SMOOTH_PARAM = 300;
        mMinFlingVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();

        mViewFlinger = new ViewFlinger();
    }

    public void setScrollConstQuickly() {
        VELOCITY_UNIT = 800;
        SCROLL_FRICTION = 1f;
        MILLISECONDS_PER_INCH = 10;
        SMOOTH_PARAM = 30;
        mMinFlingVelocity = getMinVelocity(SCROLL_FRICTION);

        mViewFlinger = new ViewFlinger();
    }

    private static int getMinVelocity(float friction) {
        double decelMinusOne = DECELERATION_RATE - 1.0;
        return (int) Math.ceil(Math.exp(Math.log(STANDARD_VAL / (friction * mPhysicalCoeff))
                * decelMinusOne / DECELERATION_RATE) * (friction * mPhysicalCoeff) / INFLEXION);
    }

    public void setAdapter(ViewPickerAdapter adapter) {
        if (adapter == null) {
            adapter = DEFAULT_ADAPTER;
        }
        mViewPickerAdapter = adapter;
        setWillNotDraw(mViewPickerAdapter.willNotDraw());
        adapter.registerDataSetObserver(new DataSetObserver() {
            @Override
            public void onChanged() {
                mIsItemChanged = true;
                addItems();
                measureStandardChildSize();
                requestLayout();
            }
        });
        adapter.notifyDataSetChanged();
    }

    private void measureStandardChildSize() {
        int widthMeasureSpec = ITEM_MEASURE_AT_MOST_SPEC;
        int heightMeasureSpec = ITEM_MEASURE_UNSPECIFIED_SPEC;
        if (isHorizontal()) {
            widthMeasureSpec = ITEM_MEASURE_UNSPECIFIED_SPEC;
            heightMeasureSpec = ITEM_MEASURE_AT_MOST_SPEC;
        }
        int childCount = getChildCount();
        View child;
        int[] measureWidths = new int[childCount];
        int[] measureHeights = new int[childCount];
        setAllItemNormalStyle();
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            mViewPickerAdapter.setNormalStyle(child);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            measureWidths[i] = child.getMeasuredWidth();
            measureHeights[i] = child.getMeasuredHeight();
        }
        mChildFocusSize = mChildNormalSize = isHorizontal() ? measureWidths : measureHeights;
        if (!mViewPickerAdapter.isCenterSizeSameAsNormal()) {
            measureWidths = new int[childCount];
            measureHeights = new int[childCount];
            for (int i = 0; i < childCount; i++) {
                child = getChildAt(i);
                mViewPickerAdapter.setFocusStyle(child);
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                measureWidths[i] = child.getMeasuredWidth();
                measureHeights[i] = child.getMeasuredHeight();
                mViewPickerAdapter.setNormalStyle(child);
            }
            mChildFocusSize = isHorizontal() ? measureWidths : measureHeights;
        }
        mMaxScroll = getScrollByFocusIndex(childCount - 1);
        mMaxItemSize = 0;
        int[] arr = isHorizontal() ? measureHeights : measureWidths;
        for (int size : arr) {
            mMaxItemSize = Math.max(mMaxItemSize, size);
        }
    }

    private void setAllItemNormalStyle() {
        int childCount = getChildCount();
        View child;
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            mViewPickerAdapter.setNormalStyle(child);
        }
    }

    private void addItems() {
        View item;
        for (int i = 0; i < mViewPickerAdapter.getCount(); i++) {
            item = mViewPickerAdapter.getView(i, null, this);
            item.setTag(i);
            item.setOnClickListener(this);
            addView(item);
        }
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    public void onClick(View v) {
        if (!mIsClickingItem) {
            mIsClickingItem = true;
            focusItem((Integer) v.getTag());
        }
    }

    public void focusItem(int index) {
        if (index < 0 || index >= mViewPickerAdapter.getCount()) {
            throw new IndexOutOfBoundsException("initCenter width invalid index: " + index);
        }
        focusItem(index, true);
    }

    public void focusItemWidthOutSmoothScroll(int index) {
        if (index < 0 || index >= mViewPickerAdapter.getCount()) {
            throw new IndexOutOfBoundsException("initCenter width invalid index: " + index);
        }
        if (!mIsItemChanged) {
            focusItem(index, false);
            return;
        }
        focusChanged(index, true);
        requestLayout();
    }

    private void focusItem(int index, boolean hasSmoothScroll) {
        if (hasSmoothScroll && index != mFocusIndex) {
            focusStateChanged(false);
        }
        int scroll = getScroll();
        int targetLen = getScrollByFocusIndex(index);
        if (hasSmoothScroll) {
            mViewFlinger.smoothScrollBy(targetLen - scroll);
        } else {
            if (isHorizontal()) {
                scrollBy(targetLen - scroll, 0);
            } else {
                scrollBy(0, targetLen - scroll);
            }
            focusChanged(index, true);
        }
    }

    private void focusChanged(int index, boolean isInit) {
        mLastFocusIndex = mFocusIndex;
        mFocusIndex = index;
        mIsClickingItem = false;
        focusStateChanged(true);
        if (mOnViewPickerListener != null && (isInit || mLastFocusIndex != mFocusIndex)) {
            mOnViewPickerListener.onSelected(mFocusIndex, getChildAt(mFocusIndex));
        }
    }

    private void focusStateChanged(boolean isInFocus) {
        mViewPickerAdapter.focusStateChanged(isInFocus, getChildAt(mFocusIndex), getChildAt(mLastFocusIndex));
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mIsClickingItem || mIsItemChanged) {
            return false;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean isHandle = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mStatus = SCROLL_STATUS_IDLE;
                sIsInterrupt = false;
                mViewFlinger.cancelAnimation();
                if (mOverDragRecoverAnim != null && mOverDragRecoverAnim.isRunning()) {
                    mOverDragRecoverAnim.cancel();
                }
                mScrollPointerId = event.getPointerId(0);
                mLastMoveX = (int) (mDownX = event.getX());
                mLastMoveY = (int) (mDownY = event.getY());
                mScrollWhenDown = getScroll();
                mIsClickedPicker = true;
                break;
            case MotionEvent.ACTION_MOVE:
                if (sIsInterrupt || isTriggerScroll(event)) {
                    sIsInterrupt = true;
                    isHandle = true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mIsClickedPicker = false;
                if (sIsInterrupt) {
                    isHandle = true;
                }
                break;
        }
        return isHandle || super.onInterceptTouchEvent(event);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int move;
        int x = (int) event.getX();
        int y = (int) event.getY();
        move = isHorizontal() ? mLastMoveX - x : mLastMoveY - y;
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                if (mIsClickedPicker && isTriggerScroll(event)) {
                    focusStateChanged(false);
                    mIsClickedPicker = false;
                }
                int scroll = getScroll() + move;
                int realScroll = (int) (mScrollWhenDown + (isHorizontal() ? mDownX - x : mDownY - y));
                if (scroll < mMinScroll) {
                    mStatus = SCROLL_STATUS_OVER_DRAG;
                    int target = -calculateSpringPressDistance(mMinScroll - realScroll);
                    move = target - (getScroll() - mMinScroll);
                } else if (scroll > mMaxScroll) {
                    mStatus = SCROLL_STATUS_OVER_DRAG;
                    int target = calculateSpringPressDistance(realScroll - mMaxScroll);
                    move = target - (getScroll() - mMaxScroll);
                } else {
                    mStatus = SCROLL_STATUS_TOUCH_DRAG;
                }
                move(move);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mStatus == SCROLL_STATUS_OVER_DRAG) {
                    overDrag();
                    break;
                }
                mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT, mMaxFlingVelocity);
                float vel;
                if (isHorizontal()) {
                    vel = -mVelocityTracker.getXVelocity(mScrollPointerId);
                } else {
                    vel = -mVelocityTracker.getYVelocity(mScrollPointerId);
                }
                if (!fling((int) vel)) {
                    if (!snap(move)) {
                        mStatus = SCROLL_STATUS_IDLE;
                        getFocusIndexByScroll();
                    }
                }
                break;
        }
        mLastMoveX = x;
        mLastMoveY = y;
        return true;
    }

    private void getFocusIndexByScroll() {
        int scroll = getScroll();
        if (scroll == 0) {
            focusChanged(0, false);
            return;
        }
        int len = mChildNormalSize[0] - mChildNormalSize[0] / 2;
        int childCount = getChildCount();
        int target;
        for (int i = 1; i < childCount; i++) {
            target = len + mDivider + Math.round(mChildFocusSize[i] * mCenterScale) / 2;
            if (target == scroll) {
                focusChanged(i, false);
                break;
            }
            if (target > scroll) {
                return;
            }
            len += mDivider + mChildNormalSize[i];
        }
    }

    private int getScrollByFocusIndex(int index) {
        if (index == 0 || getChildCount() == 0) {
            return 0;
        }
        int scroll = mChildNormalSize[0] - mChildNormalSize[0] / 2;
        for (int i = 1; i < index; i++) {
            scroll += mDivider + mChildNormalSize[i];
        }
        scroll += mDivider + Math.round(mChildFocusSize[index] * mCenterScale) / 2;
        return scroll;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        calculateChildrenScales();

        int childCount = mViewPickerAdapter.getCount();
        if (mIsItemChanged) {
            mIsItemChanged = false;
        }
        if (!mViewPickerAdapter.isCenterSizeSameAsNormal()) {
            if (isHorizontal()) {
                measureChildren(ITEM_MEASURE_UNSPECIFIED_SPEC, heightMeasureSpec);
            } else {
                measureChildren(widthMeasureSpec, ITEM_MEASURE_UNSPECIFIED_SPEC);
            }
        }
        correctScroll();
        getCurrentFocusSize();

        float scale;
        if (isHorizontal()) {
            for (int i = 0; i < childCount; i++) {
                scale = mScaleArray.get(i, -1f);
                if (scale < 0) {
                    mViewPickerAdapter.childScale(getChildAt(i), 1f);
                } else {
                    mViewPickerAdapter.childScale(getChildAt(i), scale);
                }
            }
        } else {
            for (int i = 0; i < childCount; i++) {
                scale = mScaleArray.get(i, -1f);
                if (scale < 0) {
                    mViewPickerAdapter.childScale(getChildAt(i), 1f);
                } else {
                    mViewPickerAdapter.childScale(getChildAt(i), scale);
                }
            }
        }

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int maxItemSize = (int) Math.ceil(mMaxItemSize * mCenterScale);
        if (isHorizontal()) {
            if (hMode == MeasureSpec.AT_MOST) {
                height = Math.min(maxItemSize, height);
            }
            if (hMode == MeasureSpec.UNSPECIFIED) {
                height = maxItemSize;
            }
        } else {
            if (wMode == MeasureSpec.AT_MOST) {
                width = Math.min(maxItemSize, width);
            }
            if (wMode == MeasureSpec.UNSPECIFIED) {
                width = maxItemSize;
            }
        }
        setMeasuredDimension(width, height);
    }

    private void getCurrentFocusSize() {
        View view = getChildAt(mFocusViewIndex);
        if (view == null) {
            return;
        }
        float scale = mScaleArray.get(mFocusViewIndex);
        mFocusWidth = view.getMeasuredWidth() * scale;
        mFocusHeight = view.getMeasuredHeight() * scale;
    }

    private void correctScroll() {
        if (mStatus != SCROLL_STATUS_IDLE) {
            return;
        }
        int scroll = getScroll();
        int targetLen = getScrollByFocusIndex(mFocusIndex);
        if (targetLen == scroll) {
            return;
        }
        if (isHorizontal()) {
            scrollBy(targetLen - scroll, 0);
        } else {
            scrollBy(0, targetLen - scroll);
        }
    }

    private void calculateChildrenScales() {
        int scroll = getScroll();
        mScaleArray.clear();
        setAllItemNormalStyle();
        if (scroll == 0) {
            mFocusViewIndex = 0;
            mScaleArray.put(0, mCenterScale);
            mViewPickerAdapter.setFocusStyle(getChildAt(0));
            return;
        }
        int len = mChildNormalSize[0] - mChildNormalSize[0] / 2;
        int childCount = getChildCount();
        int target;
        boolean isInFocus = false;
        int index = 0;
        for (int i = 1; i < childCount; i++) {
            index = i;
            target = len + mDivider + Math.round(mChildFocusSize[i] * mCenterScale) / 2;
            if (target == scroll) {
                isInFocus = true;
                break;
            }
            if (target > scroll) {
                break;
            }
            len += mDivider + mChildNormalSize[i];
        }
        if (isInFocus) {
            mFocusViewIndex = index;
            mScaleArray.put(index, mCenterScale);
            mViewPickerAdapter.setFocusStyle(getChildAt(index));
            return;
        }
        int indexFocusLen = getScrollByFocusIndex(index - 1);
        int lastFocusLen = getScrollByFocusIndex(index);
        int changeArea = lastFocusLen - indexFocusLen;
        int focusMove = scroll - indexFocusLen;
        float firstFocusPercent = (changeArea - focusMove) * 1f / changeArea;
        float firstScale = 1 + (mCenterScale - 1) * firstFocusPercent;
        mScaleArray.put(index - 1, firstScale);
        float secondScale = 1 + (mCenterScale - 1) * (1 - firstFocusPercent);
        mScaleArray.put(index, secondScale);

        if (secondScale > firstScale) {
            mFocusViewIndex = index;
            mViewPickerAdapter.setNormalStyle(getChildAt(index - 1));
            mViewPickerAdapter.setFocusStyle(getChildAt(index));
        } else {
            mFocusViewIndex = index - 1;
            mViewPickerAdapter.setFocusStyle(getChildAt(index - 1));
            mViewPickerAdapter.setNormalStyle(getChildAt(index));
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = mViewPickerAdapter.getCount();
        if (count == 0) {
            return;
        }
        View child = getChildAt(0);
        int cL, cT, cR, cB;
        int start;
        int childWidth;
        int childHeight;
        int scaleWidth = getChildScaleWidth(child);
        int scaleHeight = getChildScaleHeight(child);
        if (isHorizontal()) {
            start = (r - l) / 2 - scaleWidth / 2;
            for (int i = 0; i < count; i++) {
                child = getChildAt(i);
                childWidth = child.getMeasuredWidth();
                childHeight = child.getMeasuredHeight();
                scaleWidth = getChildScaleWidth(child);
                cL = start + (scaleWidth - childWidth) / 2;
                cT = (b - t - childHeight) / 2;
                cR = cL + childWidth;
                cB = cT + childHeight;
                child.layout(cL, cT, cR, cB);
                start += scaleWidth + mDivider;
            }
        } else {
            start = (b - t) / 2 - scaleHeight / 2;
            for (int i = 0; i < count; i++) {
                child = getChildAt(i);
                childWidth = child.getMeasuredWidth();
                childHeight = child.getMeasuredHeight();
                scaleHeight = getChildScaleHeight(child);
                cL = (r - l - childWidth) / 2;
                cT = start + (scaleHeight - childHeight) / 2;
                cR = cL + childWidth;
                cB = cT + childHeight;
                child.layout(cL, cT, cR, cB);
                start += scaleHeight + mDivider;
            }
        }
    }

    private int getChildScaleWidth(View child) {
        return Math.round(child.getMeasuredWidth() * child.getScaleX());
    }

    private int getChildScaleHeight(View child) {
        return Math.round(child.getMeasuredHeight() * child.getScaleY());
    }

    public boolean isHorizontal() {
        return mOrientation == HORIZONTAL;
    }

    private int getScroll() {
        return isHorizontal() ? getScrollX() : getScrollY();
    }

    private boolean isTriggerScroll(MotionEvent event) {
        if (isHorizontal()) {
            return (SCALED_TOUCH_SLOP < Math.abs(event.getX() - mDownX));
        }
        return (SCALED_TOUCH_SLOP < Math.abs(event.getY() - mDownY));
    }

    private boolean snap(int move) {
        int scroll = getScroll() + move;
        if (scroll <= mMinScroll || scroll >= mMaxScroll) {
            move(move);
            return false;
        }
        move(move);

        int len = mChildNormalSize[0] - mChildNormalSize[0] / 2;
        int childCount = getChildCount();
        int target;
        boolean isInFocus = false;
        int index = 0;
        for (int i = 1; i < childCount; i++) {
            index = i;
            target = len + mDivider + Math.round(mChildFocusSize[i] * mCenterScale) / 2;
            if (target == scroll) {
                isInFocus = true;
                break;
            }
            if (target > scroll) {
                break;
            }
            len += mDivider + mChildNormalSize[i];
        }
        if (isInFocus) {
            return false;
        }
        int desc = getScrollByFocusIndex(index - 1) - scroll;
        int plus = getScrollByFocusIndex(index) - scroll;

        if (Math.abs(desc) < Math.abs(plus)) {
            mViewFlinger.snapSmoothScroll(desc);
        } else {
            mViewFlinger.snapSmoothScroll(plus);
        }
        return true;
    }

    private void overDrag() {
        int scroll = getScroll();
        boolean isListStart = scroll < mMinScroll;
        FloatValueHolder valueHolder = new FloatValueHolder(scroll);
        mOverDragRecoverAnim = new SpringAnimation(valueHolder);
        SpringForce force = new SpringForce();
        force.setDampingRatio(OVER_DRAGGER_DAMPING_RATIO);
        force.setStiffness(OVER_DRAGGER_STIFFNESS);
        mOverDragRecoverAnim.setSpring(force);
        mOverDragRecoverAnim.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
            int lastValue = scroll;

            @Override
            public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
                int currVal = Math.round(value);
                move(currVal - lastValue);
                lastValue = currVal;
            }
        });
        mOverDragRecoverAnim.animateToFinalPosition(isListStart ? mMinScroll : mMaxScroll);
    }

    private void move(int move) {
        int target = getScroll() + move;
        if (mStatus == SCROLL_STATUS_OVER_DRAG) {
            if (target < mMinScroll - mOverDragLen) {
                target = mMinScroll - mOverDragLen;
            }
            if (target > mMaxScroll + mOverDragLen) {
                target = mMaxScroll + mOverDragLen;
            }
        } else {
            if (target < mMinScroll) {
                target = mMinScroll;
            }
            if (target > mMaxScroll) {
                target = mMaxScroll;
            }
        }
        if (isHorizontal()) {
            scrollTo(target, getScrollY());
        } else {
            scrollTo(getScrollX(), target);
        }
        requestLayout();
    }

    private boolean fling(int velocity) {
        if (Math.abs(velocity) < mMinFlingVelocity) {
            return false;
        }
        velocity = Math.max(-mMaxFlingVelocity, Math.min(velocity, mMaxFlingVelocity));
        mViewFlinger.fling(velocity);
        return true;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (mViewPickerAdapter.isDrawFloatingUpToItems()) {
            super.dispatchDraw(canvas);
            drawFocusBg(canvas);
        } else {
            drawFocusBg(canvas);
            super.dispatchDraw(canvas);
        }
    }

    protected void drawFocusBg(Canvas canvas) {
        canvas.save();
        canvas.translate(getScrollX(), getScrollY());
        mViewPickerAdapter.onDraw(canvas, mFocusWidth, mFocusHeight);
        canvas.restore();
    }

    class ViewFlinger implements Runnable {
        private static final int MAX_SCROLL_DURATION = 2000;
        public static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
        private final float SPEED_PER_PIXEL;

        private int mLastFling;

        private final OverScroller mOverScroller;
        private boolean mEatRunOnAnimationRequest = false;
        private boolean mReSchedulePostAnimationCallback = false;

        private ViewFlinger() {
            mOverScroller = new OverScroller(getContext(), sQuinticInterpolator);
            mOverScroller.setFriction(SCROLL_FRICTION);
            SPEED_PER_PIXEL = MILLISECONDS_PER_INCH / getResources().getDisplayMetrics().densityDpi;
        }

        @Override
        public void run() {
            mReSchedulePostAnimationCallback = false;
            mEatRunOnAnimationRequest = true;

            final OverScroller scroller = mOverScroller;
            boolean res = scroller.computeScrollOffset();
            if (res) {
                int target = isHorizontal() ? scroller.getCurrX() : scroller.getCurrY();
                int unconsumed = target - mLastFling;
                mLastFling = target;

                boolean scrollerFinished = isHorizontal() ? scroller.getCurrX() == scroller.getFinalX() : scroller.getCurrY() == scroller.getFinalY();
                final boolean doneScrolling = scroller.isFinished() || scrollerFinished;

                boolean isResetScrollStatus = true;
                if (unconsumed != 0) {
                    if (mStatus == SCROLL_STATUS_SNAP || mStatus == SCROLL_STATUS_SMOOTH) {
                        move(unconsumed);
                    }
                    if (mStatus == SCROLL_STATUS_INERTIA) {
                        if (!doneScrolling) {
                            move(unconsumed);
                        } else {
                            isResetScrollStatus = !snap(unconsumed);
                        }
                    }
                }
                if (!doneScrolling && !isNeedStopScroll()) {
                    postOnAnimation();
                } else {
                    if (isResetScrollStatus) {
                        mStatus = SCROLL_STATUS_IDLE;
                        getFocusIndexByScroll();
                    }
                }
            }

            mEatRunOnAnimationRequest = false;
            if (mReSchedulePostAnimationCallback) {
                internalPostOnAnimation();
            }
        }

        private boolean isNeedStopScroll() {
            return (mLastFling < 0 && getScroll() == 0) || (mLastFling > 0 && getScroll() == mMaxScroll);
        }

        void fling(int velocity) {
            mStatus = SCROLL_STATUS_INERTIA;
            mLastFling = 0;
            if (isHorizontal()) {
                mOverScroller.fling(0, 0, velocity, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
            } else {
                mOverScroller.fling(0, 0, 0, velocity, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
            }
            postOnAnimation();
        }

        public void snapSmoothScroll(int move) {
            mStatus = SCROLL_STATUS_SNAP;
            smoothScrollBy(move, calculateTimeForDeceleration(move));
        }

        public void smoothScrollBy(int move) {
            mStatus = SCROLL_STATUS_SMOOTH;
            smoothScrollBy(move, UNDEFINED_DURATION);
        }

        public void smoothScrollBy(int move, int duration) {
            if (duration == UNDEFINED_DURATION) {
                duration = computeScrollDuration(move);
            }
            mLastFling = 0;
            if (isHorizontal()) {
                mOverScroller.startScroll(0, 0, move, 0, duration);
            } else {
                mOverScroller.startScroll(0, 0, 0, move, duration);
            }
            mOverScroller.computeScrollOffset();

            postOnAnimation();
        }

        public int calculateTimeForDeceleration(int dy) {
            int timeForScrolling = (int) Math.ceil(Math.abs(dy) * SPEED_PER_PIXEL);
            return (int) Math.ceil(timeForScrolling / .3356);
        }

        private int computeScrollDuration(int dy) {
            int containerSize = getHeight();
            float absDelta = Math.abs(dy);
            int duration = (int) (((absDelta / containerSize) + 1) * SMOOTH_PARAM);
            return Math.min(duration, MAX_SCROLL_DURATION);
        }

        void postOnAnimation() {
            if (mEatRunOnAnimationRequest) {
                mReSchedulePostAnimationCallback = true;
            } else {
                internalPostOnAnimation();
            }
        }

        private void internalPostOnAnimation() {
            removeCallbacks(this);
            ViewCompat.postOnAnimation(ViewPicker.this, this);
        }

        private void cancelAnimation() {
            if (!mOverScroller.isFinished()) {
                mOverScroller.abortAnimation();
            }
        }
    }

    private int calculateSpringPressDistance(float distance) {
        float drag = distance * OVER_DRAGGER_FRICTION;
        float percent = Math.min(1f, Math.abs(drag / mOverDragLen));
        return (int) (mOverDragLen * (float) ((percent / 2) - Math.pow((percent / 2), 2)) * 4f);
    }

    static final Interpolator sQuinticInterpolator = t -> {
        t -= 1.0F;
        return t * t * t * t * t + 1.0f;
    };

    public void setOnViewPickerListener(OnViewPickerListener listener) {
        if (listener == null) {
            listener = DEFAULT_LISTENER;
        }
        this.mOnViewPickerListener = listener;
    }
}

package com.xunrui.camera.ui.commonui.viewpicker;

import android.graphics.Canvas;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import androidx.annotation.CallSuper;

public class ViewPickerAdapter<T> extends BaseAdapter {
    protected boolean sIsCenterSizeSameAsNormal = true;

    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public T getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return null;
    }

    protected void setFocusStyle(View view) {
    }

    protected void setNormalStyle(View view) {
    }

    @CallSuper
    protected void childScale(View child, float scale) {
        child.setScaleX(scale);
        child.setScaleY(scale);
    }

    public void focusStateChanged(boolean isInFocus, View focus, View lastFocus) {
    }

    public boolean isCenterSizeSameAsNormal() {
        return sIsCenterSizeSameAsNormal;
    }

    public boolean willNotDraw() {
        return true;
    }

    public boolean isDrawFloatingUpToItems() {
        return false;
    }

    public void onDraw(Canvas canvas, float focusWidth, float focusHeight) {
    }
}

package com.xunrui.camera.ui.commonui.viewpicker;

import android.annotation.SuppressLint;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.xunrui.camera.R;
import com.xunrui.camera.app.CameraApp;
import com.xunrui.camera.ui.vo.FilterViewItem;

import java.util.List;

public class FilterPickerAdapter extends ViewPickerAdapter<FilterViewItem> {
    private List<FilterViewItem> mDatas;
    private final int mItemSize;
    private final Drawable mCenterDrawable;

    @SuppressLint("UseCompatLoadingForDrawables")
    public FilterPickerAdapter(List<FilterViewItem> datas) {
        this.mDatas = datas;
        Resources resources = CameraApp.getContext().getResources();
        mItemSize = resources.getDimensionPixelSize(R.dimen.filter_item_size);
        mCenterDrawable = resources.getDrawable(R.drawable.filter_image_bg, null);
        mCenterDrawable.setBounds(0, 0, mCenterDrawable.getIntrinsicWidth(), mCenterDrawable.getIntrinsicHeight());
    }

    public void setDatas(List<FilterViewItem> datas) {
        this.mDatas = datas;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return mDatas.size();
    }

    @Override
    public FilterViewItem getItem(int position) {
        return mDatas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView view = new ImageView(parent.getContext());
        FilterViewItem item = getItem(position);
        view.setImageResource(item.getIconId());
        view.setFocusable(true);
        view.setScaleType(ImageView.ScaleType.FIT_CENTER);
        view.setContentDescription(item.getName());
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(mItemSize, mItemSize);
        view.setLayoutParams(params);
        return view;
    }

    @Override
    public void onDraw(Canvas canvas, float focusWidth, float focusHeight) {
        canvas.save();
        canvas.translate((canvas.getWidth() - mCenterDrawable.getIntrinsicWidth()) / 2f,
                (canvas.getHeight() - mCenterDrawable.getIntrinsicHeight()) / 2f);
        mCenterDrawable.draw(canvas);
        canvas.restore();
    }

    @Override
    public boolean willNotDraw() {
        return false;
    }

    @Override
    public boolean isDrawFloatingUpToItems() {
        return true;
    }
}

package com.yb.camx.view.viewpicker;

import android.view.View;

public interface OnViewPickerListener {
    default void onSelected(int focusIndex, View view) {
    }
}

<resources>
    <declare-styleable name="ViewPicker">
        <attr name="centerScale" format="float" />
        <attr name="itemDivider" format="dimension"/>
        <attr name="android:orientation" format="integer" />
    </declare-styleable>
</resources>
<com.yb.camx.view.viewpicker.ViewPicker
        android:id="@+id/module_switch"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginBottom="180dp"
        android:orientation="horizontal"
        android:visibility="visible"
        app:centerScale="1.2"
        app:itemDivider="10dp" />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值