先讲下原理:
ScrollView的子View 主要分为3部分:head头部,滚动内容,fooder底部
我们实现惯性滑动,以及回弹,都是靠超过head或者fooder 就重新滚动到 ,内容的顶部或者底部。
之前看了Pulltorefresh 他是通过不断改变 head或者 fooder的 pading 值来实现 上拉或者 下拉的效果。感觉有点不流畅,而且层次嵌套得比较多。当然他的好处是扩展性好。
因工作需求,需要层次嵌套少,对性能要求非常高。因此重新自定义了ViewGroup实现。
直接上代码:
- package com.example.administrator.customscrollview;
-
- import android.content.Context;
- import android.content.res.TypedArray;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.Gravity;
- import android.view.MotionEvent;
- import android.view.VelocityTracker;
- import android.view.View;
- import android.view.ViewConfiguration;
- import android.view.ViewGroup;
- import android.widget.OverScroller;
-
- public class PullTorefreshScrollView extends ViewGroup {
-
- private FoodeLayout fooder_layout;
- private View top_layout;
-
- private int desireWidth, desireHeight;
- private VelocityTracker velocityTracker;
- private int mPointerId;
- private float x, y;
- private OverScroller mScroller;
- private int maxFlingVelocity, minFlingVelocity;
- private int mTouchSlop;
- protected Boolean isMove = false;
- protected float downX = 0, downY = 0;
- private int top_hight = 0;
- private int scrollYButtom = 0;
- private int nScrollYButtom = 0;
-
- private int pullDownMin = 0;
- private Boolean isEnablePullDown = true;
-
- private Boolean isFirst=true;
-
- public void setEnablePullDown(Boolean isEnablePullDown) {
- this.isEnablePullDown = isEnablePullDown;
- }
-
- public PullTorefreshScrollView(Context context) {
- super(context);
- init(null, 0);
- }
-
- public PullTorefreshScrollView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(attrs, 0);
- }
-
- public PullTorefreshScrollView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(attrs, defStyle);
- }
-
- private void init(AttributeSet attrs, int defStyle) {
-
- mScroller = new OverScroller(getContext());
- maxFlingVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity();
- minFlingVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- fooder_layout = (FoodeLayout) findViewById(R.id.fooder_layout);
- top_layout = findViewById(R.id.top_layout);
-
-
- if (isEnablePullDown) {
- fooder_layout.showFooderPull();
- } else {
- fooder_layout.hideFooder();
- }
- }
-
-
- public int getScrollYTop() {
- return top_hight;
- }
-
- public int getScrollYButtom() {
- return scrollYButtom;
- }
-
- public int getNScrollYTop() {
- return 0;
- }
-
- public int getNScrollYButtom() {
- return nScrollYButtom;
- }
-
- public int measureWidth(int widthMeasureSpec) {
- int result = 0;
- int measureMode = MeasureSpec.getMode(widthMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec);
- switch (measureMode) {
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = width;
- break;
- default:
- break;
- }
- return result;
- }
-
- public int measureHeight(int heightMeasureSpec) {
- int result = 0;
- int measureMode = MeasureSpec.getMode(heightMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- switch (measureMode) {
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = height;
- break;
- default:
- break;
- }
- return result;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- int width = measureWidth(widthMeasureSpec);
- int height = measureHeight(heightMeasureSpec);
-
- desireWidth = 0;
- desireHeight = 0;
- int count = getChildCount();
- for (int i = 0; i < count; ++i) {
- View v = getChildAt(i);
-
- if (v.getVisibility() != View.GONE) {
-
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- measureChildWithMargins(v, widthMeasureSpec, 0,
- heightMeasureSpec, 0);
-
-
- if (v.getId() == R.id.top_layout) {
- top_hight = v.getMeasuredHeight();
- }
- desireWidth = Math.max(desireWidth, v.getMeasuredWidth()
- + lp.leftMargin + lp.rightMargin);
- desireHeight += v.getMeasuredHeight() + lp.topMargin
- + lp.bottomMargin;
-
- }
- }
-
-
- desireWidth += getPaddingLeft() + getPaddingRight();
- desireHeight += getPaddingTop() + getPaddingBottom();
-
-
- desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
- desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
-
-
-
- int scrollHight = height + top_hight * 2;
- if (scrollHight > desireWidth) {
- int offset = scrollHight - desireHeight;
- View view = new View(getContext());
- view.setBackgroundResource(R.color.top_layout_color);
- LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, offset);
- addView(view, getChildCount() - 1, lp);
- desireWidth = scrollHight;
- }
-
-
- setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec),
- resolveSize(desireHeight, heightMeasureSpec));
-
- scrollYButtom = desireHeight - getMeasuredHeight() - top_hight;
- nScrollYButtom = desireHeight - getMeasuredHeight();
-
- pullDownMin = nScrollYButtom - top_hight / 2;
- if(isFirst){
- scrollTo(0, top_hight);
- isFirst=false;
- }
-
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- final int parentLeft = getPaddingLeft();
- final int parentRight = r - l - getPaddingRight();
- final int parentTop = getPaddingTop();
- final int parentBottom = b - t - getPaddingBottom();
-
- if (BuildConfig.DEBUG)
- Log.d("onlayout", "parentleft: " + parentLeft + " parenttop: "
- + parentTop + " parentright: " + parentRight
- + " parentbottom: " + parentBottom);
-
- int left = parentLeft;
- int top = parentTop;
-
- int count = getChildCount();
- for (int i = 0; i < count; ++i) {
- View v = getChildAt(i);
- if (v.getVisibility() != View.GONE) {
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- final int childWidth = v.getMeasuredWidth();
- final int childHeight = v.getMeasuredHeight();
- final int gravity = lp.gravity;
- final int horizontalGravity = gravity
- & Gravity.HORIZONTAL_GRAVITY_MASK;
- final int verticalGravity = gravity
- & Gravity.VERTICAL_GRAVITY_MASK;
-
-
-
-
- left = parentLeft;
- top += lp.topMargin;
- switch (horizontalGravity) {
- case Gravity.LEFT:
- break;
- case Gravity.CENTER_HORIZONTAL:
- left = parentLeft
- + (parentRight - parentLeft - childWidth) / 2
- + lp.leftMargin - lp.rightMargin;
- break;
- case Gravity.RIGHT:
- left = parentRight - childWidth - lp.rightMargin;
- break;
- }
- v.layout(left, top, left + childWidth, top + childHeight);
- top += childHeight + lp.bottomMargin;
- }
-
- }
-
-
- }
-
- @Override
- protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
- }
-
- @Override
- public android.view.ViewGroup.LayoutParams generateLayoutParams(
- AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected android.view.ViewGroup.LayoutParams generateLayoutParams(
- android.view.ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
-
- public static class LayoutParams extends MarginLayoutParams {
- public int gravity = -1;
-
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
-
- TypedArray ta = c.obtainStyledAttributes(attrs,
- R.styleable.SlideGroup);
-
- gravity = ta.getInt(R.styleable.SlideGroup_layout_gravity, -1);
-
- ta.recycle();
- }
-
- public LayoutParams(int width, int height) {
- this(width, height, -1);
- }
-
- public LayoutParams(int width, int height, int gravity) {
- super(width, height);
- this.gravity = gravity;
- }
-
- public LayoutParams(android.view.ViewGroup.LayoutParams source) {
- super(source);
- }
-
- public LayoutParams(MarginLayoutParams source) {
- super(source);
- }
- }
-
-
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- final int action = ev.getActionMasked();
- if (BuildConfig.DEBUG)
- Log.d("onInterceptTouchEvent", "action: " + action);
-
- if (action == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
-
- return false;
- }
-
- boolean isIntercept = false;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
-
-
- isIntercept = !mScroller.isFinished();
-
-
- mPointerId = ev.getPointerId(0);
- downX = x = ev.getX();
- downY = y = ev.getY();
- break;
- case MotionEvent.ACTION_MOVE:
- int pointerIndex = ev.findPointerIndex(mPointerId);
- if (BuildConfig.DEBUG)
- Log.d("onInterceptTouchEvent", "pointerIndex: " + pointerIndex
- + ", pointerId: " + mPointerId);
- float mx = ev.getX(pointerIndex);
- float my = ev.getY(pointerIndex);
-
- if (BuildConfig.DEBUG)
- Log.d("onInterceptTouchEvent", "action_move [touchSlop: "
- + mTouchSlop + ", deltaX: " + (x - mx) + ", deltaY: "
- + (y - my) + "]");
-
-
-
- if (Math.abs(y - my) >= mTouchSlop) {
- isIntercept = true;
- }
-
-
-
-
- if (isIntercept) {
- x = mx;
- y = my;
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
-
- if (velocityTracker != null) {
- velocityTracker.recycle();
- velocityTracker = null;
- }
- break;
- case MotionEvent.ACTION_POINTER_UP:
- solvePointerUp(ev);
-
- break;
- }
- return isIntercept;
- }
-
- private void solvePointerUp(MotionEvent event) {
-
- int pointerIndexLeave = event.getActionIndex();
- int pointerIdLeave = event.getPointerId(pointerIndexLeave);
- if (mPointerId == pointerIdLeave) {
-
- int reIndex = pointerIndexLeave == 0 ? 1 : 0;
- mPointerId = event.getPointerId(reIndex);
-
- x = event.getX(reIndex);
- y = event.getY(reIndex);
- if (velocityTracker != null)
- velocityTracker.clear();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
-
- final int action = event.getActionMasked();
-
- if (velocityTracker == null) {
- velocityTracker = VelocityTracker.obtain();
- }
- velocityTracker.addMovement(event);
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
-
-
-
- isMove = false;
- mPointerId = event.getPointerId(0);
- x = event.getX();
- y = event.getY();
- if (!mScroller.isFinished())
- mScroller.abortAnimation();
- break;
- case MotionEvent.ACTION_MOVE:
- isMove = true;
-
-
-
-
-
- final int pointerIndex = event.findPointerIndex(mPointerId);
- float mx = event.getX(pointerIndex);
- float my = event.getY(pointerIndex);
-
- moveBy((int) (x - mx), (int) (y - my));
-
- x = mx;
- y = my;
- break;
- case MotionEvent.ACTION_UP:
- isMove = false;
- velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);
- float velocityX = velocityTracker.getXVelocity(mPointerId);
- float velocityY = velocityTracker.getYVelocity(mPointerId);
-
- completeMove(-velocityX, -velocityY);
- if (velocityTracker != null) {
- velocityTracker.recycle();
- velocityTracker = null;
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
-
- isMove = false;
- int pointerIndexLeave = event.getActionIndex();
- int pointerIdLeave = event.getPointerId(pointerIndexLeave);
- if (mPointerId == pointerIdLeave) {
-
- int reIndex = pointerIndexLeave == 0 ? 1 : 0;
- mPointerId = event.getPointerId(reIndex);
-
- x = event.getX(reIndex);
- y = event.getY(reIndex);
- if (velocityTracker != null)
- velocityTracker.clear();
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- isMove = false;
- break;
- }
- return true;
- }
-
- private Boolean isPull = false;
-
-
- public void moveBy(int deltaX, int deltaY) {
- if (BuildConfig.DEBUG)
- Log.d("moveBy", "deltaX: " + deltaX + " deltaY: " + deltaY);
- if (Math.abs(deltaY) >= Math.abs(deltaX)) {
- int mScrollY = getScrollY();
- if (mScrollY <= 0) {
- scrollTo(0, 0);
- } else if (mScrollY >= getNScrollYButtom()) {
- scrollTo(0, getNScrollYButtom());
-
-
- } else {
- scrollBy(0, deltaY);
- }
-
- if (isEnablePullDown && deltaY > 0 && mScrollY >= pullDownMin) {
- isPull = true;
- Log.d("onlayout", "isPull: true");
- }
- }
-
-
- }
-
- private void completeMove(float velocityX, float velocityY) {
-
- int mScrollY = getScrollY();
- int maxY = getScrollYButtom();
- int minY = getScrollYTop();
-
-
- if (mScrollY >= maxY) {
-
- if (isPull) {
- mScroller.startScroll(0, mScrollY, 0, getNScrollYButtom() - mScrollY, 300);
- invalidate();
-
-
- fooder_layout.showFooderRadar();
- if (pullDownListem != null) {
- pullDownListem.onPullDown();
- }
- Log.d("onlayout", "isPull: true 滚动到最底部,显示出雷达");
-
- } else {
- mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY);
- invalidate();
- Log.d("onlayout", "isPull: true");
- }
-
- } else if (mScrollY <= minY) {
-
- mScroller.startScroll(0, mScrollY, 0, minY - mScrollY);
- invalidate();
- } else if (Math.abs(velocityY) >= minFlingVelocity && maxY > 0) {
- mScroller.fling(0, mScrollY, 0, (int) (velocityY * 2f), 0, 0, getNScrollYTop(), getNScrollYButtom());
- invalidate();
- }
-
-
- }
-
-
- public void ifNeedScrollBack() {
- int mScrollY = getScrollY();
- int maxY = getScrollYButtom();
- int minY = getScrollYTop();
- if (mScrollY > maxY) {
-
- mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY);
-
- invalidate();
-
- } else if (mScrollY < minY) {
-
- mScroller.startScroll(0, mScrollY, 0, minY - mScrollY);
- invalidate();
- }
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- }
-
- @Override
- public void computeScroll() {
- if (mScroller.computeScrollOffset()) {
-
- scrollTo(0, mScroller.getCurrY());
-
- postInvalidate();
-
- } else {
- Log.d("onlayout", "computeScroll,isMove:"+isMove+",isPull:"+isPull);
- if (!isMove && !isPull) {
- ifNeedScrollBack();
- }
-
- }
- }
-
-
- public void onPullSuccess() {
-
- soomToBack();
- }
-
- public void soomToBack() {
- int mScrollY = getScrollY();
- int maxY = getScrollYButtom();
- Log.d("onlayout", "soomToBack: (maxY - mScrollY)="+(maxY - mScrollY)+",maxY="+maxY+",mScrollY="+mScrollY);
-
- mScroller.startScroll(0, mScrollY, 0, maxY - mScrollY, 300);
- invalidate();
- postDelayed(new Runnable() {
- @Override
- public void run() {
- fooder_layout.showFooderPull();
- isPull = false;
- }
- }, 310);
-
-
- }
-
- private PullDownListem pullDownListem;
-
- public void setPullDownListem(PullDownListem pullDownListem) {
- this.pullDownListem = pullDownListem;
- }
-
- public interface PullDownListem {
-
- public void onPullDown();
-
- }
- }
Android 自定义ScrollView 支持惯性滑动,惯性回弹效果。支持上拉加载更多
原文:http://www.cnblogs.com/zhoug2020/p/6076194.html