[Android实例]Scroll原理-附ScrollView源码分析

更新时间:2023-07-13 21:24:12 阅读: 评论:0

[Android实例]Scroll原理-附ScrollView源码分析
想象⼀下你拿着放⼤镜贴很近的看⼀副巨⼤的清明上河图, 那放⼤镜⾥可以看到的内容是很有限的,
⽽随着放⼤镜的上下左右移动,就可以看到不同的内容了
android中⼿机屏幕就相当于这个放⼤镜, ⽽看到的内容是画在⼀个⽆限⼤的画布上~
画的内容有限, ⽽⼿机屏幕可以看到的东西更有限~ 但是背景画布是⽆限的
如果把放⼤镜的移动⽐作scroll操作,那么可以理解,这个scroll的距离是⽆限制的~
只不过scroll到有图的地⽅才能看到内容
参考ScrollView理解, 当child内容过长时,有⼀部分内容是"看不到"的,相当于"在屏幕之外",
⽽随着我们的拖动滚动,则慢慢看到剩下的内容,相当于我们拿着放⼤镜向下移动~
⽽代码中的这个scroll⽅法系统提供了两个:
scrollTo和scrollBy
源码如下
/**
* Set the scrolled position of your view. This will cau a call to
*{@link #onScrollChanged(int, int, int, int)}and the view will be
* invalidated.
*@param x the x position to scroll to
*@param y the y position to scroll to
*/
public void if(mScrollX!= x ||mScrollY!= y) {
int oldX =mScrollX;
int oldY =mScrollY;
mScrollX= x;
mScrollY= y;
invalidateParentCaches();
onScrollChanged(mScrollX,mScrollY, oldX, oldY);
if(!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
/**
* Move the scrolled position of your view. This will cau a call to
*{@link #onScrollChanged(int, int, int, int)}and the view will be
* invalidated.
*@param x the amount of pixels to scroll by horizontally
*@param y the amount of pixels to scroll by vertically
*/
军事学校public void scrollBy(int x,int y) {
}
mScrollX 表⽰离视图起始位置的x⽔平⽅向的偏移量
mScrollY表⽰离视图起始位置的y垂直⽅向的偏移量
可以通过getScrollX() 和getScrollY()⽅法分别获得
两个⽅法的区别就是to参数是绝对值,by是相对于当前滚动到的位置的增量值
高一物理公式大全总结
⽐如:
mScrollX=100, mScrollY=100
scrollTo(20, 20) -> mScrollX=20, mScrollY=20;
scrollBy(20, 20) -> mScrollX=120,mScrollY=120;
注意:
这⾥mScrollX和mScrollY的值是偏移量,是相对于视图起始位置的偏移量~
天官赐福语录
所以任何view,⽆论布局是怎么样的,只要是刚初始化未经过scroll的,偏移量都是0~
即mScrollX/Y是相对于⾃⼰初始位置的偏移量,⽽不是相对于其容器的位置坐标
下⾯是就ScrollView的源码拆开了的分析,并加⼊了⼀些补充扩展,
主要内容包括
1.最基本的随着touch滚动的效果
2.fling效果,即滑动后抬起⼿后继续关⼼滚动的效果
3.over scroll效果,即拖动超出边界的处理
上述123系统都有提供相关实现⽅法,但是ScrollView默认只有1,2的实现效果,
over scroll需要我们⾃⾏进⾏⼀定处理后才可以看到~
下⾯就ScrollView的源码进⾏分析,且提供三个⾃定义ScrollView(难度依次递进)实现上⾯的三种效果,已打包成demo
后⾯源码分析时,系统是乱七⼋糟直接写⼀起时,分析的被⽐较细也⽐较乱,
demo中三个⾃定义ScrollView相当于按照难度梯度抽取出来的,
即view2是在view1基础上修改添加功能的,view3是在view2基础上修改添加功能的
可以从demo下⼿帮助理解其中原理
-----------------------------------------------------------------------------
scroll相当于⼀个拖动,我们可以⽤scrollTo/By控制其滚动到某个位置,
那⼀般ScrollView控件这种都是随着我们的⼿势⽣效的,内部原理是如何的呢~
下⾯来研究下系统ScrollView控件源码⾥⾯的具体实现~
系统考虑的东西⽐较多,研究起来较为复杂,所以先就核⼼部分拆开⼀点点研究~
⼿的拖动肯定是跟touch即触摸事件挂钩了~直接定位到ScrollView中的该⽅法
(onTouchEvent⼲什么⽤的就不扫盲了)
⾸先是ACTION_DOWN
ca MotionEvent.ACTION_DOWN: {
mIsBeingDragged = getChildCount() != 0;
if (!mIsBeingDragged) {
return fal;
}
/
*
* If being flinged and ur touches, stop the fling. isFinished
* will be fal if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
mFlingStrictSpan = null;
}
}
/
渲图
/ Remember where the motion event started
mLastMotionY = ev.getY();
mActivePointerId = ev.getPointerId(0);
break;
}
有三段代码,第⼆三段带注释,下⾯是介绍:
1.ScrollView没有child则不做处理,这个不解释,都没child滚个蛋啊
舒畅的反义词如果有child则设置标志位mIsBeingDragged即"开始拖动"(看英⽂就可以理解了)
2.看注释理解~如果还在滑动⽤户触碰了屏幕,则⽴刻停⽌滑动
mScroller是⼀个OverScroller对象,是处理滚动的,
类介绍⾥提到这个功能和Scroller类差不多,⼤部分情况下可以替代之,
区别可以简单的理解为OverScroller允许超出边界,后⾯会介绍Scroller类~
⾄于mFlingStrictSpan⽆视之
3.看注释理解~记住点击的位置
scrollerView,处理垂直滚动,这⾥就只记录Y坐标了
mActivePointerId是⽤来处理多点触控时的稳定性的,这⾥先记住作⽤就⾏了
然后是ACTION_MOVE,这个是重点,随着move我们希望控件也能随着我们的⼿的拖动滚动到所需位置ca MotionEvent.ACTION_MOVE:
if (mIsBeingDragged ) {
// Scroll to follow the motion event
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
final float y = ev.getY(activePointerIndex);
final int deltaY = ( int) ( mLastMotionY - y);
mLastMotionY = y;
final int oldX = mScrollX;
final int oldY = mScrollY;
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
if (overScrollBy(0, deltaY, 0, mScrollY,
0, range, 0, mOverscrollDistance, true)) {
// Break our velocity if we hit a scroll barrier.
什么是四轮定位
mVelocityTracker.clear();
}
onScrollChanged( mScrollX, mScrollY , oldX, oldY);
if (canOverscroll) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
if (! mEdgeGlowBottom.isFinished()) {
}
} el if (pulledToY > range) {
if (! mEdgeGlowTop.isFinished()) {
}
}
if ( mEdgeGlowTop != null
&& (! mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
invalidate();
}
}
}
break;
系统注释还是很良⼼的,⼀般以功能点为单位注释,这⾥还是根据注释去看
分两段:随着触摸事件的滚动,还有顶部底部边缘阴影的处理
阴影处理的先忽视
这⾥的触摸点获取不是直接Y,⽽是通过下⾯两句代码获取的
final int activePointerIndex = ev.findPointerIndex( mActivePointerId);
final float y = ev.getY(activePointerIndex);
上⾯已经介绍过了mActivityPointerId的保证多点触碰稳定性的作⽤,
包括onTouchEvent⾥⾯的ACTION_POINTER_DOWN/UP也是为了处理多点情况的,
为了不发散太多就不细介绍了(其实我也不是研究太透彻)
知道这样处理能防⽌多点触控的⼲扰,可以稳定获取到我们需要的触摸的y坐标就⾏了
根据现在的触摸坐标y和上次位置的y坐标mLastMotionY算出差值,即这次移动的距离deltaY
最后以获取到的这些数据进⾏滚动操作~
ScrollView中在这⾥使⽤的是overScrollBy⽅法,该⽅法是其⽗类view的⽅法,定位过去看下
其实如果要简单处理的话直接掉scrollTo⽅法就可以了~参见demo中MyScrollView1
如果觉得ScrollView⾥逻辑⽆法理解,那就可以先把上⾯demo的view1研究懂以后再继续下⽂
注意,scroll由于是没有限制的,即可以滚动到任何位置,显然不符合我们的需要,
所以我们要限制滚动范围,只在有内容的绘制部分滚动
由于ScrollView只是纵向Y轴上滚动,所以只限定y上滚动范围即可,
如下图⽰,红框是scrollview,蓝框是child,滚动范围应该是箭头所⽰部分~
即0 到 child.height-scrollview,height
⽽demo⾥view1中也添加了这么⼀段(demo是横向滚动)
// Clamp values if at the limits and record
final int left = 0;
final int right = getScrollRangeX();
// 防⽌滚动超出边界
if(scrollX > right) {
scrollX = right;
}el if(scrollX < left) {
scrollX = left;
}
-----------------------------------------------------------------------------
因为scrollView考虑的⽐较多,所以处理⿇烦点,按照源码追踪到view中的overScrollerBy⽅法
暖心简短文案/**
* Scroll the view with standard behavior for scrolling beyond the normal
* content boundaries. Views that call this method should override
*{@link #onOverScrolled(int, int, boolean, boolean)}to respond to the
* results of an over-scroll operation.
*
* Views can u this method to handle any touch or fling-bad scrolling.
*
*@param deltaX Change in X in pixels
*@param deltaY Change in Y in pixels
*@param scrollX Current X scroll value in pixels before applying deltaX
*@param scrollY Current Y scroll value in pixels before applying deltaY
*@param scrollRangeX Maximum content scroll range along the X axis
*@param scrollRangeY Maximum content scroll range along the Y axis
*@param maxOverScrollX Number of pixels to overscroll by in either direction
*          along the X axis.
*@param maxOverScrollY Number of pixels to overscroll by in either direction
*          along the Y axis.
*@param isTouchEvent true if this scroll operation is the result of a touch event.
*@return true if scrolling was clamped to an over-scroll boundary along either
*          axis, fal otherwi.
*/
@SuppressWarnings({"UnudParameters"})
protected boolean overScrollBy( int deltaX, int deltaY,
int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY,
boolean isTouchEvent) {
final int overScrollMode = mOverScrollMode;
final boolean canScrollHorizontal =
final boolean canScrollVertical =
computeVerticalScrollRange() > computeVerticalScrollExtent();
final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS ||
(overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
(overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
int newScrollX = scrollX + deltaX;
if (!overScrollHorizontal) {
maxOverScrollX = 0;
}
int newScrollY = scrollY + deltaY;
if (!overScrollVertical) {
maxOverScrollY = 0;
}
// Clamp values if at the limits and record
final int left = -maxOverScrollX;
final int right = maxOverScrollX + scrollRangeX;
final int top = -maxOverScrollY;
final int bottom = maxOverScrollY + scrollRangeY;
boolean clampedX = fal;
if (newScrollX > right) {
newScrollX = right;
clampedX = true;
} el if (newScrollX < left) {
newScrollX = left;
clampedX = true;
}
boolean clampedY = fal;
if (newScrollY > bottom) {
newScrollY = bottom;
clampedY = true;
} el if (newScrollY < top) {
newScrollY = top;
clampedY = true;
}
onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
return clampedX || clampedY;
}
⽅法的作⽤:
总结起来就是计算over scroll时scrollX/Y的值~ 并将其记录在onOverScrolled⽅法⾥
参数意义:
前8个分两组,1357是针对x轴的,2468则是y轴的,这⾥scrollView只纵向滚动,所以只处理y轴
⽐较难理解的是后俩参数
1.scrollRange
是某⽅向上滚动的范围,可以参考上⾯的图⽚,只不过要把padding部分考虑进去
下⾯是系统获取范围的⽅法
private int int scrollRange = 0;
if (getChildCount() > 0) {
嫁值连城
View child = getChildAt(0);
scrollRange = Math. max(0,
}
return scrollRange;
}
不难理解,最⼤距离的情况就是childview移动到了最顶部,然后滑动到最底部,上⾯这个⽅法算的就是这个最⼤距离2.maxOverScroll
为越界滚动最⼤距离,即在之前范围的基础上再加上这个越界最⼤距离~
ScrollView在这⾥设置的是系统默认值0
⽐如以前纵向的滚动范围是0~300,那如果这个值设为50,则最终over scroll的范围就是-50~350,⽅法内算法如下// Clamp values if at the limits and record
final int left = -maxOverScrollX;
final int right = maxOverScrollX + scrollRangeX;
final int top = -maxOverScrollY;
final int bottom = maxOverScrollY + scrollRangeY;
top=-maxOverScrollY ~ bottom=maxOverScrollY+scrollRangY
带⼊我们假设的值,那就是0~300, 注意,这个300是Y坐标300~
这⾥假设我们的maxOverScrollY不是系统默认的Y⽽是 50,
那虽然滚动范围不变还是500-200=300~ 但是实际上可以滚动的范围是⼤于300的~
效果类似于ios那种,listview到达顶部以后继续拖还可以移动~ 也可以脑补下拉刷新listview的效果

本文发布于:2023-07-13 21:24:12,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/1094960.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:滚动   位置   处理   系统   范围   理解
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图