使用Scroller实现View的平滑滚动

  在《实现View的移动的方法总结》一文中,介绍了实现View的移动的几种方法:setLayoutParams(),scrollTo()和scrollBy(),layout(),offsetLeftAndRight()和offsetTopAndBottom(),位移动画和属性动画。以上这几种方法中,只有位移动画和属性动画才是平滑滚动的,其他的几种方法都是瞬间让View移动到目标位置,给人的视觉体验并不太好,Android在一些ViewGroup中使用Scroller类来解决这个问题,比如ViewPager和ScrollView就是使用Scroller来让子View实现平滑滚动的。

  Scroller虽然能让View产生平滑滚动的效果,但其实Scroller本身并不会直接让View发生滚动,它是被动作用于View之上的。下面通过例子来看看Scroller的使用方法:

MyButton.java

import android.content.Context;
import android.util.AttributeSet;
import android.widget.Button;
import android.widget.Scroller;

public class MyButton extends Button {

	private Scroller mScroller;

	public MyButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		// 实例化Scroller
		mScroller = new Scroller(context);
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		width = w;
		height = h;
	}

	public void startScroll(int startX, int startY, int dx, int dy, int duration) {
		// 对Scroller进行初始设置
		mScroller.startScroll(startX, startY, dx, dy, duration);
		invalidate();
	}

	private int currX, currY;
	private int width, height;

	@Override
	public void computeScroll() {
		// 如果滑动仍在进行
		if (mScroller.computeScrollOffset()) {
			currX = mScroller.getCurrX();
			currY = mScroller.getCurrY();
			layout(currX, currY, currX + width, currY + height);
			invalidate();
		}
	}

}

FirstActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

public class FirstActivity extends Activity implements OnClickListener {

	private MyButton button;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_first);
		button = (MyButton) findViewById(R.id.button1);
		button.setOnClickListener(this);
	}

	@Override
	public void onClick(View v) {
		button.startScroll(0, 0, 200, 0, 1500);
	}

}

activity_first.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.hellomagic.learnscroller.MyButton
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>

运行之后的效果是这样的:

一般来说,Scroller的使用分为以下几个步骤:

  1. 创建Scroller的实例
  2. 让需要滚动的View或ViewGroup重写computeScroll()方法
  3. 在重写的computeScroll()方法里调用scroller.computeScrollOffset()方法判断滑动是否正在进行
  4. 在if(scroller.computeScrollOffset())的代码块里实现View的滑动,并调用invalidate()方法通知View重绘
  5. 需要开始滑动时调用scroller.startScroll()方法进行滑动的参数设置,并调用View的invalidate()方法开始滑动

为什么调用Scroller的startScroll()方法进行设置之后,调用View的invalidate()方法就能开始滑动?为什么滑动会自己进行?

从上图可以看到,View调用invalidate()之后,如果scroller.computeScrollOffset()返回true也就是滑动正在进行的时候,就会形成一个循环,view会不断重绘,不断layout。而每次调用scroller.computeScrollOffset()的时候,scroller内部都会改变currX和currY的值。

  /**
     * Call this when you want to know the new location.  If it returns true,
     * the animation is not yet finished.
     */
    public boolean computeScrollOffset() {
        if (mFinished) {   // 如果滑动结束了,就返回false
            return false;
        }
     // 用 现在的时间 - 滑动开始时的时间 = 滑动进行的时间(即滑动进行了多久)
        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

        if (timePassed < mDuration) { // 如果 滑动进行的时间 < 滑动持续的时间
            switch (mMode) {
            case SCROLL_MODE:         // 根据滑动进行的时间 timePassed 和 插值器 mInteerpolator 计算得到当前的currX和currY
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                float distanceCoef = 1.f;
                float velocityCoef = 0.f;
                if (index < NB_SAMPLES) {
                    final float t_inf = (float) index / NB_SAMPLES;
                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
                    final float d_inf = SPLINE_POSITION[index];
                    final float d_sup = SPLINE_POSITION[index + 1];
                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                }

                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;

                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);

                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);

                if (mCurrX == mFinalX && mCurrY == mFinalY) {
                    mFinished = true;
                }

                break;
            }
        }
        else { // 如果滑动进行的时间 >= 滑动持续的时间
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

由此可见,scroller.getCurrX()和scroller.getCurrY()的值会随着滑动进行的时间而改变,因此每次调用view.layout(currX, currY, currX + width, currY + height方法时),每次view的位置都会发生一点变化,就因为在duration时间内,view每次layout都会移动一点位置,所以就产生了View滑动的动画效果。

时间: 2024-10-05 06:16:55

使用Scroller实现View的平滑滚动的相关文章

android开发教程之使用线程实现视图平滑滚动示例

最近一直想做下拉刷新的效果,琢磨了好久,才走到通过onTouch方法把整个视图往下拉的步骤,接下来就是能拉下来,松开手要能滑回去啊.网上看了好久,没有找到详细的下拉刷新的例子,只有自己慢慢琢磨了.昨天和今天,研究了两天,下拉之后回滚回去的效果终于今天做出来了!开心.现在来分享下我的实现方法和一些心得体会吧.我看了网上一个大神的例子,发现是在onTouch里面使用View的scrollTo(int, int)方法,来使整个视图往下滚动的,我尝试了使用setTranslationY()来对视图进行回

Android游戏开发之主角的移动与地图的平滑滚动

人物移动地图的平滑滚动处理 玩过rpg游戏的朋友应该都知道RPG的游戏地图一般都比较大 今天我和大家分享一下在RPG游戏中如何来处理超出手机屏幕大小的游戏地图. 如图所示为程序效果动画图 地图滚动的原理 在本人之前博客的文章中介绍过人物在屏幕中的移动方式,因为之前拼的游戏地图是完全填充整个手机屏幕的,所以无需处理地图的平滑滚动.这篇文章我着重的向 大家介绍一下控制人物移动后地图滚动的处理方式.举个例子 如上图所示 比如人物向右移动,如果地图贴在屏幕左边边界 将先移动人物在地图的坐标,当人物在屏幕

Android学习Scroller(一)——View调用scrollTo()的理解及使用

MainActivity如下: package cc.uu; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.app.Activity; /** * Demo描述: * scrollTo()和scrollB

jquery 平滑滚动页面到某个锚点

1 $(document).ready(function() { 2         $("a.topLink").click(function() { 3                 $("html, body").animate({ 4                         scrollTop: $($(this).attr("href")).offset().top + "px" 5            

平滑滚动

//平滑滚动 12.5.24未完 <html>    <style>    *{        padding:0px;        margin:0px;    }        html,body,div{            height:100%;        }    </style>    <body>        <div style='background-color:red'></div>        &l

在WPF中实现平滑滚动

WPF实现滚动条还是比较方便的,只要在控件外围加上ScrollViewer即可,但美中不足的是:滚动的时候没有动画效果.在滚动的时候添加过渡动画能给我们的软件增色不少,例如Office 2013的滚动的时候支持动画看起来就舒服多了. 之前倒是研究过如何实现这个平滑滚动,不过网上的方案大部分大多数如下: 通过VisualTree找到ScrollViewer 在ScrollChanged事件中添加动画 这种方案效果并不好,以为我们的滚动很多时候都是一口气滚动好几格滚轮的,这个时候上一个动画还没有结束

JQuery 实现锚点链接之间的平滑滚动

web开发前端一直用JQuery ,真正接触了才体会到,JQuery 原来比我想象的要强大的多,也可能比我体会到的还要强大的多,特别是兼容性那个好,于是把一些好玩的,酷炫的,能够取代 JS 的.统统给用上了. 从 JQuery 引入今天的正题.用 JQuery 实现锚点链接之间的平滑滚动.曾经介绍过一个用 JS 实现的页面锚点跳转缓冲特效,效果相当不错.能够在同一页面的锚点链接之间实现平滑的滚动,可是 JS 代码相对来说比較冗长.如今好了.仅仅要已经载入了 JQuery.我们就能够用较为简短的代

页面中的平滑滚动——smooth-scroll.js的使用

正常的本页面锚链接跳转的时候跟PPT似的,特别生硬,用户体验非常差. 这时候我们就可以借助smooth-scroll.js这个插件,来实现本页面的平滑的跳转. 1首先,导入必须的JS文件 <script src="js/jquery-1.10.2.js"></script> <script src="js/jquery.smooth-scroll.min.js"></script> <script src=&qu

jQuery写出可调控自定义的平滑滚动效果(锚点跳转动画)

今天朋友,没错,是上次的好基友,在用jQuery.scrollTo.js这个插件的时候总是没反应,而且在函数里console可以输出数据,这点现在仍让我很困惑,难道是scroll版本和我引用的jQUery版本不兼容?我在自己本地搭建了一个小demo也没反应,于是就借助动画写了一个可以自定义滑动的距离和速度调控的demo.供大家参考,欢迎交流更好的办法. 1 <!doctype html> 2 <html lang="en"> 3 <head> 4 &