Android Scroller大揭秘

发布时间 - 2026-01-11 00:42:48    点击率:

在学习使用Scroller之前,需要明白scrollTo()、scrollBy()方法。

一、View的scrollTo()、scrollBy()

scrollTo、scrollBy方法是View中的,因此任何的View都可以通过这两种方法进行移动。首先要明白的是,scrollTo、scrollBy滑动的是View中的内容(而且还是整体滑动),而不是View本身。我们的滑动控件如SrollView可以限定宽、高大小,以及在布局中的位置,但是滑动控件中的内容(或者里面的childView)可以是无限长、宽的,我们调用View的scrollTo、scrollBy方法,相当于是移动滑动控件中的画布Canvas,然后进行重绘,屏幕上也就显示相应的内容。如下:

1、getScrollX()、getScrollY()

在学习scrollTo()、scrollBy()之前,先来了解一下getScrollX()、getScrollY()方法。

getScrollX()、getScrollY()得到的是偏移量,是相对自己初始位置的滑动偏移距离,只有当有scroll事件发生时,这两个方法才能有值,否则getScrollX()、getScrollY()都是初始时的值0,而不管你这个滑动控件在哪里。所谓自己初始位置是指,控件在刚开始显示时、没有滑动前的位置。以getScrollX()为例,其源码如下:

 public final int getScrollX() {
 return mScrollX;
}

可以看到getScrollX()直接返回的就是mScrollX,代表水平方向上的偏移量,getScrollY()也类似。偏移量mScrollX的正、负代表着,滑动控件中的内容相对于初始位置在水平方向上偏移情况,mScrollX为正代表着当前内容相对于初始位置向左偏移了mScrollX的距离,mScrollX为负表示当前内容相对于初始位置向右偏移了mScrollX的距离。

这里的坐标系和我们平常的认知正好相反。为了以后更方便的处理滑动相关坐标和偏移,在处理偏移、滑动相关的功能时,我们就可以把坐标反过来看,如下图:

因为滑动控件中的内容是整体进行滑动的,同时也是相对于自己显示时的初始位置的偏移,对于View中内容在偏移时的参考坐标原点(注意是内容视图的坐标原点,不是图中说的滑动控件的原点),可以选择初始位置的某一个地方,因为滑动时整体行为,在进行滑动的时候从这个选择的原点出进行分析即可。

2、scrollTo()、scrollBy()

scrollTo(int x,int y)移动的是View中的内容,而滑动控件中的内容都是整体移动的,scrollTo(int x,int y)中的参数表示View中的内容要相对于内容初始位置移动x和y的距离,即将内容移动到距离内容初始位置x和y的位置。正如前面所说,在处理偏移、滑动问题时坐标系和平常认知的坐标系是相反的。以一个例子说明scrollTo():

说明:图中黄色矩形区域表示的是一个可滑动的View控件,绿色虚线矩形为滑动控件中的滑动内容。注意这里的坐标是相反的。(例子来源于:http://blog.csdn.net/bigconvience/article/details/26697645)

(1)调用scrollTo(100,0)表示将View中的内容移动到距离内容初始显示位置的x=100,y=0的地方,效果如下图:

(2)调用scrollTo(0,100)效果如下图:

(3)调用scrollTo(100,100)效果如下图:

(4)调用scrollTo(-100,0)效果如下图:

通过上面几个图,可以清楚看到scrollTo的作用和滑动坐标系的关系。在实际使用中,我们一般是在onTouchEvent()方法中处理滑动事件,在MotionEvent.ACTION_MOVE时调用scrollTo(int x,int y)进行滑动,在调用scrollTo(int x,int y)前,我们先要计算出两个参数值,即水平和垂直方向需要滑动的距离,如下:

@Override 
public boolean onTouchEvent(MotionEvent event) { 
 int y = (int) event.getY(); 
 int action = event.getAction(); 
 switch (action){ 
 case MotionEvent.ACTION_DOWN: 
 mLastY = y; 
 break; 
 case MotionEvent.ACTION_MOVE: 
 int dy = mLastY - y;//本次手势滑动了多大距离 
 int oldScrollY = getScrollY();//先计算之前已经偏移了多少距离 
 int scrollY = oldScrollY + dy;//本次需要偏移的距离=之前已经偏移的距离+本次手势滑动了多大距离 
 if(scrollY < 0){ 
 scrollY = 0; 
 } 
 if(scrollY > getHeight() - mScreenHeight){ 
 scrollY = getHeight() - mScreenHeight; 
 } 
 scrollTo(getScrollX(),scrollY); 
 mLastY = y; 
 break; 
 } 
 return true; 
} 

上面在计算参数时,分为了三步。第一是,通过int dy = mLastY - y;得到本次手势在屏幕上滑动了多少距离,这里要特别注意这个相减顺序,因为这里的坐标与平常是相反的,因此,手势滑动距离是按下时的坐标mLastY - 当前的坐标y;第二是,通过oldScrollY = getScrollY();获得滑动内容之前已经距初始位置便宜了多少;第三是,计算本次需要偏移的参数int scrollY = oldScrollY + dy; 后面通过两个if条件进行了边界处理,然后调用scrollTo进行滑动。调用完scrollTo后,新的偏移量又重新产生了。从scrollTo源码中可以看到:

public void scrollTo(int x, int y) { 
 if (mScrollX != x || mScrollY != y) { 
 int oldX = mScrollX; 
 int oldY = mScrollY; 
 mScrollX = x;//赋值新的x偏移量 
 mScrollY = y;//赋值新的y偏移量 
 invalidateParentCaches(); 
 onScrollChanged(mScrollX, mScrollY, oldX, oldY); 
 if (!awakenScrollBars()) { 
 postInvalidateOnAnimation(); 
 } 
 } 
 } 

scrollTo是相对于初始位置来进行移动的,而scrollBy(int x ,int y)则是相对于上一次移动的距离来进行本次移动。scrollBy其实还是依赖于scrollTo的,如下源码:

public void scrollBy(int x, int y) { 
 scrollTo(mScrollX + x, mScrollY + y); 
 } 

可以看到,使用scrollBy其实就是省略了我们在计算scrollTo参数时的第三步而已,因为scrollBy内部已经自己帮我加上了第三步的计算。因此scrollBy的作用就是相当于在上一次的偏移情况下进行本次的偏移。

一个完整的水平方向滑动的例子:

public class MyViewPager extends ViewGroup { 
 private int mLastX; 
 public MyViewPager(Context context) { 
 super(context); 
 init(context); 
 } 
 public MyViewPager(Context context, AttributeSet attrs) { 
 super(context, attrs); 
 init(context); 
 } 
 public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) { 
 super(context, attrs, defStyleAttr); 
 init(context); 
 } 
 private void init(Context context) { 
 } 
 @Override 
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
 int count = getChildCount(); 
 for(int i = 0; i < count; i++){ 
 View child = getChildAt(i); 
 child.measure(widthMeasureSpec,heightMeasureSpec); 
 } 
 } 
 @Override 
 protected void onLayout(boolean changed, int l, int t, int r, int b) { 
 int count = getChildCount(); 
 Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b); 
 for(int i = 0; i < count; i++){ 
 View child = getChildAt(i); 
 child.layout(i * getWidth(), t, (i+1) * getWidth(), b); 
 } 
 } 
 @Override 
 public boolean onTouchEvent(MotionEvent ev) { 
 int x = (int) ev.getX(); 
 switch (ev.getAction()){ 
 case MotionEvent.ACTION_DOWN: 
 mLastX = x; 
 break; 
 case MotionEvent.ACTION_MOVE: 
 int dx = mLastX - x; 
 int oldScrollX = getScrollX();//原来的偏移量 
 int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量 
 if(preScrollX > (getChildCount() - 1) * getWidth()){ 
  preScrollX = (getChildCount() - 1) * getWidth(); 
 } 
 if(preScrollX < 0){ 
  preScrollX = 0; 
 } 
 scrollTo(preScrollX,getScrollY()); 
 mLastX = x; 
 break; 
 } 
 return true; 
 } 
} 

布局文件:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:orientation="vertical"> 

 <com.scu.lly.viewtest.view.MyViewPager 
android:layout_width="match_parent" 
android:layout_height="300dp" 
> 
 <ImageView 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:scaleType="fitXY" 
android:src="@drawable/test1" /> 

 <ImageView 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:scaleType="fitXY" 
android:src="@drawable/test2" /> 

 <ImageView 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:scaleType="fitXY" 
android:src="@drawable/test3" /> 

 <ImageView 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:scaleType="fitXY" 
android:src="@drawable/test4" /> 
 </com.scu.lly.viewtest.view.MyViewPager> 
</LinearLayout> 

效果如图:

二、Scroller滑动辅助类

根据我们上面的分析,可知View的scrollTo()、scrollBy()是瞬间完成的,当我们的手指在屏幕上移动时,内容会跟着手指滑动,但是当我们手指一抬起时,滑动就会停止,如果我们想要有一种惯性的滚动过程效果和回弹效果,此时就需要使用Scroller辅助类。

但是注意的是,Scroller本身不会去移动View,它只是一个移动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹记录工具,最终还是通过View的scrollTo、scrollBy方法完成View的移动的。

在使用Scroller类之前,先了解其重要的两个方法:

(1)startScroll()

public void startScroll(int startX, int startY, int dx, int dy, int duration)

开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处。

(2)computeScrollOffset()

public boolean computeScrollOffset()

滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中。

上面两个方法的源码如下:

public class Scroller {
private int mStartX;//水平方向,滑动时的起点偏移坐标
private int mStartY;//垂直方向,滑动时的起点偏移坐标
private int mFinalX;//滑动完成后的偏移坐标,水平方向
private int mFinalY;//滑动完成后的偏移坐标,垂直方向
private int mCurrX;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,水平方向
private int mCurrY;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,垂直方向
private int mDuration; //本次滑动的动画时间
private float mDeltaX;//滑动过程中,在达到mFinalX前还需要滑动的距离,水平方向
private float mDeltaY;//滑动过程中,在达到mFinalX前还需要滑动的距离,垂直方向
public void startScroll(int startX, int startY, int dx, int dy) {
 startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}
/**
 * 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处
*/
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
 mMode = SCROLL_MODE;
 mFinished = false;
 mDuration = duration;
 mStartTime = AnimationUtils.currentAnimationTimeMillis();
 mStartX = startX;
 mStartY = startY;
 mFinalX = startX + dx;//确定本次滑动完成后的偏移坐标
 mFinalY = startY + dy;
 mDeltaX = dx;
 mDeltaY = dy;
 mDurationReciprocal = 1.0f / (float) mDuration;
}
/**
 * 滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中
 * @return
*/
public boolean computeScrollOffset() {
 if (mFinished) {//已经完成了本次动画控制,直接返回为false
 return false;
 }
 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
 if (timePassed < mDuration) {
 switch (mMode) {
 case SCROLL_MODE:
 final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
 mCurrX = mStartX + Math.round(x * mDeltaX);//计算出当前的滑动偏移位置,x轴
 mCurrY = mStartY + Math.round(x * mDeltaY);//计算出当前的滑动偏移位置,y轴
 break;
 ...
 }
 }else {
 mCurrX = mFinalX;
 mCurrY = mFinalY;
 mFinished = true;
 }
 return true;
 }
 ...
}

Scroller类中最重要的两个方法就是startScroll()和computeScrollOffset(),但是Scroller类只是一个滑动计算辅助类,它的startScroll()和computeScrollOffset()方法中也只是对一些轨迹参数进行设置和计算,真正需要进行滑动还是得通过View的scrollTo()、scrollBy()方法。为此,View中提供了computeScroll()方法来控制这个滑动流程。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 {@link android.widget.Scroller Scroller} 
 * object. 
 * 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制 
 */ 
public void computeScroll() { //空方法 ,自定义滑动功能的ViewGroup必须实现方法体 
 
} 

因此Scroller类的基本使用流程可以总结如下:

(1)首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算;

(2)在调用startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;

(3)在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘。

如下的一个简单代码示例:  

@Override 
 public boolean onTouchEvent(MotionEvent ev) { 
 initVelocityTrackerIfNotExists(); 
 mVelocityTracker.addMovement(ev); 
 int x = (int) ev.getX(); 
 switch (ev.getAction()){ 
 case MotionEvent.ACTION_DOWN: 
 if(!mScroller.isFinished()){ 
  mScroller.abortAnimation(); 
 } 
 mLastX = x; 
 break; 
 case MotionEvent.ACTION_MOVE: 
 int dx = mLastX - x; 
 int oldScrollX = getScrollX();//原来的偏移量 
 int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量 
 if(preScrollX > (getChildCount() - 1) * getWidth()){ 
  preScrollX = (getChildCount() - 1) * getWidth(); 
 } 
 if(preScrollX < 0){ 
  preScrollX = 0; 
 } 
 //开始滑动动画 
 mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0);//第一步 
 //注意,一定要进行invalidate刷新界面,触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,只是一个辅助类,并不会触发界面的绘制 
 invalidate(); 
 mLastX = x; 
 break; 
 } 
 return true; 
 } 
 @Override 
 public void computeScroll() { 
 super.computeScroll(); 
 if(mScroller.computeScrollOffset()){//第二步 
 scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//第三步 
 invalidate(); 
 } 
 } 

下面是一个完整的例子:一个类似ViewPager的Demo,效果图如下:

代码如下:

public class MyViewPager3 extends ViewGroup { 

 private int mLastX; 

 private Scroller mScroller; 
 private VelocityTracker mVelocityTracker; 
 private int mTouchSlop; 
 private int mMaxVelocity; 
 /** 
 * 当前显示的是第几个屏幕 
 */ 
 private int mCurrentPage = 0; 

 public MyViewPager3(Context context) { 
 super(context); 
 init(context); 
 } 

 public MyViewPager3(Context context, AttributeSet attrs) { 
 super(context, attrs); 
 init(context); 
 } 

 public MyViewPager3(Context context, AttributeSet attrs, int defStyleAttr) { 
 super(context, attrs, defStyleAttr); 
 init(context); 
 } 

 private void init(Context context) { 
 mScroller = new Scroller(context); 
 ViewConfiguration config = ViewConfiguration.get(context); 
 mTouchSlop = config.getScaledPagingTouchSlop(); 
 mMaxVelocity = config.getScaledMinimumFlingVelocity(); 
 } 

 @Override 
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
 int count = getChildCount(); 
 for(int i = 0; i < count; i++){ 
 View child = getChildAt(i); 
 child.measure(widthMeasureSpec, heightMeasureSpec); 
 } 
 } 

 @Override 
 protected void onLayout(boolean changed, int l, int t, int r, int b) { 
 int count = getChildCount(); 
 Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b); 
 for(int i = 0; i < count; i++){ 
 View child = getChildAt(i); 
 child.layout(i * getWidth(), t, (i + 1) * getWidth(), b); 
 } 
 } 

 @Override 
 public boolean onTouchEvent(MotionEvent ev) { 
 initVelocityTrackerIfNotExists(); 
 mVelocityTracker.addMovement(ev); 
 int x = (int) ev.getX(); 
 switch (ev.getAction()){ 
 case MotionEvent.ACTION_DOWN: 
 if(!mScroller.isFinished()){ 
  mScroller.abortAnimation(); 
 } 
 mLastX = x; 
 break; 
 case MotionEvent.ACTION_MOVE: 
 int dx = mLastX - x; 
 /* 注释的里面是使用startScroll()来进行滑动的 
 int oldScrollX = getScrollX();//原来的偏移量 
 int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量 
 if (preScrollX > (getChildCount() - 1) * getWidth()) { 
  preScrollX = (getChildCount() - 1) * getWidth(); 
  dx = preScrollX - oldScrollX; 
 } 
 if (preScrollX < 0) { 
  preScrollX = 0; 
  dx = preScrollX - oldScrollX; 
 } 
 mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, 0); 
 //注意,使用startScroll后面一定要进行invalidate刷新界面,触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,只是一个辅助类,并不会触发界面的绘制 
 invalidate(); 
 */ 
 //但是一般在ACTION_MOVE中我们直接使用scrollTo或者scrollBy更加方便 
 scrollBy(dx,0); 
 mLastX = x; 
 break; 
 case MotionEvent.ACTION_UP: 
 final VelocityTracker velocityTracker = mVelocityTracker; 
 velocityTracker.computeCurrentVelocity(1000); 
 int initVelocity = (int) velocityTracker.getXVelocity(); 
 if(initVelocity > mMaxVelocity && mCurrentPage > 0){//如果是快速的向右滑,则需要显示上一个屏幕 
  Log.d("TAG","----------------快速的向右滑--------------------"); 
  scrollToPage(mCurrentPage - 1); 
 }else if(initVelocity < -mMaxVelocity && mCurrentPage < (getChildCount() - 1)){//如果是快速向左滑动,则需要显示下一个屏幕 
  Log.d("TAG","----------------快速的向左滑--------------------"); 
  scrollToPage(mCurrentPage + 1); 
 }else{//不是快速滑动的情况,此时需要计算是滑动到 
  Log.d("TAG","----------------慢慢的滑动--------------------"); 
  slowScrollToPage(); 
 } 
 recycleVelocityTracker(); 
 break; 
 } 
 return true; 
 } 

 /** 
 * 缓慢滑动抬起手指的情形,需要判断是停留在本Page还是往前、往后滑动 
 */ 
 private void slowScrollToPage() { 
 //当前的偏移位置 
 int scrollX = getScrollX(); 
 int scrollY = getScrollY(); 
 //判断是停留在本Page还是往前一个page滑动或者是往后一个page滑动 
 int whichPage = (getScrollX() + getWidth() / 2 ) / getWidth() ; 
 scrollToPage(whichPage); 
 } 

 /** 
 * 滑动到指定屏幕 
 * @param indexPage 
 */ 
 private void scrollToPage(int indexPage) { 
 mCurrentPage = indexPage; 
 if(mCurrentPage > getChildCount() - 1){ 
 mCurrentPage = getChildCount() - 1; 
 } 
 //计算滑动到指定Page还需要滑动的距离 
 int dx = mCurrentPage * getWidth() - getScrollX(); 
 mScroller.startScroll(getScrollX(),0,dx,0,Math.abs(dx) * 2);//动画时间设置为Math.abs(dx) * 2 ms 
 //记住,使用Scroller类需要手动invalidate 
 invalidate(); 
 } 

 @Override 
 public void computeScroll() { 
 Log.d("TAG", "---------computeScrollcomputeScrollcomputeScroll--------------"); 
 super.computeScroll(); 
 if(mScroller.computeScrollOffset()){ 
 scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); 
 invalidate(); 
 } 
 } 

 private void recycleVelocityTracker() { 
 if (mVelocityTracker != null) { 
 mVelocityTracker.recycle(); 
 mVelocityTracker = null; 
 } 
 } 

 private void initVelocityTrackerIfNotExists() { 
 if(mVelocityTracker == null){ 
 mVelocityTracker = VelocityTracker.obtain(); 
 } 
 } 
} 

布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">

 <com.lusheep.viewtest.view.MyViewPager3
 android:layout_width="match_parent"
 android:layout_height="200dp"
 android:background="#999" >
 <ImageView
 android:layout_width="300dp"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/test1" />

 <ImageView
 android:layout_width="300dp"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/test2" />

 <ImageView
 android:layout_width="300dp"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/test3" />

 <ImageView
 android:layout_width="300dp"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/test4" />
 </com.lusheep.viewtest.view.MyViewPager3>
</LinearLayout>

点此下载

简单总结:

(1)Scroller类能够帮助我们实现高级的滑动功能,如手指抬起后的惯性滑动功能。使用流程为,首先通过Scroller类的startScroll()+invalidate()触发View的computeScroll(),在computeScroll()中让Scroller类去计算最新的坐标信息,拿到最新的坐标偏移信息后还是要调用View的scrollTo来实现滑动。可以看到,使用Scroller的整个流程比较简单,关键的是控制滑动的一些逻辑计算,比如上面例子中的计算什么时候该往哪一页滑动...

(2)Android后面推出了OverScroller类,OverScroller在整体功能上和Scroller类似,使用也相同。OverScroller类可以完全代替Scroller,相比Scroller,OverScroller主要是增加了对滑动到边界的一些控制,如增加一些回弹效果等,功能更加强大。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!


# android  # scroller  # Android使用Scroller实现弹性滑动效果  # Android自定义View弹性滑动Scroller详解  # Android用Scroller实现一个可向上滑动的底部导航栏  # 详解Android应用开发中Scroller类的屏幕滑动功能运用  # android使用 ScrollerView 实现 可上下滚动的分类栏实例  # 深入理解Android中Scroller的滚动原理  # Android程序开发之UIScrollerView里有两个tableView  # Android Scroller完全解析  # Android Scroller及下拉刷新组件原理解析  # android开发通过Scroller实现过渡滑动效果操作示例  # 的是  # 偏移量  # 相对于  # 计算出  # 过程中  # 可以看到  # 如下图  # 还需要  # 都是  # 只是一个  # 是一个  # 几个  # 第三步  # 时间内  # 多大  # 当我们  # 完成后  # 停留在  # 图中  # 屏幕上 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel如何创建自定义Facades?(详细步骤)  谷歌浏览器如何更改浏览器主题 Google Chrome主题设置教程  php做exe能调用系统命令吗_执行cmd指令实现方式【详解】  如何在建站之星网店版论坛获取技术支持?  Laravel如何设置定时任务(Cron Job)_Laravel调度器与任务计划配置  Laravel如何实现图片防盗链功能_Laravel中间件验证Referer来源请求【方案】  新三国志曹操传主线渭水交兵攻略  JS去除重复并统计数量的实现方法  Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制  网易LOFTER官网链接 老福特网页版登录地址  Laravel如何使用查询构建器?(Query Builder高级用法)  DeepSeek是免费使用的吗 DeepSeek收费模式与Pro版本功能详解  Laravel广播系统如何实现实时通信_Laravel Reverb与WebSockets实战教程  lovemo网页版地址 lovemo官网手机登录  详解Nginx + Tomcat 反向代理 负载均衡 集群 部署指南  Laravel Livewire是什么_使用Laravel Livewire构建动态前端界面  JavaScript实现Fly Bird小游戏  Laravel Blade模板引擎语法_Laravel Blade布局继承用法  如何在阿里云虚拟主机上快速搭建个人网站?  Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道  Laravel事件监听器怎么写_Laravel Event和Listener使用教程  Laravel请求验证怎么写_Laravel Validator自定义表单验证规则教程  php后缀怎么变mp4格式错误_修改扩展名提示格式不对怎么办【技巧】  Android滚轮选择时间控件使用详解  javascript基本数据类型及类型检测常用方法小结  HTML5段落标签p和br怎么选_文本排版常用标签对比【解答】  Laravel怎么设置路由分组Prefix_Laravel多级路由嵌套与命名空间隔离【步骤】  JavaScript如何实现继承_有哪些常用方法  Windows Hello人脸识别突然无法使用  Laravel怎么配置自定义表前缀_Laravel数据库迁移与Eloquent表名映射【步骤】  Laravel如何实现RSS订阅源功能_Laravel动态生成网站XML格式订阅内容【教程】  Laravel如何创建自定义Artisan命令?(代码示例)  laravel怎么使用数据库工厂(Factory)生成带有关联模型的数据_laravel Factory生成关联数据方法  Android自定义listview布局实现上拉加载下拉刷新功能  Laravel如何安装Breeze扩展包_Laravel用户注册登录功能快速实现【流程】  JS中对数组元素进行增删改移的方法总结  使用PHP下载CSS文件中的所有图片【几行代码即可实现】  在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?  Laravel如何实现用户密码重置功能?(完整流程代码)  香港服务器网站生成指南:免费资源整合与高速稳定配置方案  ai格式如何转html_将AI设计稿转换为HTML页面流程【页面】  Laravel怎么调用外部API_Laravel Http Client客户端使用  微信小程序 wx.uploadFile无法上传解决办法  Laravel如何使用Laravel Vite编译前端_Laravel10以上版本前端静态资源管理【教程】  如何确保FTP站点访问权限与数据传输安全?  Laravel怎么在Controller之外的地方验证数据  如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环  Laravel怎么实现API接口鉴权_Laravel Sanctum令牌生成与请求验证【教程】  微信h5制作网站有哪些,免费微信H5页面制作工具?  jQuery中的100个技巧汇总