自已写的一个带反弹效果的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) } } }