【Scroller】scrollTo scrollBy startScroll computeScroll 自定义ViewPage 简介 示例
简介
android.widget.Scroller是用于模拟scrolling行为,它是scrolling行为的一个帮助类。我们通常通过它的 startScroll 函数来设置一个 scrolling 行为模型,即在 duration 毫秒时间内从 int startX, int startY 这个点起向X和Y方向分别滚动 int dx 和 int dy 个像素;或者通过它的 fling 函数来设置一个 fling 行为模型(特殊的scroll),即在 int startX, int startY 这个点起向X和Y方向分别以 int velocityX 和 int velocityY 个像素的速度进行滚动。然后我们可以调用 computeScrollOffset 方法计算此时scroll到的位置,并调用 getCurrX 和 getCurrY 得到到此时在X和Y方向的位置。
构造函数
- Scroller(Context context):Create a Scroller with the default duration and interpolator.
- Scroller(Context context, Interpolator interpolator):interpolator参数只是在computeScrollOffset()中用于对我们的流逝的时间(通过timePassed()取得)这值进行重新解析。If the interpolator is null, the default (viscous) interpolator will be used. "Flywheel" behavior will be in effect for apps targeting Honeycomb or newer.
- Scroller(Context context, Interpolator interpolator, boolean flywheel):Specify指定 whether or not to support progressive "flywheel" behavior in flinging.
公共函数
- void abortAnimation() 停止scroll。Stops the animation.
- boolean computeScrollOffset():计算scroll的情况,判断滚动操作是否已经完成了。Call this when you want to know the new location. If it returns true,the animation is not yet finished.
- void extendDuration(int extend):增加scroll的时间。Extend the scroll animation. This allows a running animation to scroll further and longer, when used with setFinalX(int) orsetFinalY(int).
- void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY):模拟fling形式的scroll行为。int startX, int startY 代表起点,int velocityX, int velocityY代表初速度,int minX, int maxX, int minY, int maxY代表终点的范围
- final void forceFinished(boolean finished):强制设置为scroll状态。Force the finished field to a particular特定的 value.
- float getCurrVelocity():得到当前速度。该值是X方向和Y方向的合成值。Returns the current velocity. Return the original velocity less the deceleration减速. Result may be negative负值.
- final int getCurrX():得到当前的X坐标。Returns the current X offset in the scroll.
- final int getDuration():得到设置的scroll行为的总时间值。Returns how long the scroll event will take, in milliseconds.
- final int getFinalX():得到scroll行为终点的X值。Returns where the scroll will end.
- void setFinalX(int newX):设置scroll行为的终点的X值。Sets the final position (X) for this scroller.
- final int getStartX():得到scroll行为起点的X值。Returns the start X offset in the scroll.
- final boolean isFinished():返回scroll行为是否结束。Returns whether the scroller has finished scrolling.True if the scroller has finished scrolling, false otherwise.
- boolean isScrollingInDirection(float xvel, float yvel):这是一个public类型的hide方法
- final void setFriction(float friction):设置摩擦力。The amount of friction applied to flings. The default value is ViewConfiguration的getScrollFriction.
- void startScroll(int startX, int startY, int dx, int dy):设置一个scrolling行为模型,即在默认时间(250毫秒)内从int startX, int startY 这个点起向X和Y方向分别滚动 int dx 和 int dy 个像素。Start scrolling by providing a starting point and the distance距离 to travel.
- void startScroll(int startX, int startY, int dx, int dy, int duration):指定时间
- int timePassed():从scroll开始到现在已经过去的时间。其值为 AnimationUtils.currentAnimationTimeMillis() - mStartTime。Returns the time elapsed消逝、过去 since the beginning of the scrolling.
View与Scroller的关系
Scroller是一个封装位置和速度等信息的变量,startScroll()函数只是对它的一些成员变量做一些设置,这个设置的唯一效果就是导致mScroller.computeScrollOffset()返回true,而并不会导致View的滚动。
这里大家可能就会有一个疑问,既然startScroll()只是虚晃一枪,那scroll的动态效果到底是谁触发的呢?
其实是invalidate方法,或导致invalidate方法被调用的其他操作。
当View重绘时(即因故调用了其invalidate方法),若此时mScroller.startScroll方法被调用了,那么mScroller将会存在一些有效的设置。然后当整个View系统自上而下重新绘制过程中调用到drawChild时,也会使得View的computeScroll函数被触发,如果我们在computeScroll方法中对View进行了移动(调用其自身的scrollTo、scrollBy方法),将会导致系统不断的重绘,直到startScroll的mScroller.computeScrollOffset()返回false才停下来。
所以,我们可以在父容器重画自己孩子过程中,在调用孩子的computScroll方法时,在这个方法里去获取事先设置好的成员变量mScroller中的位置、时间、加速度等信息,用这些参数来做我们想做的事情。
我们再看下View中computeScroll方法的默认实现:
/**
* Called by a parent to request that a child update its values for mScrollX and mScrollY if necessary.
* This will typically通常 be done if the child is animating a scroll在滚动 using a Scroller object.
*/
public void computeScroll() {
}
可以看到,View的computeScroll是一个空函数,很明显我们需要去实现它,至于做什么,就由我们自己来决定了。
示例代码
/**
* 自定义模仿ViewPage,只实现其最基本的滑动平滑切换item的功能
* @author 白乾涛
*/
public class ScrollerLayout extends ViewGroup {
private Scroller mScroller = new Scroller(getContext(), new AccelerateInterpolator());//可以指定加速器
private int mTouchSlop = 20;//判定为拖动的最小移动像素数
private float mXDown;//手指按下时的屏幕坐标
private float mLastX;//【上次】触发ACTION_MOVE事件时的屏幕坐标
private int leftBorder;//界面可滚动的左边界
private int rightBorder;//界面可滚动的右边界
public ScrollerLayout(Context context) {
this(context, null);
}
public ScrollerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);// 为ScrollerLayout中的每一个子控件测量大小
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
int left = i * childView.getMeasuredWidth();
int right = (i + 1) * childView.getMeasuredWidth();
childView.layout(left, 0, right, childView.getMeasuredHeight());// 为ScrollerLayout中的每一个子控件在水平方向上进行布局
}
// 初始化左右边界值
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.i("bqt", "onInterceptTouchEvent" + "--" + mLastX);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mXDown = event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
// 当手指拖动值大于TouchSlop时,认为应该进行滚动,拦截子控件的事件,之后就会将事件交给自己的onTouchEvent()方法来处理
if (Math.abs(event.getRawX() - mXDown) > mTouchSlop) {
mLastX = event.getRawX();//记录拦截事件之前、即调用自己的onTouchEvent()方法之前的触摸点的位置
return true;
}
break;
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("bqt", "onTouchEvent" + "--" + mLastX);
switch (event.getAction()) {
//1、在拖动过程中,使用View自身的scrollTo和scrollBy方法滚动自身的【内容】
//使用scrollTo和scrollBy滑动的过程本身并非是平滑的,但是由于ACTION_MOVE事件比较密集,所以看起来像是平滑的一样
case MotionEvent.ACTION_MOVE:
float scrolledX = mLastX - event.getRawX();//上一次回调与此次回调期间,用户拖动了多少距离
if (getScrollX() + scrolledX < leftBorder) scrollTo(leftBorder, 0);//scrollTo()是让View相对于初始的位置滚动某段距离,【左上】滚动为正
else if (getScrollX() + getWidth() + scrolledX > rightBorder) scrollTo(rightBorder - getWidth(), 0);
else scrollBy((int) scrolledX, 0);//scrollBy()是让View相对于当前的位置滚动某段距离。用户拖动了多少这里就scrollBy多少
mLastX = event.getRawX();
break;
//2、当手指抬起时,使用Scroller的startScroll方法【初始化滚动数据】。注意调用Scroller的startScroll方法本身并不会导致View的滑动!
case MotionEvent.ACTION_UP:
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
int dx = targetIndex * getWidth() - getScrollX();
mScroller.startScroll(getScrollX(), 0, dx, 0, 100);//参数:滚动开始时X/Y坐标,横/纵向滚动的距离,滚动时间,【左上】滚动为正
invalidate();//一定要在startScroll后刷新才可以
break;
}
return super.onTouchEvent(event);
}
@Override
//3、重写View的computeScroll()方法,并在其内部完成平滑滚动的逻辑。在整个后续的平滑滚动过程中,此方法是会一直被调用的
public void computeScroll() {
if (mScroller.computeScrollOffset()) {//判断滚动操作是否已经完成了
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//把Scroller的currX和currY坐标传入。由此可以看出,Scroller只是提供数据用的
invalidate();
}
}
}本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/6d790505afd442c5045fd61fc8617971.html
分类:
03 可能乱码的文章
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!