Android 自定义ScrollView 支持惯性滑动,惯性回弹效果。支持上拉加载更多

Posted 莫水千流

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 自定义ScrollView 支持惯性滑动,惯性回弹效果。支持上拉加载更多相关的知识,希望对你有一定的参考价值。

先讲下原理:

ScrollView的子View 主要分为3部分:head头部,滚动内容,fooder底部

我们实现惯性滑动,以及回弹,都是靠超过head或者fooder 就重新滚动到  ,内容的顶部或者底部。

之前看了Pulltorefresh 他是通过不断改变 head或者 fooder的 pading 值来实现 上拉或者 下拉的效果。感觉有点不流畅,而且层次嵌套得比较多。当然他的好处是扩展性好。

因工作需求,需要层次嵌套少,对性能要求非常高。因此重新自定义了ViewGroup实现。

技术分享

直接上代码:

  1. package com.example.administrator.customscrollview;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.util.AttributeSet;  
  6. import android.util.Log;  
  7. import android.view.Gravity;  
  8. import android.view.MotionEvent;  
  9. import android.view.VelocityTracker;  
  10. import android.view.View;  
  11. import android.view.ViewConfiguration;  
  12. import android.view.ViewGroup;  
  13. import android.widget.OverScroller;  
  14.   
  15. /** 
  16.  * 自定义 pulltorefresh Layout 
  17.  * TODO: ferris 2015年9月11日 18:52:40 
  18.  */  
  19. public class PullTorefreshScrollView extends ViewGroup {  
  20.   
  21.     private FoodeLayout fooder_layout;// top and buttom  
  22.     private View top_layout;  
  23.   
  24.     private int desireWidth, desireHeight;  
  25.     private VelocityTracker velocityTracker;  
  26.     private int mPointerId;  
  27.     private float x, y;  
  28.     private OverScroller mScroller;  
  29.     private int maxFlingVelocity, minFlingVelocity;  
  30.     private int mTouchSlop;  
  31.     protected Boolean isMove = false;  
  32.     protected float downX = 0, downY = 0;  
  33.     private int top_hight = 0;  
  34.     private int scrollYButtom = 0;  
  35.     private int nScrollYButtom = 0;  
  36.   
  37.     private int pullDownMin = 0;  
  38.     private Boolean isEnablePullDown = true;  
  39.   
  40.     private Boolean isFirst=true;  
  41.   
  42.     public void setEnablePullDown(Boolean isEnablePullDown) {  
  43.         this.isEnablePullDown = isEnablePullDown;  
  44.     }  
  45.   
  46.     public PullTorefreshScrollView(Context context) {  
  47.         super(context);  
  48.         init(null, 0);  
  49.     }  
  50.   
  51.     public PullTorefreshScrollView(Context context, AttributeSet attrs) {  
  52.         super(context, attrs);  
  53.         init(attrs, 0);  
  54.     }  
  55.   
  56.     public PullTorefreshScrollView(Context context, AttributeSet attrs, int defStyle) {  
  57.         super(context, attrs, defStyle);  
  58.         init(attrs, defStyle);  
  59.     }  
  60.   
  61.     private void init(AttributeSet attrs, int defStyle) {  
  62.         // Load attributes  
  63. //        final TypedArray a = getContext().obtainStyledAttributes(  
  64. //                attrs, R.styleable.PullTorefreshScrollView, defStyle, 0);  
  65. //  
  66. //  
  67. //        a.recycle();  
  68.         mScroller = new OverScroller(getContext());  
  69.         maxFlingVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity();  
  70.         minFlingVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();  
  71.         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();  
  72.     }  
  73.   
  74.     @Override  
  75.     protected void onFinishInflate() {  
  76.         super.onFinishInflate();  
  77.         fooder_layout = (FoodeLayout) findViewById(R.id.fooder_layout);  
  78.         top_layout = findViewById(R.id.top_layout);  
  79.   
  80.   
  81.         if (isEnablePullDown) {  
  82.             fooder_layout.showFooderPull();  
  83.         } else {  
  84.             fooder_layout.hideFooder();  
  85.         }  
  86.     }  
  87.   
  88.   
  89.     public int getScrollYTop() {  
  90.         return top_hight;  
  91.     }  
  92.   
  93.     public int getScrollYButtom() {  
  94.         return scrollYButtom;  
  95.     }  
  96.   
  97.     public int getNScrollYTop() {  
  98.         return 0;  
  99.     }  
  100.   
  101.     public int getNScrollYButtom() {  
  102.         return nScrollYButtom;  
  103.     }  
  104.   
  105.     public int measureWidth(int widthMeasureSpec) {  
  106.         int result = 0;  
  107.         int measureMode = MeasureSpec.getMode(widthMeasureSpec);  
  108.         int width = MeasureSpec.getSize(widthMeasureSpec);  
  109.         switch (measureMode) {  
  110.             case MeasureSpec.AT_MOST:  
  111.             case MeasureSpec.EXACTLY:  
  112.                 result = width;  
  113.                 break;  
  114.             default:  
  115.                 break;  
  116.         }  
  117.         return result;  
  118.     }  
  119.   
  120.     public int measureHeight(int heightMeasureSpec) {  
  121.         int result = 0;  
  122.         int measureMode = MeasureSpec.getMode(heightMeasureSpec);  
  123.         int height = MeasureSpec.getSize(heightMeasureSpec);  
  124.         switch (measureMode) {  
  125.             case MeasureSpec.AT_MOST:  
  126.             case MeasureSpec.EXACTLY:  
  127.                 result = height;  
  128.                 break;  
  129.             default:  
  130.                 break;  
  131.         }  
  132.         return result;  
  133.     }  
  134.   
  135.     @Override  
  136.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  137.         // 计算所有child view 要占用的空间  
  138.         int width = measureWidth(widthMeasureSpec);  
  139.         int height = measureHeight(heightMeasureSpec);  
  140.   
  141.         desireWidth = 0;  
  142.         desireHeight = 0;  
  143.         int count = getChildCount();  
  144.         for (int i = 0; i < count; ++i) {  
  145.             View v = getChildAt(i);  
  146.   
  147.             if (v.getVisibility() != View.GONE) {  
  148.   
  149.                 LayoutParams lp = (LayoutParams) v.getLayoutParams();  
  150.                 measureChildWithMargins(v, widthMeasureSpec, 0,  
  151.                         heightMeasureSpec, 0);  
  152.   
  153.                 //只是在这里增加了垂直或者水平方向的判断  
  154.                 if (v.getId() == R.id.top_layout) {  
  155.                     top_hight = v.getMeasuredHeight();  
  156.                 }  
  157.                 desireWidth = Math.max(desireWidth, v.getMeasuredWidth()  
  158.                         + lp.leftMargin + lp.rightMargin);  
  159.                 desireHeight += v.getMeasuredHeight() + lp.topMargin  
  160.                         + lp.bottomMargin;  
  161.   
  162.             }  
  163.         }  
  164.   
  165.         // count with padding  
  166.         desireWidth += getPaddingLeft() + getPaddingRight();  
  167.         desireHeight += getPaddingTop() + getPaddingBottom();  
  168.   
  169.         // see if the size is big enough  
  170.         desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());  
  171.         desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());  
  172.   
  173.   
  174.         //处理内容比较少的时候,就添加一定的高度  
  175.         int scrollHight = height + top_hight * 2;  
  176.         if (scrollHight > desireWidth) {  
  177.             int offset = scrollHight - desireHeight;  
  178.             View view = new View(getContext());  
  179.             view.setBackgroundResource(R.color.top_layout_color);  
  180.             LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, offset);  
  181.             addView(view, getChildCount() - 1, lp);  
  182.             desireWidth = scrollHight;  
  183.         }  
  184.   
  185.   
  186.         setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec),  
  187.                 resolveSize(desireHeight, heightMeasureSpec));  
  188.   
  189.         scrollYButtom = desireHeight - getMeasuredHeight() - top_hight;  
  190.         nScrollYButtom = desireHeight - getMeasuredHeight();  
  191.         //如果上啦拖出一半的高度,就代表将要执行上啦  
  192.         pullDownMin = nScrollYButtom - top_hight / 2;  
  193.         if(isFirst){  
  194.             scrollTo(0, top_hight);  
  195.             isFirst=false;  
  196.         }  
  197.   
  198.     }  
  199.   
  200.     @Override  
  201.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  202.         final int parentLeft = getPaddingLeft();  
  203.         final int parentRight = r - l - getPaddingRight();  
  204.         final int parentTop = getPaddingTop();  
  205.         final int parentBottom = b - t - getPaddingBottom();  
  206.   
  207.         if (BuildConfig.DEBUG)  
  208.             Log.d("onlayout", "parentleft: " + parentLeft + "   parenttop: "  
  209.                     + parentTop + "   parentright: " + parentRight  
  210.                     + "   parentbottom: " + parentBottom);  
  211.   
  212.         int left = parentLeft;  
  213.         int top = parentTop;  
  214.   
  215.         int count = getChildCount();  
  216.         for (int i = 0; i < count; ++i) {  
  217.             View v = getChildAt(i);  
  218.             if (v.getVisibility() != View.GONE) {  
  219.                 LayoutParams lp = (LayoutParams) v.getLayoutParams();  
  220.                 final int childWidth = v.getMeasuredWidth();  
  221.                 final int childHeight = v.getMeasuredHeight();  
  222.                 final int gravity = lp.gravity;  
  223.                 final int horizontalGravity = gravity  
  224.                         & Gravity.HORIZONTAL_GRAVITY_MASK;  
  225.                 final int verticalGravity = gravity  
  226.                         & Gravity.VERTICAL_GRAVITY_MASK;  
  227.   
  228.   
  229.                 // layout vertical, and only consider horizontal gravity  
  230.   
  231.                 left = parentLeft;  
  232.                 top += lp.topMargin;  
  233.                 switch (horizontalGravity) {  
  234.                     case Gravity.LEFT:  
  235.                         break;  
  236.                     case Gravity.CENTER_HORIZONTAL:  
  237.                         left = parentLeft  
  238.                                 + (parentRight - parentLeft - childWidth) / 2  
  239.                                 + lp.leftMargin - lp.rightMargin;  
  240.                         break;  
  241.                     case Gravity.RIGHT:  
  242.                         left = parentRight - childWidth - lp.rightMargin;  
  243.                         break;  
  244.                 }  
  245.                 v.layout(left, top, left + childWidth, top + childHeight);  
  246.                 top += childHeight + lp.bottomMargin;  
  247.             }  
  248.   
  249.         }  
  250.   
  251.   
  252.     }  
  253.   
  254.     @Override  
  255.     protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {  
  256.         return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
  257.                 ViewGroup.LayoutParams.MATCH_PARENT);  
  258.     }  
  259.   
  260.     @Override  
  261.     public android.view.ViewGroup.LayoutParams generateLayoutParams(  
  262.             AttributeSet attrs) {  
  263.         return new LayoutParams(getContext(), attrs);  
  264.     }  
  265.   
  266.     @Override  
  267.     protected android.view.ViewGroup.LayoutParams generateLayoutParams(  
  268.             android.view.ViewGroup.LayoutParams p) {  
  269.         return new LayoutParams(p);  
  270.     }  
  271.   
  272.     public static class LayoutParams extends MarginLayoutParams {  
  273.         public int gravity = -1;  
  274.   
  275.         public LayoutParams(Context c, AttributeSet attrs) {  
  276.             super(c, attrs);  
  277.   
  278.             TypedArray ta = c.obtainStyledAttributes(attrs,  
  279.                     R.styleable.SlideGroup);  
  280.   
  281.             gravity = ta.getInt(R.styleable.SlideGroup_layout_gravity, -1);  
  282.   
  283.             ta.recycle();  
  284.         }  
  285.   
  286.         public LayoutParams(int width, int height) {  
  287.             this(width, height, -1);  
  288.         }  
  289.   
  290.         public LayoutParams(int width, int height, int gravity) {  
  291.             super(width, height);  
  292.             this.gravity = gravity;  
  293.         }  
  294.   
  295.         public LayoutParams(android.view.ViewGroup.LayoutParams source) {  
  296.             super(source);  
  297.         }  
  298.   
  299.         public LayoutParams(MarginLayoutParams source) {  
  300.             super(source);  
  301.         }  
  302.     }  
  303.   
  304.   
  305.     /** 
  306.      * onInterceptTouchEvent()用来询问是否要拦截处理。 onTouchEvent()是用来进行处理。 
  307.      * <p/> 
  308.      * 例如:parentLayout----childLayout----childView 事件的分发流程: 
  309.      * parentLayout::onInterceptTouchEvent()---false?---> 
  310.      * childLayout::onInterceptTouchEvent()---false?---> 
  311.      * childView::onTouchEvent()---false?---> 
  312.      * childLayout::onTouchEvent()---false?---> parentLayout::onTouchEvent() 
  313.      * <p/> 
  314.      * <p/> 
  315.      * <p/> 
  316.      * 如果onInterceptTouchEvent()返回false,且分发的子View的onTouchEvent()中返回true, 
  317.      * 那么onInterceptTouchEvent()将收到所有的后续事件。 
  318.      * <p/> 
  319.      * 如果onInterceptTouchEvent()返回true,原本的target将收到ACTION_CANCEL,该事件 
  320.      * 将会发送给我们自己的onTouchEvent()。 
  321.      */  
  322.     @Override  
  323.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  324.         final int action = ev.getActionMasked();  
  325.         if (BuildConfig.DEBUG)  
  326.             Log.d("onInterceptTouchEvent", "action: " + action);  
  327.   
  328.         if (action == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {  
  329.             // 该事件可能不是我们的  
  330.             return false;  
  331.         }  
  332.   
  333.         boolean isIntercept = false;  
  334.         switch (action) {  
  335.             case MotionEvent.ACTION_DOWN:  
  336.                 // 如果动画还未结束,则将此事件交给onTouchEvet()处理,  
  337.                 // 否则,先分发给子View  
  338.                 isIntercept = !mScroller.isFinished();  
  339.                 // 如果此时不拦截ACTION_DOWN时间,应该记录下触摸地址及手指id,当我们决定拦截ACTION_MOVE的event时,  
  340.                 // 将会需要这些初始信息(因为我们的onTouchEvent将可能接收不到ACTION_DOWN事件)  
  341.                 mPointerId = ev.getPointerId(0);  
  342. //          if (!isIntercept) {  
  343.                 downX = x = ev.getX();  
  344.                 downY = y = ev.getY();  
  345. //          }  
  346.                 break;  
  347.             case MotionEvent.ACTION_MOVE:  
  348.                 int pointerIndex = ev.findPointerIndex(mPointerId);  
  349.                 if (BuildConfig.DEBUG)  
  350.                     Log.d("onInterceptTouchEvent", "pointerIndex: " + pointerIndex  
  351.                             + ", pointerId: " + mPointerId);  
  352.                 float mx = ev.getX(pointerIndex);  
  353.                 float my = ev.getY(pointerIndex);  
  354.   
  355.                 if (BuildConfig.DEBUG)  
  356.                     Log.d("onInterceptTouchEvent", "action_move [touchSlop: "  
  357.                             + mTouchSlop + ", deltaX: " + (x - mx) + ", deltaY: "  
  358.                             + (y - my) + "]");  
  359.   
  360.                 // 根据方向进行拦截,(其实这样,如果我们的方向是水平的,里面有一个ScrollView,那么我们是支持嵌套的)  
  361.   
  362.                 if (Math.abs(y - my) >= mTouchSlop) {  
  363.                     isIntercept = true;  
  364.                 }  
  365.   
  366.   
  367.                 //如果不拦截的话,我们不会更新位置,这样可以通过累积小的移动距离来判断是否达到可以认为是Move的阈值。  
  368.                 //这里当产生拦截的话,会更新位置(这样相当于损失了mTouchSlop的移动距离,如果不更新,可能会有一点点跳的感觉)  
  369.                 if (isIntercept) {  
  370.                     x = mx;  
  371.                     y = my;  
  372.                 }  
  373.                 break;  
  374.             case MotionEvent.ACTION_CANCEL:  
  375.             case MotionEvent.ACTION_UP:  
  376.                 // 这是触摸的最后一个事件,无论如何都不会拦截  
  377.                 if (velocityTracker != null) {  
  378.                     velocityTracker.recycle();  
  379.                     velocityTracker = null;  
  380.                 }  
  381.                 break;  
  382.             case MotionEvent.ACTION_POINTER_UP:  
  383.                 solvePointerUp(ev);  
  384.   
  385.                 break;  
  386.         }  
  387.         return isIntercept;  
  388.     }  
  389.   
  390.     private void solvePointerUp(MotionEvent event) {  
  391.         // 获取离开屏幕的手指的索引  
  392.         int pointerIndexLeave = event.getActionIndex();  
  393.         int pointerIdLeave = event.getPointerId(pointerIndexLeave);  
  394.         if (mPointerId == pointerIdLeave) {  
  395.             // 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker  
  396.             int reIndex = pointerIndexLeave == 0 ? 1 : 0;  
  397.             mPointerId = event.getPointerId(reIndex);  
  398.             // 调整触摸位置,防止出现跳动  
  399.             x = event.getX(reIndex);  
  400.             y = event.getY(reIndex);  
  401.             if (velocityTracker != null)  
  402.                 velocityTracker.clear();  
  403.         }  
  404.     }  
  405.   
  406.     @Override  
  407.     public boolean onTouchEvent(MotionEvent event) {  
  408.   
  409.         final int action = event.getActionMasked();  
  410.   
  411.         if (velocityTracker == null) {  
  412.             velocityTracker = VelocityTracker.obtain();  
  413.         }  
  414.         velocityTracker.addMovement(event);  
  415.   
  416.         switch (action) {  
  417.             case MotionEvent.ACTION_DOWN:  
  418.                 // 获取索引为0的手指id  
  419.   
  420.   
  421.                 isMove = false;  
  422.                 mPointerId = event.getPointerId(0);  
  423.                 x = event.getX();  
  424.                 y = event.getY();  
  425.                 if (!mScroller.isFinished())  
  426.                     mScroller.abortAnimation();  
  427.                 break;  
  428.             case MotionEvent.ACTION_MOVE:  
  429.                 isMove = true;  
  430.                 // 获取当前手指id所对应的索引,虽然在ACTION_DOWN的时候,我们默认选取索引为0  
  431.                 // 的手指,但当有第二个手指触摸,并且先前有效的手指up之后,我们会调整有效手指  
  432.   
  433.                 // 屏幕上可能有多个手指,我们需要保证使用的是同一个手指的移动轨迹,  
  434.                 // 因此此处不能使用event.getActionIndex()来获得索引  
  435.                 final int pointerIndex = event.findPointerIndex(mPointerId);  
  436.                 float mx = event.getX(pointerIndex);  
  437.                 float my = event.getY(pointerIndex);  
  438.   
  439.                 moveBy((int) (x - mx), (int) (y - my));  
  440.   
  441.                 x = mx;  
  442.                 y = my;  
  443.                 break;  
  444.             case MotionEvent.ACTION_UP:  
  445.                 isMove = false;  
  446.                 velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);  
  447.                 float velocityX = velocityTracker.getXVelocity(mPointerId);  
  448.                 float velocityY = velocityTracker.getYVelocity(mPointerId);  
  449.   
  450.                 completeMove(-velocityX, -velocityY);  
  451.                 if (velocityTracker != null) {  
  452.                     velocityTracker.recycle();  
  453.                     velocityTracker = null;  
  454.                 }  
  455.                 break;  
  456.   
  457.             case MotionEvent.ACTION_POINTER_UP:  
  458.                 // 获取离开屏幕的手指的索引  
  459.                 isMove = false;  
  460.                 int pointerIndexLeave = event.getActionIndex();  
  461.                 int pointerIdLeave = event.getPointerId(pointerIndexLeave);  
  462.                 if (mPointerId == pointerIdLeave) {  
  463.                     // 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker  
  464.                     int reIndex = pointerIndexLeave == 0 ? 1 : 0;  
  465.                     mPointerId = event.getPointerId(reIndex);  
  466.                     // 调整触摸位置,防止出现跳动  
  467.                     x = event.getX(reIndex);  
  468.                     y = event.getY(reIndex);  
  469.                     if (velocityTracker != null)  
  470.                         velocityTracker.clear();  
  471.                 }  
  472.                 break;  
  473.             case MotionEvent.ACTION_CANCEL:  
  474.                 isMove = false;  
  475.                 break;  
  476.         }  
  477.         return true;  
  478.     }  
  479.   
  480.     private Boolean isPull = false;  
  481.   
  482.     //此处的moveBy是根据水平或是垂直排放的方向,  
  483. //来选择是水平移动还是垂直移动  
  484.     public void moveBy(int deltaX, int deltaY) {  
  485.         if (BuildConfig.DEBUG)  
  486.             Log.d("moveBy", "deltaX: " + deltaX + "    deltaY: " + deltaY);  
  487.         if (Math.abs(deltaY) >= Math.abs(deltaX)) {  
  488.             int mScrollY = getScrollY();  
  489.             if (mScrollY <= 0) {  
  490.                 scrollTo(0, 0);  
  491.             } else if (mScrollY >= getNScrollYButtom()) {  
  492.                 scrollTo(0, getNScrollYButtom());  
  493.   
  494.   
  495.             } else {  
  496.                 scrollBy(0, deltaY);  
  497.             }  
  498.   
  499.             if (isEnablePullDown && deltaY > 0 && mScrollY >= pullDownMin) {  
  500.                 isPull = true;  
  501.                 Log.d("onlayout", "isPull: true");  
  502.             }  
  503.         }  
  504.   
  505.   
  506.     }  
  507.   
  508.     private void completeMove(float velocityX, float velocityY) {  
  509.   
  510.         int mScrollY = getScrollY();  
  511.         int maxY = getScrollYButtom();  
  512.         int minY = getScrollYTop();  
  513.   
  514.   
  515.         if (mScrollY >= maxY) {//如果滚动,超过了 下边界,就回弹到下边界  
  516.   
  517.             if (isPull) {//滚动到最底部  
  518.                 mScroller.startScroll(0, mScrollY, 0, getNScrollYButtom() - mScrollY, 300);  
  519.                 invalidate();  
  520.   
  521.                 //显示雷达  
  522.                 fooder_layout.showFooderRadar();  
  523.                 if (pullDownListem != null) {  
  524.                     pullDownListem.onPullDown();  
  525.                 }  
  526.                 Log.d("onlayout", "isPull: true 滚动到最底部,显示出雷达");  
  527.   
  528.             } else {  
  529.                 mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY);  
  530.                 invalidate();  
  531.                 Log.d("onlayout", "isPull: true");  
  532.             }  
  533.   
  534.         } else if (mScrollY <= minY) {//如果滚动,超过了上边界,就回弹到上边界  
  535.             // 超出了上边界,弹回  
  536.             mScroller.startScroll(0, mScrollY, 0, minY - mScrollY);  
  537.             invalidate();  
  538.         } else if (Math.abs(velocityY) >= minFlingVelocity && maxY > 0) {//大于1页的时候  
  539. //            mScroller.fling(0, mScrollY, 0, (int) (velocityY * 1.2f), 0, 0, minY, maxY);  
  540.             mScroller.fling(0, mScrollY, 0, (int) (velocityY * 2f), 0, 0, getNScrollYTop(), getNScrollYButtom());  
  541.             invalidate();  
  542.         }  
  543.   
  544.   
  545.     }  
  546.   
  547.   
  548.     public void ifNeedScrollBack() {  
  549.         int mScrollY = getScrollY();  
  550.         int maxY = getScrollYButtom();  
  551.         int minY = getScrollYTop();  
  552.         if (mScrollY > maxY) {  
  553.             // 超出了下边界,弹回  
  554.             mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY);  
  555.   
  556.             invalidate();  
  557.   
  558.         } else if (mScrollY < minY) {  
  559.             // 超出了上边界,弹回  
  560.             mScroller.startScroll(0, mScrollY, 0, minY - mScrollY);  
  561.             invalidate();  
  562.         }  
  563.     }  
  564.   
  565.     @Override  
  566.     protected void onScrollChanged(int l, int t, int oldl, int oldt) {  
  567.         super.onScrollChanged(l, t, oldl, oldt);  
  568.     }  
  569.   
  570.     @Override  
  571.     public void computeScroll() {  
  572.         if (mScroller.computeScrollOffset()) {  
  573.   
  574.             scrollTo(0, mScroller.getCurrY());  
  575.   
  576.             postInvalidate();  
  577.   
  578.         } else {  
  579.             Log.d("onlayout", "computeScroll,isMove:"+isMove+",isPull:"+isPull);  
  580.             if (!isMove && !isPull) {  
  581.                 ifNeedScrollBack();  
  582.             }  
  583.   
  584.         }  
  585.     }  
  586.   
  587.   
  588.     public void onPullSuccess() {  
  589.   
  590.         soomToBack();  
  591.     }  
  592.   
  593.     public void soomToBack() {  
  594.         int mScrollY = getScrollY();  
  595.         int maxY = getScrollYButtom();  
  596.         Log.d("onlayout", "soomToBack: (maxY - mScrollY)="+(maxY - mScrollY)+",maxY="+maxY+",mScrollY="+mScrollY);  
  597.         // 超出了下边界,弹回  
  598.         mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY, 300);  
  599.         invalidate();  
  600.         postDelayed(new Runnable() {  
  601.             @Override  
  602.             public void run() {  
  603.                 fooder_layout.showFooderPull();  
  604.                 isPull = false;  
  605.             }  
  606.         }, 310);  
  607.   
  608.   
  609.     }  
  610.   
  611.     private PullDownListem pullDownListem;  
  612.   
  613.     public void setPullDownListem(PullDownListem pullDownListem) {  
  614.         this.pullDownListem = pullDownListem;  
  615.     }  
  616.   
  617.     public interface PullDownListem {  
  618.   
  619.         public void onPullDown();  
  620.   
  621.     }  

以上是关于Android 自定义ScrollView 支持惯性滑动,惯性回弹效果。支持上拉加载更多的主要内容,如果未能解决你的问题,请参考以下文章

Android -- 自定义ScrollView实现放大回弹效果

自定义 Android ScrollView 的垂直滚动条

Android中Scrollview嵌套viewpage问题:viewpage设置非固定高点,按照自定义的scrollview 还是不能解决.

Android的FixScrollView自定义控件

将自定义视图放入自定义 ScrollView

android 自定义scrollview 仿QQ空间效果 下拉伸缩顶部图片,上拉回弹 上拉滚动顶部title 颜色渐变