Scroller的使用


这是一个滑动帮助类。并不能够使View真正的滑动,而是依据时间的流逝。获取插值器中的数据。传递给我们。让我们去配合scrollTo/scrollBy去让view产生缓慢滑动,产生动画的效果。事实上是和属性动画同一个原理。以下是官方文档对于这个类所给的解释:

This class encapsulates scrolling. You can use scrollers (Scroller or OverScroller) to collect the data you need to produce a scrolling animation—for example, in response to a fling gesture. Scrollers track scroll offsets for you over time, but they don’t automatically apply those positions to your view. It’s your responsibility to get and apply new coordinates at a rate that will make the scrolling animation look smooth.

首先。我们要先获得这个对象,我们一起看看它的构造方法:

public Scroller (Context context)

public Scroller (Context context, Interpolator interpolator)

public Scroller (Context context, Interpolator interpolator, boolean flywheel)

一共同拥有三个构造方法。我们通经常使用第二个比較多,给定Scroller一个插值器,让其从这样的插值器中取值。

那么,怎样使用Scroller呢,仅仅需调用以下的代码就可以:

Scroller.(int startX, int startY, int dx, int dy, int duration)。
invalidate();

就能够为Scroller指定从起始位置和结束位置以及滑动的时间。Scroller就能够帮助我们获取某一时刻,我们的view所在的位置了。

接着我们须要在view的computeScroll()的方法中推断scroller是否结束,假设没有结束就用scrollTo方法使view处于正确的位置就可以。

    @Override
public void computeScroll() {
//推断是否还在滚动,还在滚动为true
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//更新界面
postInvalidate();
isMove = true;
}
super.computeScroll();
}

怎么样,用起来是不是非常easy粗暴。

Scroller的源代码分析


仅仅是会使用可不行。我们不仅要知其然还要知其所以然。接下来,我们从源代码的角度来分析下Scroller的工作原理。从哪里分析呢。事实上也不用从Scroller类的第一行代码開始看。捡重要的看就可以了。

首先看看startScroll()这种方法吧:

    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;
}

我们能够从中看到,Scroller仅仅是给他的成员变量一一赋值而已,比方模式啊,位置信息,时间等。并没有做关于view滑动的不论什么工作。我们接下来调用了View的invalidate()方法,让View树又一次绘制,让它绘制什么呢?什么东西都没有,这不是坑我们么?事实上。View的draw方法中都会调用我们上面说的computeScroll() 方法,可是这种方法在View中却是一个空方法。接下来。我们就继续分析Scroller的computeScrollOffset()方法:

    public boolean computeScrollOffset() {
if (mFinished) {
return false;
} int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = timePassed * mDurationReciprocal; if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x); 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先推断滑动有没有结束,假设没有结束就去获取View此时应该所处的位置信息。这种方法中重要的是它的返回值。假设完毕了返回false,没有完毕才返回true。继续接着Scroller的使用段落往下看。假设mScroller.computeScrollOffset()的返回值是true的话,也就是Scroller还没结束,我们就让我们的View滑动到这里,并刷新。值得注意的是,View中的computeScroll()方法并非执行在主线程中的,所以我们要使postInvalidate()方法来调用重绘。

接着重绘又会调用View的draw方法,draw方法又会调用computeScroll()方法,直至Scroller结束。到这里我们依据平时使用的代码的走向,了解了Scroller的大致工作流程。

怎么样。是不是对Scroller的理解更深刻一些了呢?

Scroller的实例


仅仅是理解还不够啊,我们得从实际开发中使用Scroller。才干真正的会用他。接下来,我会用两个实例,来升华你对Scroller的理解。

实例一:仿微信朋友圈刷新

废话不多说,直接上代码吧!

/**
* 仿微信刷新
* @author Nipuream
*/
public class WXLayout extends LinearLayout{ private static final String TAG = "WXLayout";
private int mTouchSlop;
private boolean mIsBeingDragged = false;
private float mLastMotionY;
private float mInitialMotionY;
private float resistance = 0.6f;
private Scroller mScroller;
private ListView mListView;
private boolean isMove = false;
private int duration = 300;
private ScrollRershListener l;
private boolean isRersh = false; public WXLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init(context);
} private void init(final Context context){
ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
DecelerateInterpolator interpolator = new DecelerateInterpolator();
mScroller = new Scroller(context,interpolator);
post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
mListView = (ListView) WXLayout.this.getChildAt(0);
}
});
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
final int action = ev.getAction(); if(action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP){
mIsBeingDragged = false;
return false;
} if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
return true;
} switch(action){
case MotionEvent.ACTION_DOWN:{
mLastMotionY = mInitialMotionY = ev.getY();
mIsBeingDragged = false;
break;
}
case MotionEvent.ACTION_MOVE:{
final float y = ev.getY(), x = ev.getX();
final float diff, absDiff;
diff = y - mLastMotionY;
absDiff = Math.abs(diff);
if(absDiff > mTouchSlop){
if(diff > 1){
if(mListView.getFirstVisiblePosition()==0){
View view = mListView.getChildAt(0);
Rect rect = new Rect ();
view.getLocalVisibleRect(rect);
if(rect.top == 0){
mLastMotionY = y;
mIsBeingDragged = true;
}
}
}
}
break;
}
}
return mIsBeingDragged;
} @Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub //假设碰触到控件的边缘。就不接受这一系列的action了
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
return false;
} //假设Scroller正在滑动。就不接受这次事件了
if(isMove){
return false;
} switch(event.getAction()){
case MotionEvent.ACTION_DOWN:{
mLastMotionY = mInitialMotionY = event.getY();
return true;
}
case MotionEvent.ACTION_MOVE:{
if (mIsBeingDragged) {
if(l!=null && !isRersh){
l.startRersh();
isRersh = true;
}
mLastMotionY = event.getY();
float moveY = mLastMotionY - mInitialMotionY;
pullEvent(moveY);
return true;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:{
if(mIsBeingDragged){
mIsBeingDragged = false;
startMoveAnim(getScrollY(), Math.abs(getScrollY()), duration);
if(l!= null && isRersh && (event.getY() - mInitialMotionY) > 0){
l.endRersh(event.getY() - mInitialMotionY);
isRersh = false;
}
return true;
}
break;
}
}
return super.onTouchEvent(event);
} private void pullEvent(float moveY){
if(l != null){
l.Rersh(moveY);
}
if(moveY > 0){
int value = (int) Math.abs(moveY);
scrollTo(0, - (int)(value*resistance));
}
} public void startMoveAnim(int startY, int dy, int duration) {
isMove = true;
mScroller.startScroll(0, startY, 0, dy, duration);
invalidate();//通知UI线程的更新
} @Override
public void computeScroll() {
//推断是否还在滚动,还在滚动为true
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//更新界面
postInvalidate();
isMove = true;
} else {
isMove = false;
}
super.computeScroll();
} public interface ScrollRershListener{
void Rersh(float value);
void startRersh();
void endRersh(float value);
} public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
} public void setOnScrollRershListener(ScrollRershListener l){
this.l = l;
} }

实例二:仿QQ側滑删除

得多谢夏安明大神的博客,让我有了清晰的思路,对他的代码加以改造。从而完毕了这个实例。

/**
* 仿QQ側滑删除
* @author Nipuream
*
*/
public class SlideListView extends ListView implements OnTouchListener,OnClickListener{ private static final String TAG = "SlideListView";
private Context mContext;
private Scroller mScroller;
/**
* 初始值
*/
private float initalXvalue,mLastXvalue;
private float initalYvalue,mLastYvalue;
/**
* 确认滑动的最小速度
*/
private int MIN_VELOCITY = 800;
/**
* 速度跟踪器
*/
private VelocityTracker velocityTracker;
/**
* 正在被拖动的view
*/
private View dragView;
/**
* 正在被拖动的position
*/
private int touchPos;
/**
* 默认的最小滑动距离
*/
private int mTouchSlop;
/**
* 滑动时间
*/
private int DURATION_TIME = 300;
/**
* 出现删除button的Item
*/
private View tempView ;
/**
* 出现删除button的position
*/
private int tempPos ;
/**
* 删除button
*/
private Button deleteBtn;
/**
* 移除接口
*/
private RemoveItemListener l;
/**
* 能否够滑动
*/
private boolean isSlide = false; public SlideListView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init(context);
} private void init(Context context){
mContext = context;
AccelerateInterpolator interpolator = new AccelerateInterpolator();
mScroller = new Scroller(context,interpolator);
velocityTracker = VelocityTracker.obtain();
ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
setOnTouchListener(this);
} /**
* 捕捉用户究竟拖动了哪个view
* 拦截事件
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
final int action = ev.getAction(); if (action == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
return false;
} if(action == MotionEvent.ACTION_DOWN){ mLastYvalue = initalYvalue = ev.getY();
initalXvalue = mLastXvalue = ev.getX(); if(!mScroller.isFinished()){
return super.dispatchTouchEvent(ev);
} touchPos = pointToPosition((int)mLastXvalue, (int)mLastYvalue); if(touchPos == AdapterView.INVALID_POSITION){
return super.dispatchTouchEvent(ev);
} GetTracker(ev);
dragView = getChildAt(touchPos - getFirstVisiblePosition());
isSlide = false;
}else if(action == MotionEvent.ACTION_MOVE){
mLastXvalue = ev.getX();
mLastYvalue = ev.getY();
if(velocityTracker == null){
GetTracker(ev);
}
velocityTracker.computeCurrentVelocity(1000);
if(Math.abs(velocityTracker.getXVelocity()) > MIN_VELOCITY
||Math.abs(mLastXvalue - initalXvalue)> mTouchSlop
&& Math.abs(mLastYvalue - initalYvalue) < mTouchSlop){
isSlide = true;
}
}else if(action == MotionEvent.ACTION_UP){
CloseTracker();
}
return super.dispatchTouchEvent(ev);
} /**
* 消费事件
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
if(isSlide){
switch(ev.getAction()){
case MotionEvent.ACTION_MOVE:
{
mLastXvalue = ev.getX();
final float moveX = mLastXvalue - initalXvalue;
if(Math.abs(moveX) > dip2px(mContext, 20))
{
if(moveX < 0 && Math.abs(moveX)<dip2px(mContext, 100))
{
if(dragView != null){
dragView.scrollTo(Math.abs((int)moveX), 0);
}
return true;
}
}
}
break;
case MotionEvent.ACTION_UP:
{
mLastXvalue = ev.getX();
float scrollDistance = mLastXvalue - initalXvalue;
if(scrollDistance < 0){
if(Math.abs(scrollDistance) < dip2px(mContext, 50)){
//滑动回去
if(dragView != null){
mScroller.startScroll(dragView.getScrollX(), 0, -dragView.getScrollX(), 0, DURATION_TIME);
invalidate();
}
}else if(Math.abs(scrollDistance) > dip2px(mContext, 50) ){
//滑动究竟
if(dragView != null){
mScroller.startScroll(dragView.getScrollX(), 0, (dip2px(mContext, 100) - Math.abs(dragView.getScrollX())), 0,DURATION_TIME);
invalidate();
tempView = dragView;
tempPos = touchPos;
setListener();
}
}
}
}
break;
}
}
return super.onTouchEvent(ev);
} private void GetTracker(MotionEvent ev){
if(velocityTracker == null){
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(ev);
} private void CloseTracker() {
if(velocityTracker != null){
velocityTracker.recycle();
velocityTracker.clear();
velocityTracker = null;
}
} private void setListener(){
deleteBtn = (Button) tempView.findViewById(R.id.delete);
deleteBtn.setOnClickListener(this);
} @Override
public void computeScroll() {
// TODO Auto-generated method stub
if(mScroller.computeScrollOffset()){
dragView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
super.computeScroll();
} public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
} @Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(tempView != null){
dragView = tempView ;
mScroller.startScroll(dragView.getScrollX(), 0, - dragView.getScrollX(), 0,DURATION_TIME);
invalidate();
deleteBtn.setOnClickListener(null);
deleteBtn = null;
tempView = null;
return true;
}
return false;
} @Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(l != null){
l.remove(tempPos);
dragView = tempView;
dragView.scrollTo(0, 0);
tempView = null;
invalidate();
}
} public interface RemoveItemListener{
void remove(int pos);
} public void setOnRemoveItemListener(RemoveItemListener l){
this.l = l;
} }

我认为这两个样例都非常easy。所以没有什么好解释的,我在以下也会给出下载的地址。假设有什么疑惑的,或者什么地方须要改进的请联系我QQ:571829491。

源代码下载

仿微信刷新

仿QQ側滑删除

带你彻彻底底弄懂Scroller的更多相关文章

  1. Golang, 以17个简短代码片段,切底弄懂 channel 基础

    (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...

  2. [Go] 通过 17 个简短代码片段,切底弄懂 channel 基础

    关于管道 Channel Channel 用来同步并发执行的函数并提供它们某种传值交流的机制. Channel 的一些特性:通过 channel 传递的元素类型.容器(或缓冲区)和 传递的方向由“&l ...

  3. Golang, 以 9 个简短代码片段,弄懂 defer 的使用特点

    作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...

  4. 一文带你弄懂 JVM 三色标记算法!

    大家好,我是树哥. 最近和一个朋友聊天,他问了我 JVM 的三色标记算法.我脑袋一愣发现竟然完全不知道!于是我带着疑问去网上看了几天的资料,终于搞清楚啥事三色标记算法,它是用来干嘛的,以及它和 CMS ...

  5. 必须弄懂的495个C语言问题

    1.1 我如何决定使用那种整数类型? 如果需要大数 值(大于32, 767 或小于¡32, 767), 使用long 型.否则, 如果空间很重要(如有大数组或很多结构), 使用short 型.除此之外 ...

  6. 打工心态废掉了很多人,包括你吗?(你把现在这家公司的业务都弄清楚、弄懂了吗?君子报仇十年不晚!不离不弃!)good

    我只拿这点钱,凭什么去做那么多工作,我傻呀. 我为公司干活,公司付我一份报酬,等价交换而已,我不欠谁的. 我只要对得起这份薪水就行了,多一点我都不干,做了也白做. 工作嘛,又不是为自己干,说得过去就行 ...

  7. 30分钟彻底弄懂flex布局

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由elson发表于云+社区专栏 目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用flex进行布局了.什么是flex布 ...

  8. 一文弄懂神经网络中的反向传播法——BackPropagation【转】

    本文转载自:https://www.cnblogs.com/charlotte77/p/5629865.html 一文弄懂神经网络中的反向传播法——BackPropagation   最近在看深度学习 ...

  9. 弄懂flex布局

    目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用flex进行布局了.什么是flex布局以及它的好处,这里就不再赘述. 在这篇文章里,想说说flex布局的属性语法及其细节.那么网上也 ...

随机推荐

  1. matplotlib 可视化 —— 移动坐标轴(中心位置)

    通常软件绘图,包括 matlab.python 的 matplotlib,默认都是将坐标轴置于画布(figure)的最下侧(x 轴),最左侧(y 轴),也即将坐标原点置于左下角.而我们自己理解数学,以 ...

  2. jquery操作select的各种方法

    在工作中,有时候会遇到给select组件添加一些事件,前两天发表了一篇文章,<用jquery给select加选中事件>大致阐述了简单的jq操作select的方法,但是为了详细的介绍一下se ...

  3. NodeJS学习笔记 进阶 (11)Nodejs 进阶:调试日志打印:debug模块

    个人总结:读完这篇文章需要5分钟,讲解了debug模块的使用 摘选自网络 前言 在node程序开发中时,经常需要打印调试日志.用的比较多的是debug模块,比如express框架中就用到了.下文简单举 ...

  4. 说说Kindle那些事

    已经不记得是什么时候在哪里听过kindle这玩意的了,反正最开始买kindle还是大四上学期,貌似是2012-9-30,那时候是整个大学最闲的时候,不知道哪天闲的蛋疼一冲动就买了个kindle4黑色款 ...

  5. 20180929 北京大学 人工智能实践:Tensorflow笔记03

    更改的程序部分如下: 另: 难?????????????见链接: https://www.bilibili.com/video/av22530538/?p=17 + (完)

  6. 紫书 习题 10-14 UVa 10886(暴力+数据范围)

    开始的时候一看这题感觉很难,觉得肯定有什么很快的办法 不能暴力做(受了上一题10-13的影响) 然后一看那个函数感觉无从下手. 然后看了博客发现,原来这道题就是直接暴力-- 因为n的范围为10的7次方 ...

  7. MySQL auttoReconnect

    com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from ...

  8. 一、 Hbase特性 3v特性,Volume(量级) Varity(种类) Velocity(速度)

    HBase中表的特点 大: 一个表可以由百亿行,上百万列(列多时,插入变慢) 面向列:面向列(族)的存储和权限控制,列(族)独立检索 稀疏:对于为空(null) 的列,并不占用存储空间,因此表可以设计 ...

  9. 第五章,AsyncTask和ProgressBar的练习(Android)

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools=&q ...

  10. Whats the difference between git reset --mixed, --soft, and --hard?

    https://stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-ha ...