带阻尼效果的RelativeLayout 可以包容ListView

 

为了日后有用,转载一个带阻尼效果的RelativeLayout 可以包容ListView,上下都有阻尼效果。代码直接copy既可以用

代码暂未细读,所以无太多解释。

 



import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.RelativeLayout;

public class BounceScroller extends RelativeLayout {
    public static final String TAG = "BounceScroller";

    public static final int DEFALUT_DURATION = 400;

    public static enum State {
        STATE_FIT_CONTENT, STATE_SHOW, STATE_OVER, STATE_FIT_EXTRAS
    }

    ;

    protected State mState = State.STATE_FIT_CONTENT;
    private Bouncer mBouncer = new Bouncer();
    private BounceListener mListener;
    private View mContentView;

    private int mLastEventY;
    private int mLastTargetTop;

    private View mHeaderView;
    protected int mHeaderHeight;

    private View mFooterView;
    private int mFooterHeight;

    private boolean overScrolled;

    private boolean pullingHeader;
    private boolean pullingFooter;

    private boolean headerBounce = true;
    private boolean footerBounce = true;

    private int remainOffset;

    private View mTargetView;
    private TimeInterpolator mInterpolator;
    private long mTimeBase = 0;

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

    public BounceScroller(Context context, AttributeSet attrs) {
        super(context, attrs);

        mInterpolator = new DecelerateInterpolator();
        remainOffset = 0;
    }

    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() > 0) {
            mContentView = getChildAt(0);
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mContentView == null && !eventInView(event, mContentView)) {
            return super.dispatchTouchEvent(event);
        }

        int action = event.getAction();

        if (takeTouchEvent(event)) {
            Log.d(TAG, System.currentTimeMillis() + " takeTouchEvent " + action);
        } else {
            boolean result = super.dispatchTouchEvent(event);
            Log.d(TAG, System.currentTimeMillis() + " dispatchTouchEvent "
                    + result);
        }

        if (action == MotionEvent.ACTION_CANCEL
                || action == MotionEvent.ACTION_UP) {
            overScrolled = false;
            mTimeBase = 0;
            pullingHeader = false;
            pullingFooter = false;
            mLastEventY = 0;
            mLastTargetTop = 0;
            mTargetView = null;
            return true;
        } else if (action == MotionEvent.ACTION_DOWN) {
            // cancel bounce if exists
            mBouncer.cancel();

            mTargetView = getTargetView(mContentView, event);
            mTimeBase = 0;
        } else if (action == MotionEvent.ACTION_MOVE) {
            int eventOffset = (int) event.getY() - mLastEventY;
            if (!overScrolled) {
                if (mHeaderView != null && mHeaderView.getBottom() > 0
                        && eventOffset < 0) {
                    overScrolled = true;
                } else if (mFooterView != null
                        && mFooterView.getTop() < getBottom()
                        && eventOffset > 0) {
                    overScrolled = true;
                } else {
                    overScrolled = false;
                }
            }

            if (mTargetView != null
                    && mTargetView.getVisibility() != View.VISIBLE) {
                mTargetView = getTargetView(mContentView, event);
                Log.d(TAG, "update mTargetView " + mTargetView.getId());
                mTimeBase = 0;
                overScrolled = false;
            } else {
                int targetTop = getViewTop(mTargetView);
                int viewOffset = targetTop - mLastTargetTop;
                Log.d(TAG, "targetTop " + targetTop + " viewOffset "
                        + viewOffset + " eventOffset " + eventOffset
                        + " mTimeBase " + mTimeBase);
                if (eventOffset != 0 && viewOffset == 0 && !overScrolled) {
                    long currentTime = System.currentTimeMillis();
                    remainOffset += eventOffset;
                    if (mTimeBase == 0) {
                        mTimeBase = currentTime;
                    } else if (currentTime - mTimeBase > 50) {
                        overScrolled = true;
                        mTimeBase = 0;
                    }
                } else if (eventOffset != 0 && viewOffset != 0) {
                    mTimeBase = 0;
                }

                if (remainOffset != 0 && overScrolled) {
                    Log.d(TAG, "do remainOffset " + remainOffset);
                    onOffset(remainOffset);
                    remainOffset = 0;
                }
            }
        }

        mLastTargetTop = getViewTop(mTargetView);
        mLastEventY = (int) event.getY();

        return true;
    }

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d(TAG, System.currentTimeMillis() + " onDraw");
    }

    private int getViewTop(View view) {
        if (view == null) {
            return 0;
        }
        int[] location = new int[2];
        view.getLocationOnScreen(location);
        return location[1];
    }

    private boolean takeTouchEvent(MotionEvent event) {
        if (!headerBounce && !footerBounce) {
            return false;
        }

        int action = event.getAction();
        int contentTop = mContentView.getTop();

        if (action == MotionEvent.ACTION_UP
                || action == MotionEvent.ACTION_CANCEL) {
            if (contentTop > 0) {
                int offset = contentTop;
                if (mHeaderView != null && offset > mHeaderHeight / 2) {
                    offset = offset - mHeaderHeight;
                    mBouncer.recover(true, offset, State.STATE_FIT_EXTRAS);
                } else {
                    mBouncer.recover(true, offset, State.STATE_FIT_CONTENT);
                }
            } else if (contentTop < 0) {
                int offset = mContentView.getBottom() - getBottom();
                if (mFooterView != null && (offset + mFooterHeight / 2) < 0) {
                    offset = offset + mFooterHeight;
                    mBouncer.recover(false, offset, State.STATE_FIT_EXTRAS);
                } else {
                    // add by suliyea 防止上拉回弹时候不能回复到原位.
                    if (offset != contentTop) {
                        offset = contentTop;
                    }
                    mBouncer.recover(false, offset, State.STATE_FIT_CONTENT);
                }
            }
        } else if (action == MotionEvent.ACTION_MOVE) {
            int offset = (int) (event.getY() - mLastEventY);
            return onOffset(offset);
        }
        return false;
    }

    private boolean onOffset(int offset) {
        offset = offset / 2;
        boolean handled = false;
        int contentTop = mContentView.getTop();
        if (headerBounce && !handled && contentTop >= 0 && !pullingFooter) {
            handled |= pullHeader(offset);
        }

        if (footerBounce && !handled && contentTop <= 0 && !pullingHeader) {
            handled |= pullFooter(offset);
        }
        return handled;
    }

    private void setState(boolean header, State newState) {
        String position = header ? "header" : "footer";

        if (newState == mState) {
            return;
        }

        Log.d(TAG, position + " setState " + mState + " -> " + newState);

        mState = newState;

        if (mListener != null) {
            mListener.onState(header, newState);
        }
    }

    private boolean pullHeader(int offset) {
        int scrollY = mContentView.getScrollY();
        int curTop = mContentView.getTop();

        // pull header
        if (!overScrolled || scrollY > 0
                || (offset < 0 && scrollY == 0 && curTop <= 0)) {
            return false;
        }

        pullingHeader = true;

        int nextTop = curTop + offset;
        if (nextTop <= 0) {
            offset = -curTop;
            overScrolled = false;
            mTimeBase = 0;
            nextTop = 0;
            pullingHeader = false;
            if (mState != State.STATE_FIT_CONTENT) {
                setState(true, State.STATE_FIT_CONTENT);
            }
        } else if (nextTop > 0 && nextTop <= mHeaderHeight) {
            if ((mState != State.STATE_SHOW)) {
                setState(true, State.STATE_SHOW);
            }
        } else if (nextTop > mHeaderHeight) {
            if (mState != State.STATE_OVER) {
                setState(true, State.STATE_OVER);
            }
        }

        Log.d(TAG, "pullHeader " + offset + " nextTop " + nextTop);
        offsetContent(offset);
        return true;
    }

    private boolean pullFooter(int offset) {
        int curBottom = mContentView.getBottom();
        int conBottom = this.getBottom();

        // pull footer
        if (!overScrolled || (offset > 0 && conBottom <= curBottom)) {
            return false;
        }

        pullingFooter = true;

        int nextBottom = curBottom + offset;
        if (nextBottom >= conBottom) {
            offset = conBottom - curBottom;
            overScrolled = false;
            mTimeBase = 0;
            nextBottom = conBottom;
            pullingFooter = false;
            if (mState != State.STATE_FIT_CONTENT) {
                setState(false, State.STATE_FIT_CONTENT);
            }
        } else if (nextBottom < conBottom
                && nextBottom >= (conBottom - mFooterHeight)) {
            if ((mState != State.STATE_SHOW)) {
                setState(false, State.STATE_SHOW);
            }
        } else if (nextBottom < (conBottom - mFooterHeight)) {
            if (mState != State.STATE_OVER) {
                setState(false, State.STATE_OVER);
            }
        }

        Log.d(TAG, "pullFooter " + offset + " nextBottom " + nextBottom);
        offsetContent(offset);
        return true;
    }

    private class Bouncer implements AnimatorUpdateListener, AnimatorListener {
        private ValueAnimator mAnimator;
        private int mLastOffset;
        private boolean isHeader;
        private State mTargetState;
        private boolean mCanceled;

        public void recover(boolean header, int offset, State state) {
            cancel();
            Log.d(TAG, "recover offset " + offset);
            mCanceled = false;
            isHeader = header;
            mTargetState = state;
            mAnimator = new ValueAnimator();
            mAnimator.setIntValues(0, offset);
            mLastOffset = 0;
            mAnimator.setDuration(500);
            mAnimator.setRepeatCount(0);
            if (mInterpolator == null) {
                mInterpolator = new DecelerateInterpolator();
            }
            mAnimator.setInterpolator(mInterpolator);
            mAnimator.addListener(this);
            mAnimator.addUpdateListener(this);
            mAnimator.start();
        }

        public void cancel() {
            if (mAnimator != null && mAnimator.isRunning()) {
                mAnimator.cancel();
            }
            mAnimator = null;
        }

        @Override
        public void onAnimationUpdate(ValueAnimator va) {
            int currentOffset = (Integer) va.getAnimatedValue();
            int delta = mLastOffset - currentOffset;
            Log.d(TAG, "recover delta " + delta + " currentOffset "
                    + currentOffset);
            offsetContent(delta);
            mLastOffset = currentOffset;

            if (mListener != null) {
                int contentOffset = mContentView.getTop();
                mListener.onOffset(isHeader, contentOffset);
            }
        }

        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            Log.d(TAG, "onAnimationEnd");
            mAnimator = null;
            if (!mCanceled) {
                setState(isHeader, mTargetState);
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            Log.d(TAG, "onAnimationCancel");
            mCanceled = true;
        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        int contentTop = 0;
        int contentBottom = 0;

        if (mContentView != null) {
            contentTop = mContentView.getTop();
            contentBottom = contentTop + this.getMeasuredHeight();
            mContentView.layout(0, contentTop, right, contentBottom);
        }

        if (mHeaderView != null) {
            int headerTop = contentTop - mHeaderHeight;
            mHeaderView.layout(0, headerTop, right, headerTop + mHeaderHeight);
        }

        if (mFooterView != null) {
            int footerTop = contentBottom;
            mFooterView.layout(0, footerTop, right, footerTop + mFooterHeight);
        }
    }

    private boolean offsetContent(int offset) {
        if (mContentView != null) {
            mContentView.offsetTopAndBottom(offset);
        }

        if (mHeaderView != null) {
            mHeaderView.offsetTopAndBottom(offset);
        }

        if (mFooterView != null) {
            mFooterView.offsetTopAndBottom(offset);
        }

        if (mListener != null) {
            int contentOffset = mContentView.getTop();
            boolean header = contentOffset > 0;
            mListener.onOffset(header, contentOffset);
        }

        invalidate();
        return true;
    }

    public boolean attach(View view) {
        if (view == null) {
            return false;
        }
        ViewGroup parent = (ViewGroup) view.getParent();
        ViewGroup.LayoutParams params = parent.getLayoutParams();
        int index = parent.indexOfChild(view);
        parent.removeView(view);

        parent.addView(this, index, params);

        index = 0;
        if (mHeaderView != null) {
            index = 1;
        }
        params = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        addView(view, index, params);
        this.mContentView = view;
        return true;
    }

    public void resetState() {
        if (mState != State.STATE_FIT_EXTRAS) {
            return;
        }

        if (mContentView == null) {
            return;
        }

        int offset = mContentView.getTop();
        if (offset != 0) {
            boolean header = true;
            if (offset < 0) {
                header = false;
            }
            mBouncer.recover(header, offset, State.STATE_FIT_CONTENT);
        }
    }

    public BounceScroller enableHeader(boolean bounce) {
        this.headerBounce = bounce;
        return this;
    }

    public BounceScroller enableFooter(boolean bounce) {
        this.footerBounce = bounce;
        return this;
    }

    public BounceScroller setHeaderView(View view) {
        if (mHeaderView != null) {
            removeView(mHeaderView);
            mHeaderView = null;
        }

        mHeaderView = view;
        if (mHeaderView != null) {
            mHeaderView.measure(MeasureSpec.UNSPECIFIED,
                    MeasureSpec.UNSPECIFIED);
            mHeaderHeight = mHeaderView.getMeasuredHeight();
            LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
                    mHeaderHeight);
            addView(mHeaderView, 0, params);
        }
        return this;
    }

    public View getHeaderView() {
        return mHeaderView;
    }

    public BounceScroller setFooterView(View view) {
        if (mFooterView != null) {
            removeView(mFooterView);
            mFooterView = null;
        }

        mFooterView = view;
        if (mFooterView != null) {
            mFooterView.measure(MeasureSpec.UNSPECIFIED,
                    MeasureSpec.UNSPECIFIED);
            mFooterHeight = mFooterView.getMeasuredHeight();
            LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
                    mHeaderHeight);
            addView(mFooterView, 0, params);
        }
        return this;
    }

    public View getFooterView() {
        return mFooterView;
    }

    public BounceScroller setListener(BounceListener listener) {
        this.mListener = listener;
        return this;
    }

    public BounceScroller setInterpolator(TimeInterpolator interpolator) {
        this.mInterpolator = interpolator;
        return this;
    }

    private View getTargetView(View target, MotionEvent event) {
        View view = null;
        if (target == null) {
            return view;
        }

        if (!eventInView(event, target)) {
            return view;
        }

        if (!(target instanceof ViewGroup)) {
            view = target;
            return view;
        }

        if (target instanceof AdapterView) {
            AdapterView<?> parent = (AdapterView<?>) target;
            int first = parent.getFirstVisiblePosition();
            int last = parent.getLastVisiblePosition();
            for (int index = 0; index <= (last - first); ++index) {
                View child = parent.getChildAt(index);
                if (!eventInView(event, child)) {
                    continue;
                }
                if (!(child instanceof ViewGroup)) {
                    view = child;
                    return view;
                }

                view = getTargetView(child, event);
                // stop search in current view group
                return view;
            }
        } else if (target instanceof ViewGroup) {
            ViewGroup parent = (ViewGroup) target;
            int childCount = parent.getChildCount();

            // with z-order
            for (int index = childCount - 1; index >= 0; --index) {
                View child = parent.getChildAt(index);
                if (!eventInView(event, child)) {
                    continue;
                }

                if (!(child instanceof ViewGroup)) {
                    view = child;
                    return view;
                }

                view = getTargetView(child, event);
                // stop search in current view group
                return view;
            }
        }

        // set view as group self
        view = target;
        return view;
    }

    private boolean eventInView(MotionEvent event, View view) {
        if (event == null || view == null) {
            return false;
        }

        int eventX = (int) event.getRawX();
        int eventY = (int) event.getRawY();

        int[] location = new int[2];
        view.getLocationOnScreen(location);

        int width = view.getWidth();
        int height = view.getHeight();
        int left = location[0];
        int top = location[1];
        int right = left + width;
        int bottom = top + height;

        Rect rect = new Rect(left, top, right, bottom);
        boolean contains = rect.contains(eventX, eventY);
        return contains;
    }


    public interface BounceListener {

        public void onState(boolean header, BounceScroller.State state);

        public void onOffset(boolean header, int offset);
    }

}

使用方法:

<com.iszcc.car.dspcar.view.BounceScroller
        android:id="@+id/bound_scroller"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <ListView
            android:id="@+id/lv_bleList"
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:layout_marginStart="30.0dip" />

    </com.iszcc.car.dspcar.view.BounceScroller>

 

 

转载至:https://www.jianshu.com/p/c68f57805761

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值