zoukankan      html  css  js  c++  java
  • 安卓 实现IOS阻尼回弹效果

    根据修改SwipeRefreshLayout源码实现滚动回弹效果,支持各大layout以及滚动view的回弹,用法和SwipeRefreshLayout类似。(如果不足之处请各位大神吐槽。)

    public class DampingReboundLayout extends ViewGroup implements NestedScrollingParent3,
    NestedScrollingParent2, NestedScrollingChild3, NestedScrollingChild2, NestedScrollingParent,
    NestedScrollingChild {

    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
    private static final float DRAG_RATE = .35f;


    // Default offset in dips from the top of the view to where the progress spinner should stop
    private static final int DEFAULT_CIRCLE_TARGET = 200;

    private View mTarget; // the target of the gesture

    // If nested scrolling is enabled, the total amount that needed to be
    // consumed by this as the nested scrolling parent is used in place of the
    // overscroll determined by MOVE events in the onTouch handler
    private float mTotalUnconsumed;
    private final NestedScrollingParentHelper mNestedScrollingParentHelper;
    private final NestedScrollingChildHelper mNestedScrollingChildHelper;
    private final int[] mParentScrollConsumed = new int[2];
    private final int[] mParentOffsetInWindow = new int[2];

    // Used for calls from old versions of onNestedScroll to v3 version of onNestedScroll. This only
    // exists to prevent GC costs that are present before API 21.
    private final int[] mNestedScrollingV2ConsumedCompat = new int[2];
    private boolean mNestedScrollInProgress;


    private float mInitialDownY;

    // Target is returning to its start offset because it was cancelled or a
    // refresh was triggered.
    private final DecelerateInterpolator mDecelerateInterpolator;
    private static final int[] LAYOUT_ATTRS = new int[]{
    android.R.attr.enabled
    };
    private int mLinearLayoutViewIndex = -1;


    /**
    * @see #setLegacyRequestDisallowInterceptTouchEventEnabled
    */
    private boolean mEnableLegacyRequestDisallowInterceptTouch;


    @Override
    protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    }


    /**
    * Simple constructor to use when creating a SwipeRefreshLayout from code.
    *
    * @param context
    */
    public DampingReboundLayout(@NonNull Context context) {
    this(context, null);
    }

    /**
    * Constructor that is called when inflating SwipeRefreshLayout from XML.
    *
    * @param context
    * @param attrs
    */
    public DampingReboundLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);

    setWillNotDraw(false);
    mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);

    final DisplayMetrics metrics = getResources().getDisplayMetrics();

    setChildrenDrawingOrderEnabled(true);
    // the absolute offset has to take into account that the circle starts at an offset
    mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);

    mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
    setNestedScrollingEnabled(true);

    final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
    setEnabled(a.getBoolean(0, true));
    a.recycle();
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
    if (mLinearLayoutViewIndex < 0) {
    return i;
    } else if (i == childCount - 1) {
    // Draw the selected child last
    return mLinearLayoutViewIndex;
    } else if (i >= mLinearLayoutViewIndex) {
    // Move the children after the selected child earlier one
    return i + 1;
    } else {
    // Keep the children before the selected child the same
    return i;
    }
    }


    private void ensureTarget() {
    // Don't bother getting the parent height if the parent hasn't been laid
    // out yet.
    if (mTarget == null) {
    mTarget = getChildAt(0);
    }
    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    final int width = getMeasuredWidth();
    final int height = getMeasuredHeight();
    if (getChildCount() == 0) {
    return;
    }
    if (mTarget == null) {
    ensureTarget();
    }
    if (mTarget == null) {
    return;
    }
    final View child = mTarget;
    final int childLeft = getPaddingLeft();
    final int childTop = getPaddingTop();
    final int childWidth = width - getPaddingLeft() - getPaddingRight();
    final int childHeight = height - getPaddingTop() - getPaddingBottom();
    child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (mTarget == null) {
    ensureTarget();
    }
    if (mTarget == null) {
    return;
    }
    mTarget.measure(MeasureSpec.makeMeasureSpec(
    getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
    MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
    getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
    }

    /**
    * @return Whether it is possible for the child view of this layout to
    * scroll up. Override this if the child view is a custom view.
    */
    public boolean canChildScrollUp() {
    if (mTarget instanceof ListView) {
    return ListViewCompat.canScrollList((ListView) mTarget, -1);
    }
    return mTarget.canScrollVertically(-1);
    }

    public boolean canChildScrollDown() {
    if (mTarget instanceof ListView) {
    return ListViewCompat.canScrollList((ListView) mTarget, 1);
    }
    return mTarget.canScrollVertically(1);
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    ensureTarget();
    final int action = ev.getActionMasked();
    int pointerIndex;

    if (!isEnabled() || canChildScrollUp() || canChildScrollDown() || mNestedScrollInProgress) {
    // Fail fast if we're not in a state where a swipe is possible
    return false;
    }

    switch (action) {
    case MotionEvent.ACTION_DOWN:

    // pointerIndex = ev.findPointerIndex(mActivePointerId);
    // if (pointerIndex < 0) {
    // return false;
    // }
    mInitialDownY = ev.getY();
    break;

    case MotionEvent.ACTION_MOVE:
    break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_CANCEL:
    break;
    default:
    }
    return super.onInterceptTouchEvent(ev);
    }

    /**
    * Enables the legacy behavior of {@link #requestDisallowInterceptTouchEvent} from before
    * 1.1.0-alpha03, where the request is neither honored, nor propagated up to its parents,
    * in either of the following two cases:
    * <ul>
    * <li>The child as an {@link AbsListView} and the runtime is API < 21</li>
    * <li>The child has nested scrolling disabled</li>
    * </ul>
    * Use this method <em>only</em> if your application:
    * <ul>
    * <li>is upgrading SwipeRefreshLayout from &lt; 1.1.1 to &gt;= 1.1.0-alpha03</li>
    * <li>has a SwipeRefreshLayout, or its parent, that no longer responds to touch events
    * when it should</li>
    * <li>setting this method to {@code true} fixes that issue</li>
    * </ul>
    *
    * @param enabled {@code true} to enable the legacy behavior, {@code false} for default behavior
    * @deprecated Only use this method if the changes introduced in
    * {@link #requestDisallowInterceptTouchEvent} in version 1.1.0-alpha03 and 1.1.1
    * are breaking your application.
    */
    @Deprecated
    public void setLegacyRequestDisallowInterceptTouchEventEnabled(boolean enabled) {
    mEnableLegacyRequestDisallowInterceptTouch = enabled;
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean b) {
    if (mEnableLegacyRequestDisallowInterceptTouch
    && ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
    || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget)))) {
    // Legacy behavior: if this is a List < L or another view that doesn't support
    // nested scrolling, ignore this request so that the vertical scroll event
    // isn't stolen
    return;
    }
    super.requestDisallowInterceptTouchEvent(b);
    }

    // NestedScrollingParent 3

    @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
    int dxUnconsumed, int dyUnconsumed, @ViewCompat.NestedScrollType int type,
    @NonNull int[] consumed) {
    if (type != ViewCompat.TYPE_TOUCH) {
    return;
    }

    // This is a bit of a hack. onNestedScroll is typically called up the hierarchy of nested
    // scrolling parents/children, where each consumes distances before passing the remainder
    // to parents. In our case, we want to try to run after children, and after parents, so we
    // first pass scroll distances to parents and consume after everything else has.
    int consumedBeforeParents = consumed[1];
    dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
    mParentOffsetInWindow, type, consumed);
    int consumedByParents = consumed[1] - consumedBeforeParents;
    int unconsumedAfterParents = dyUnconsumed - consumedByParents;

    // There are two reasons why scroll distance may be totally consumed. 1) All of the nested
    // scrolling parents up the hierarchy implement NestedScrolling3 and consumed all of the
    // distance or 2) at least 1 nested scrolling parent doesn't implement NestedScrolling3 and
    // for comparability reasons, we are supposed to act like they have.
    //
    // We must assume 2) is the case because we have no way of determining that it isn't, and
    // therefore must fallback to a previous hack that was done before nested scrolling 3
    // existed.
    int remainingDistanceToScroll;
    if (unconsumedAfterParents == 0) {
    // The previously implemented hack is to see how far we were offset and assume that that
    // distance is equal to how much all of our parents consumed.
    remainingDistanceToScroll = dyUnconsumed + mParentOffsetInWindow[1];
    } else {
    remainingDistanceToScroll = unconsumedAfterParents;
    }

    // Not sure why we have to make sure the child can't scroll up... but seems dangerous to
    // remove.
    if (remainingDistanceToScroll < 0 && !canChildScrollUp()) {
    mTotalUnconsumed += Math.abs(remainingDistanceToScroll);
    moveSpinner(mTotalUnconsumed);
    // If we've gotten here, we need to consume whatever is left to consume, which at this
    // point is either equal to 0, or remainingDistanceToScroll.
    consumed[1] += unconsumedAfterParents;
    }
    if (remainingDistanceToScroll > 0 && !canChildScrollDown()) {
    mTotalUnconsumed -= Math.abs(remainingDistanceToScroll);
    moveSpinner(mTotalUnconsumed);
    // If we've gotten here, we need to consume whatever is left to consume, which at this
    // point is either equal to 0, or remainingDistanceToScroll.
    consumed[1] -= unconsumedAfterParents;
    }
    }

    // NestedScrollingParent 2

    @Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
    if (type == ViewCompat.TYPE_TOUCH) {
    return onStartNestedScroll(child, target, axes);
    } else {
    return false;
    }
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes, int type) {
    // Should always be true because onStartNestedScroll returns false for all type !=
    // ViewCompat.TYPE_TOUCH, but check just in case.
    if (type == ViewCompat.TYPE_TOUCH) {
    onNestedScrollAccepted(child, target, axes);
    }
    }

    @Override
    public void onStopNestedScroll(View target, int type) {
    // Should always be true because onStartNestedScroll returns false for all type !=
    // ViewCompat.TYPE_TOUCH, but check just in case.
    if (type == ViewCompat.TYPE_TOUCH) {
    onStopNestedScroll(target);
    }
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
    int dyUnconsumed, int type) {
    onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type,
    mNestedScrollingV2ConsumedCompat);
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) {
    // Should always be true because onStartNestedScroll returns false for all type !=
    // ViewCompat.TYPE_TOUCH, but check just in case.
    if (type == ViewCompat.TYPE_TOUCH) {
    onNestedPreScroll(target, dx, dy, consumed);
    }
    }

    // NestedScrollingParent 1

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    return isEnabled() && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
    // Reset the counter of how much leftover scroll needs to be consumed.
    mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
    // Dispatch up to the nested parent
    startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
    mTotalUnconsumed = 0;
    mNestedScrollInProgress = true;
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
    // If we are in the middle of consuming, a scroll, then we want to move the spinner back up
    // before allowing the list to scroll
    if (dy > 0 && mTotalUnconsumed > 0 || dy < 0 && mTotalUnconsumed < 0) {
    if (dy > mTotalUnconsumed) {
    consumed[1] = (int) mTotalUnconsumed;
    mTotalUnconsumed = 0;
    } else {
    mTotalUnconsumed -= dy;
    consumed[1] = dy;
    }
    moveSpinner(mTotalUnconsumed);
    }

    // If a client layout is using a custom start position for the circle
    // view, they mean to hide it again before scrolling the child view
    // If we get back to mTotalUnconsumed == 0 and there is more to go, hide
    // the circle so it isn't exposed if its blocking content is moved

    // Now let our nested parent consume the leftovers
    final int[] parentConsumed = mParentScrollConsumed;
    if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
    consumed[0] += parentConsumed[0];
    consumed[1] += parentConsumed[1];
    }
    }

    @Override
    public int getNestedScrollAxes() {
    return mNestedScrollingParentHelper.getNestedScrollAxes();
    }

    @Override
    public void onStopNestedScroll(View target) {
    mNestedScrollingParentHelper.onStopNestedScroll(target);
    mNestedScrollInProgress = false;
    // Finish the spinner for nested scrolling if we ever consumed any
    // unconsumed nested scroll
    if (mTotalUnconsumed != 0) {
    finishSpinner();
    mTotalUnconsumed = 0;
    }
    // Dispatch up our nested parent
    stopNestedScroll();
    }

    @Override
    public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
    final int dxUnconsumed, final int dyUnconsumed) {
    onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
    ViewCompat.TYPE_TOUCH, mNestedScrollingV2ConsumedCompat);
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
    return dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY,
    boolean consumed) {
    return dispatchNestedFling(velocityX, velocityY, consumed);
    }

    // NestedScrollingChild 3

    @Override
    public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
    int dyUnconsumed, @Nullable int[] offsetInWindow, @ViewCompat.NestedScrollType int type,
    @NonNull int[] consumed) {
    if (type == ViewCompat.TYPE_TOUCH) {
    mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed,
    dyUnconsumed, offsetInWindow, type, consumed);
    }
    }

    // NestedScrollingChild 2

    @Override
    public boolean startNestedScroll(int axes, int type) {
    return type == ViewCompat.TYPE_TOUCH && startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll(int type) {
    if (type == ViewCompat.TYPE_TOUCH) {
    stopNestedScroll();
    }
    }

    @Override
    public boolean hasNestedScrollingParent(int type) {
    return type == ViewCompat.TYPE_TOUCH && hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
    int dyUnconsumed, int[] offsetInWindow, int type) {
    return type == ViewCompat.TYPE_TOUCH && mNestedScrollingChildHelper.dispatchNestedScroll(
    dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
    int type) {
    return type == ViewCompat.TYPE_TOUCH && dispatchNestedPreScroll(dx, dy, consumed,
    offsetInWindow);
    }

    // NestedScrollingChild 1

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
    mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
    return mNestedScrollingChildHelper.isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {
    return mNestedScrollingChildHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
    mNestedScrollingChildHelper.stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
    return mNestedScrollingChildHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
    int dyUnconsumed, int[] offsetInWindow) {
    return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
    dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    return mNestedScrollingChildHelper.dispatchNestedPreScroll(
    dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
    return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
    return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }


    private void moveSpinner(float overScrollTop) {
    float scroll = overScrollTop * DRAG_RATE;
    if (scroll > getHeight() / 2f) {
    scroll = getHeight() / 2f;
    }
    if (scroll < -getHeight() / 2f) {
    scroll = -getHeight() / 2f;
    }
    mTarget.setTranslationY(scroll);
    }


    private void finishSpinner() {
    animateOffsetToStartPosition();
    }


    private void animateOffsetToStartPosition() {
    ValueAnimator valueAnimator = ValueAnimator.ofFloat(mTarget.getTranslationY(), 0);
    valueAnimator.setDuration(500);
    valueAnimator.addUpdateListener(animation -> {
    mTarget.setTranslationY((Float) animation.getAnimatedValue());
    });
    valueAnimator.setInterpolator(mDecelerateInterpolator);
    valueAnimator.start();
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getActionMasked();
    int pointerIndex = -1;
    if (!isEnabled() || canChildScrollUp() || canChildScrollDown() || mNestedScrollInProgress) {
    // Fail fast if we're not in a state where a swipe is possible
    return false;
    }
    switch (action) {
    case MotionEvent.ACTION_DOWN:
    break;

    case MotionEvent.ACTION_MOVE: {
    final float y = ev.getY();
    final float overscrollTop = (y - mInitialDownY);
    // While the spinner is being dragged down, our parent shouldn't try
    // to intercept touch events. It will stop the drag gesture abruptly.
    getParent().requestDisallowInterceptTouchEvent(true);
    moveSpinner(overscrollTop);
    break;
    }
    case MotionEvent.ACTION_UP: {
    finishSpinner();
    return false;
    }
    case MotionEvent.ACTION_CANCEL:
    return false;
    }

    return true;
    }

    }

     

  • 相关阅读:
    实例属性 类属性 实例域 类域
    研究数据集
    static 静态域 类域 静态方法 工厂方法 he use of the static keyword to create fields and methods that belong to the class, rather than to an instance of the class 非访问修饰符
    accessor mothod mutator mothod 更改器方法 访问器方法 类的方法可以访问类的任何一个对象的私有域!
    上钻 下钻 切片 转轴 降采样
    识别会话
    Performance Tuning Using Linux Process Management Commands
    Secure Hash Algorithm 3
    grouped differently across partitions
    spark 划分stage Wide vs Narrow Dependencies 窄依赖 宽依赖 解析 作业 job stage 阶段 RDD有向无环图拆分 任务 Task 网络传输和计算开销 任务集 taskset
  • 原文地址:https://www.cnblogs.com/blogzhangwei/p/14798370.html
Copyright © 2011-2022 走看看