android ScrollView实现原理,求助
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android ScrollView实现原理,求助相关的知识,希望对你有一定的参考价值。
能解释下ScrollView滚动显示的原理吗?就比如说你的ScollView有LinearLayout然后LinearLayout内有100个Button(id=1,2,3,4......),创建初,屏幕显示id=1~10的Button,当你向下拖的时候如何重新绘制控件,显示id=3~12的Button,我看ScrollView的源代码,并没有找到这部分功能,不知道能否解释下到底是怎么实现的,在哪里实现的?多谢!!
视图的滚动过程,其实是在不断修改原点坐标。当手指触摸后,ScrollView会暂时拦截触摸事件,使用一个计时器。假如在计时器到点后没有发生手指移动事件,那么ScrollView发送tracking events到被点击的subView;若是在计时器到点后发生了移动事件,那么ScrollView取消tracking自己促发滚动。
其子类可以重载
touchesShouldBegin: withEvent: inContentView: 决定自己是否接收touch事件。
pagingEnabled: 当值是YES会自动滚动到subView的边界,默认是NO。
touchesShouldCancelInContentView: 开始发送tracking messages消息给subView的时候会调用这个方法。以决定是否发送tracking messages消息到subView。假如返回NO,发送。YES则不发送。若是canCancelContentTouches属性是NO,则不调用这个方法来影响如何处理滚动手势。
ScrollView还可处理缩放和平移手势,要实现这必须实现委托viewForZoomingInScrollView:和scrollViewDidEndZooming: withView: atScale:两个方法。另外maximumZoomScale和minimumZoomScale两个属性要不一样。
常用属性介绍
maximumZoomScale 能放大的最大倍数,是浮点数。
minimumZoomScale 能缩小的最小倍数,是浮点数。
pagingEnabled 是否自动滚动到subView边界
scrollEnabled 是否可以滚动
contentSize 里面内容的大小,即可以滚动的大小,默认是0,没有滚动效果
showsHorizontalScrollIndicator 滚动时是否显示水平滚动条
showsVerticalScrollIndicator 滚动时是否显示垂直滚动条
bounces 默认是YES,就是滚动超过边界会反弹,即有反弹回来的效果。若是NO,则滚动到达边界会立刻停止
bouncesZoom 与bounces类似,只是反映在缩放效果上。
directionalLockEnabled 默认是NO,可以在垂直和水平方向同时运动。当值是YES时,视哪个方向开始则锁定另外一个方向的滚动。
indicatorStyle 滚动条的样式。总共3色:默认、黑、白
scrollIndicatorInsets 设置滚动条位置
tracking 当touch后还没有拖动的时候是YES,否则NO
zoomBouncing 当内容放大到最大或者最小的时候值是YES,否则NO
zooming 当正在缩放的时候值是YES,否则NO
decelerating 当滚动后,手指放开但还在继续滚动中。此时是YES,其它时候都是NO
decelerationRate 设置手指放开后的减速率
基本使用方法:
初始化:一般的控件初始化都是可以用alloc和init来初始化的。
UIScrollView *sv = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0,0.0,self.view.frame.size.width, 400)];
关于控件添加与初始化,建议都采用代码调用合适的初始化方法来操作,虽然IB布局能够节省时间,但不能哪过很好了解整个代码执行流程。
委托方法:UIScrollView也要指定委托对象,该委托对象的控制器同样也要遵循UIScrollViewDelegate协议,实现其相应的代理方法。
scrollViewDidScroll:
scrollViewWillBeginDragging:
scrollViewDidEndDragging:
scrollViewDidEndDecelerating:
属性作用CGPoint contentOffSet监控目前滚动的位置CGSize contentSize滚动范围的大小UIEdgeInsets contentInset视图在scrollView中的位置id<UIScrollerViewDelegate>
delegate设置协议BOOL directionalLockEnabled指定控件是否只能在一个方向上滚动BOOL bounces控制控件遇到边框是否反弹BOOL alwaysBounceVertical控制垂直方向遇到边框是否反弹BOOL alwaysBounceHorizontal控制水平方向遇到边框是否反弹BOOL pagingEnabled控制控件是否整页翻动BOOL scrollEnabled控制控件是否能滚动BOOL showsHorizontalScrollIndicator控制是否显示水平方向的滚动条BOOL
showsVerticalScrollIndicator控制是否显示垂直方向的滚动条UIEdgeInsets scrollIndicatorInsets指定滚动条在scrollerView中的位置UIScrollViewIndicatorStyle
indicatorStyle设定滚动条的样式float decelerationRate改变scrollerView的减速点位置BOOL tracking监控当前目标是否正在被跟踪BOOL dragging监控当前目标是否正在被拖拽BOOL decelerating监控当前目标是否正在减速BOOL delaysContentTouches控制视图是否延时调用开始滚动的方法BOOL canCancelContentTouches控制控件是否接触取消touch的事件float minimumZoomScale缩小的最小比例float maximumZoomScale放大的最大比例float zoomScale设置变化比例BOOL bouncesZoom控制缩放的时候是否会反弹BOOL zooming判断控件的大小是否正在改变BOOL zoomBouncing判断是否正在进行缩放反弹BOOL scrollsToTop控制控件滚动到顶部
这里把UIScrollView的几个要点总结下:
从你的手指touch屏幕开始,scrollView开始一个timer,如果:
1. 150ms内如果你的手指没有任何动作,消息就会传给subView。
2. 150ms内手指有明显的滑动(一个swipe动作),scrollView就会滚动,消息不会传给subView,这里就是产生问题二的原因。
3. 150ms内手指没有滑动,scrollView将消息传给subView,但是之后手指开始滑动,scrollView传送touchesCancelled消息给subView,然后开始滚动。
观察下tableView的情况,你先按住一个cell,cell开始高亮,手不要放开,开始滑动,tableView开始滚动,高亮取消。
delaysContentTouches的作用:
这个标志默认是YES,使用上面的150ms的timer,如果设置为NO,touch事件立即传递给subView,不会有150ms的等待。
cancelsTouches的作用:
这个标准默认为YES,如果设置为NO,这消息一旦传递给subView,这scroll事件不会再发生。 参考技术A 1. scrollview使用的时候里面都要放一个linearlayout,这是第一步。
2. 我们先不管linearlayout里面有什么,就假设有100个button,这时候linearlayout的显示长度肯定大于屏幕尺寸,这是第二步。
3. 这里就是重点了,scrollview通过实现手势监听器接口响应滑动事件,接收到滑动消息以后通过改变内部的linearlayout的margin参数来改变显示。重点就是改变其内部的linearlayout的layoutparams参数实现移动linearlayout。方法是:首先调用getLayoutParams获取linearlayout的布局参数,然后强转为MarginLayoutparams类型,然后改变它的marginTop即可实现上下滑动效果。
参考,如果感兴趣的话可以百度下滑动菜单,我是看过那个代码以后产生的灵感。
希望能够帮到你! 参考技术B
参考scrollView的 onTouchEvent 的。。
从 scrollBy--> invalidate();-----> p.invalidateChild(this, r);
最终通过调用 scrollView的 父View来重绘 scrollView的特定范围(r)实现的。
450 case MotionEvent.ACTION_DOWN:451 /*
452 * If being flinged and user touches, stop the fling. isFinished
453 * will be false if being flinged.
454 */
455 if (!mScroller.isFinished())
456 mScroller.abortAnimation();
457
458
459 // Remember where the motion event started
460 // 记录开始的点击坐标
mLastMotionY = y;
461 break;
462 case MotionEvent.ACTION_MOVE:
463 // Scroll to follow the motion event
// 手指移动时的点击坐标
464 final int deltaY = (int) (mLastMotionY - y);
// 更新点击坐标
mLastMotionY = y;
466
// 往下滚动
467 if (deltaY < 0)
468 if (mScrollY > 0)
469 scrollBy(0, deltaY);
470
// 往上滚动
471 else if (deltaY > 0)
472 final int bottomEdge = getHeight() - mPaddingBottom;
473 final int availableToScroll = getChildAt(0).getBottom() - mScrollY - bottomEdge;
474 if (availableToScroll > 0)
475 scrollBy(0, Math.min(availableToScroll, deltaY));
476
477
478 break;追问
如果触摸点中有子控件的话,ScrollView是不会调用onTouchEvent()的,只调用了onIntercepEvent(),但是onIntercepEvent()并没有调用任何方法。。
追答你参考一下 触摸消息派发
http://androidfanlf.blogspot.jp/2013/06/android-13-view.html.
另外 onInterceptTouchEvent 事件是确定是否打断当前view的 onTouchEvent。
所有的触摸事件,都是从顶层的view一层一层派发下来的。 所以,父控件肯定比子控件先接触到
onTouchEvent。
下面有android各个版本的源代码。
http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android/。
开源的东西,你可以设置断点一点一点进去看一下就明白了。
Android 自定义ScrollView 支持惯性滑动,惯性回弹效果。支持上拉加载更多
先讲下原理:
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;
- /**
- * 自定义 pulltorefresh Layout
- * TODO: ferris 2015年9月11日 18:52:40
- */
- public class PullTorefreshScrollView extends ViewGroup {
- private FoodeLayout fooder_layout;// top and buttom
- 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) {
- // Load attributes
- // final TypedArray a = getContext().obtainStyledAttributes(
- // attrs, R.styleable.PullTorefreshScrollView, defStyle, 0);
- //
- //
- // a.recycle();
- 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) {
- // 计算所有child view 要占用的空间
- 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;
- }
- }
- // count with padding
- desireWidth += getPaddingLeft() + getPaddingRight();
- desireHeight += getPaddingTop() + getPaddingBottom();
- // see if the size is big enough
- 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;
- // layout vertical, and only consider horizontal gravity
- 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);
- }
- }
- /**
- * onInterceptTouchEvent()用来询问是否要拦截处理。 onTouchEvent()是用来进行处理。
- * <p/>
- * 例如:parentLayout----childLayout----childView 事件的分发流程:
- * parentLayout::onInterceptTouchEvent()---false?--->
- * childLayout::onInterceptTouchEvent()---false?--->
- * childView::onTouchEvent()---false?--->
- * childLayout::onTouchEvent()---false?---> parentLayout::onTouchEvent()
- * <p/>
- * <p/>
- * <p/>
- * 如果onInterceptTouchEvent()返回false,且分发的子View的onTouchEvent()中返回true,
- * 那么onInterceptTouchEvent()将收到所有的后续事件。
- * <p/>
- * 如果onInterceptTouchEvent()返回true,原本的target将收到ACTION_CANCEL,该事件
- * 将会发送给我们自己的onTouchEvent()。
- */
- @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:
- // 如果动画还未结束,则将此事件交给onTouchEvet()处理,
- // 否则,先分发给子View
- isIntercept = !mScroller.isFinished();
- // 如果此时不拦截ACTION_DOWN时间,应该记录下触摸地址及手指id,当我们决定拦截ACTION_MOVE的event时,
- // 将会需要这些初始信息(因为我们的onTouchEvent将可能接收不到ACTION_DOWN事件)
- mPointerId = ev.getPointerId(0);
- // if (!isIntercept) {
- 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) + "]");
- // 根据方向进行拦截,(其实这样,如果我们的方向是水平的,里面有一个ScrollView,那么我们是支持嵌套的)
- if (Math.abs(y - my) >= mTouchSlop) {
- isIntercept = true;
- }
- //如果不拦截的话,我们不会更新位置,这样可以通过累积小的移动距离来判断是否达到可以认为是Move的阈值。
- //这里当产生拦截的话,会更新位置(这样相当于损失了mTouchSlop的移动距离,如果不更新,可能会有一点点跳的感觉)
- 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) {
- // 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker
- 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:
- // 获取索引为0的手指id
- isMove = false;
- mPointerId = event.getPointerId(0);
- x = event.getX();
- y = event.getY();
- if (!mScroller.isFinished())
- mScroller.abortAnimation();
- break;
- case MotionEvent.ACTION_MOVE:
- isMove = true;
- // 获取当前手指id所对应的索引,虽然在ACTION_DOWN的时候,我们默认选取索引为0
- // 的手指,但当有第二个手指触摸,并且先前有效的手指up之后,我们会调整有效手指
- // 屏幕上可能有多个手指,我们需要保证使用的是同一个手指的移动轨迹,
- // 因此此处不能使用event.getActionIndex()来获得索引
- 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) {
- // 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker
- 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;
- //此处的moveBy是根据水平或是垂直排放的方向,
- //来选择是水平移动还是垂直移动
- 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) {//大于1页的时候
- // mScroller.fling(0, mScrollY, 0, (int) (velocityY * 1.2f), 0, 0, minY, maxY);
- 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实现原理,求助的主要内容,如果未能解决你的问题,请参考以下文章
Android 自定义ScrollView 支持惯性滑动,惯性回弹效果。支持上拉加载更多
求助scrollview嵌套viewpager 放listview 不能滑动
android 有弹性的ScrollView 简单实现,与处理ScrollView和ListView,GridView之间的冲突