android带反弹效果ScrollView

android带反弹效果ScrollView

自已写的一个带反弹效果的ScrollView,供参考




package cn.bassy.library.widget; 
 
import android.content.Context; 
import android.util.AttributeSet; 
import android.view.MotionEvent; 
import android.view.View; 
import android.view.animation.OvershootInterpolator; 
import android.widget.LinearLayout; 
import android.widget.ScrollView; 


import android.widget.Scroller;
 
/ ** 
 * 概述:滑动反弹组件 
 * 
 
 * 创建:2017-3-30 
 * 




 
 * 
 * @author Bassy Wain 
 */ 
public class BounceScrollView extends LinearLayout { 
    private static final String TAG = "BounceScrollView"; 
 
    private enum PullAction { 
        PullDown, PullUp, None 
    } 
 
    / ** 
     * 内容视图 
     */ 
    private ScrollView mContentLayout; 
    / ** 
     * 子View 
     */ 
    private View mChildView; 
 
    / ** 
     * 反弹效果 
     */ 
    private Scroller mScroller; 
 
    / ** 
     * 记录触摸按下位置 
     */ 
    private float mTouchDownY; 
 
    / ** 
     * 下拉偏移量与展示头部布局高度之间的比率,控制手势偏移与View偏移的比例 
     */ 
    private static final float mPullRatio = 2.5f; 
 
    / ** 
     * 当前操作类型 
     */ 
    private PullAction mPullAction = PullAction.None; 
 
 
    public BounceScrollView(Context context) { 
        super(context); 
        init(context, null); 
    } 
 
    public BounceScrollView(Context context, AttributeSet attrs) { 
        super(context, attrs); 
        init(context, attrs); 
    } 
 
    public BounceScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 
        super(context, attrs, defStyleAttr); 
        init(context, attrs); 
    } 
 
    private void init(Context context, AttributeSet attrs) { 
        setClickable(true);//设置为可点击,否则无法滚动 
 
        mScroller = new Scroller(context, new OvershootInterpolator());//带弹性效果 
        mContentLayout = createContentLayout(context); 
        addView(mContentLayout); 
    } 
 
    private ScrollView createContentLayout(Context context) { 
        ScrollView sv = new ScrollView(context); 
        sv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 
        return sv; 
    } 
 
    @Override 
    public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) { 
        if (child == mContentLayout) { 
            //内容视图交给父类处理 
            super.addView(child, index, params); 
        } else { 
            //其它情况,交给ContentLayout处理 
            mContentLayout.addView(child, index, params); 
            mChildView = mContentLayout.getChildAt(0); 
        } 
    } 
 
    @Override 
    public boolean dispatchTouchEvent(MotionEvent ev) { 
 
        switch (ev.getActionMasked()) { 
            case MotionEvent.ACTION_DOWN: { 
                //记录第一个手指的按下位置 
                mTouchDownY = ev.getY(); 
                break; 
            } 
            case MotionEvent.ACTION_MOVE: { 
                //对上拉和下拉进行计算和移动 
                final float deltaY = mTouchDownY - ev.getY(); 
 
                mPullAction = (deltaY < 0) ? (PullAction.PullDown) : 
                        (deltaY > 0 ? PullAction.PullUp : PullAction.None); 
 
                if (canPull()) { 
                    mScroller.abortAnimation(); 
                    scrollTo(getScrollX(), (int) (deltaY / mPullRatio)); 
                    if (mChildView != null) { 
                        mChildView.clearFocus();//清空子View的ACTION_DOWN状态 
                    } 
                } else { 
                    mTouchDownY = ev.getY(); 
                } 
                break; 
            } 
            case MotionEvent.ACTION_UP: { 
                //回弹 
                mPullAction = PullAction.None; 
                smoothScrollTo(getScrollX(), 0); 
                break; 
            } 
            default: 
        } 
        return super.dispatchTouchEvent(ev); 
    } 
 
    private boolean canPull() { 
 
        if (mChildView == null) { 
            //子视图为空 
            return true; 
        } else if (mPullAction == PullAction.PullDown && mContentLayout.getScrollY() <= 0) { 
            //ScrollView滚动位置在开始位置并且操作为下拉 
            return true; 
        } else if (mPullAction == PullAction.PullDown && getScrollY() > 0) { 
            //ScrollView先经过上拉拉出了一段距离(正数),再进行下拉操作,此时需要恢复位置 
            return true; 
        } else if (mPullAction == PullAction.PullUp && 
                (mContentLayout.getHeight() >= mChildView.getHeight())) { 
            //ScrollView的高度大于或等于子View的高度,此时允许上拉 
            return true; 
        } else if (mPullAction == PullAction.PullUp && 
                (mContentLayout.getHeight() + mContentLayout.getScrollY() >= 
                        mChildView.getHeight())) { 
            //ScrollView的高度 + ScrollView的滚动高度 >= 子View的高度(说明已经滚动到底部) 
            return true; 
        } else if (mPullAction == PullAction.PullUp && getScrollY() < 0) { 
            //ScrollView先经过下拉拉出了一段距离(负数),再进行上拉操作,此时需要恢复位置 
            return true; 
        } else { 
            return false; 
        } 
    } 
 
 
    / ** 
     * 平滑滚动到指定位置 
     * 
     * @param toX 目标水平位置 
     * @param toY 目标垂直位置 
     */ 
    private void smoothScrollTo(int toX, int toY) { 
 
        mScroller.abortAnimation(); 
        mScroller.forceFinished(true); 
 
        int fromX = getScrollX(); 
        int fromY = getScrollY(); 
 
        mScroller.startScroll(fromX, fromY, toX - fromX, toY - fromY, 300); 
        invalidate();//要求重绘(即调用draw,该方法会调用computeScroll) 
    } 
 
    @Override 
    public void computeScroll() { 
        if (mScroller.computeScrollOffset()) { 
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 
            invalidate();//要求重绘(即调用draw,该方法会调用computeScroll) 
        } 
    }