简单的ViewPager了解Scroller类
View滑动是自定义ViewGroup中十分常见的一个功能。Android提供了多种View滑动的方法。
- layout方法
 - offsetLeftAndRight()与offsetTopAndBottom方法
 - LayoutParams方法
 - scrollTo 与scrollBy方法
 - 利用Scroller类
 - 属性动画
 - ViewDragHelper
 
虽然Android提供了这个多方法,实际上他们的原理都是一样的,当触摸到View时,系统记下当前触摸点的坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标。这样不断的重复,就实现了滑动。
这篇文章,主要说下利用Scroller类来实现滑动,Scroller类比起他之前的说的那些方法,他有一个优势在于他的滑动效果是平滑的。
View中的坐标系
在Android中有两种坐标系,一种是Android坐标系,一种是视图坐标系。根据物理学知识,坐标系的选取不同,物体的移动会有不同的效果。
在Android坐标系中,坐标的原点是以屏幕的左上角为(0,0)。这个点向右为x轴正方向,这个点向下为y轴正方向。在滑动处理的时候,我们常常需要获得点的坐标,如果我们用getRawX()和getRawY()来获得该点的坐标,则这个坐标是相对于Android坐标系的坐标。
在视图坐标系中,坐标的原点是父视图的左上角为(0,0)。同样,这个点向右为x轴正方向,这个点向下为y轴正方向。我们常常用getX()和getY()来获得该点的坐标,则这个坐标就是视图坐标系的坐标,也就是说相对于父视图的相对坐标。
最后,我们总结一下这4个方法的具体含义,在后面的滑动时会经常遇到。
getX(): 获取点击事件距离控件左边的距离,即视图坐标
getY(): 获取点击事件距离控件顶边的距离,即视图坐标
getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标
getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标
Scroller类
上面提到了Scroller类实现的是平滑移动,他实现的原理也很简单,他将一个距离的滑动分成了非常多的小距离,每个小距离通过ScrollBy方法进行瞬间移动,但在整体看来却获得了一个平滑移动的效果。
在处理滑动的时候,有一些经常容易弄混的概念,在这里说明一下,分别是scrollBy(int dx ,int dy) 中的dx,dy 以及getScrollX()和getScrollY()
dx ,dy表示视图的X,Y方向上各移动dx,dy距离。
dx > 0表示视图中(View或者ViewGroup)内容从右向左滑动,反之,从左向右滑动
dy > 0表示视图中(View或者ViewGroup)内容从下向上滑动,反之,从上向下滑动
getScrollX(): 为了好说明这个东西,我们假设我们目前是要处理一个拥有三个并排的match_parent大小LinearLayout的ViewGroup,getScrollX()得到的就是手机屏幕显示区域左上角X坐标减去这个ViewGroup视图左上角X坐标。即如果是第一个LinearLayout则getScrollX是0,如果是第三个LinearLayout,则getScrollX是2个屏幕的宽度综合
getScrollY():按照上面的例子,getScrollY()得到就是手机屏幕显示区域左上角的Y坐标减去这个ViewGroup视图左上角的Y坐标,这里不出意外,都是0,因为ViewGroup的高度就是手机屏幕高度。
在借助Scroller类完成平缓移动的时候,需要我们复写如下几个方法
startScroll(int startX, int startY, int dx, int dy)//使用默认完成时间250ms,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间
computeScrollOffset()//返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。这是一个很重要的方法,通常放在View.computeScroll()中,用来判断是否滚动是否结束。
所以,使用Scroller类需要如下三个步骤:
- 初始化Scroller
 
mScroller = new Scroller(context);
- 重写computeScroll()方法,实现模拟滑动
 - 用startScroll开启模拟过程。
 
下面,我们通过一个例子,具体说一下Scroller类的使用。这个例子是实现一个简单的ViewPager。我们放置三个并排的LinearLayout,每个都是match_parent。下面的代码是编写了拥有三个不能滑动的LinearLayout。
public class MyViewPager extends ViewGroup{
    private Scroller mScroller;
    private int lastX;
    private int mStart, mEnd;
    private int mScreenWidth;
    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) {
    // 初始化Scoller
        mScroller = new Scroller(context);
        //定义三个match_parent的LinearLayout
        LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        LinearLayout l1 = new LinearLayout(context);
        l1.setLayoutParams(lp);
        l1.setBackgroundColor(context.getResources().getColor(android.R.color.holo_orange_dark));
        LinearLayout l2 = new LinearLayout(context);
        l2.setLayoutParams(lp);
        l2.setBackgroundColor(context.getResources().getColor(android.R.color.holo_blue_dark));
        LinearLayout l3 = new LinearLayout(context);
        l3.setLayoutParams(lp);
        l3.setBackgroundColor(context.getResources().getColor(android.R.color.holo_green_dark));
        addView(l1);
        addView(l2);
        addView(l3);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = 0;
        int childCount = getChildCount();
        for(int i = 0 ; i < childCount; i++) {
            View child = getChildAt(i);
            child.layout(width,0,width + child.getMeasuredWidth(),child.getMeasuredHeight());
            width += child.getMeasuredWidth();
            mScreenWidth = child.getMeasuredWidth();
        }
    }
}
关键的部分都注释了,onMeasure和onLayout的写法可以参考我之前的ViewGroup的部分。
现在,我们处理onTouchEvent让这个ViewPager可以滑动。
 @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int)event.getX();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = lastX - x;
                if (isMove(deltaX)) {
                    scrollBy(deltaX, 0);
                }
                lastX = x;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        invalidate();
        return true;
    }
private boolean isMove(int deltaX){
        int scrollX = getScrollX();
        //滑动到第一屏,不能在向右滑动了
        if (deltaX < 0) {//从左向右滑动
            if (scrollX <= 0) {
                return false;
            } else if (deltaX + scrollX < 0) {
                scrollTo(0,0);
                return false;
            }
        }
        //滑动到最后一屏,不能在向左滑动
        int leftX = (getChildCount() - 1) * getWidth();
        if (deltaX > 0) {
            if (scrollX >= leftX) {
                return false;
            } else if (scrollX + deltaX > leftX) {
                scrollTo(leftX, 0);
                return false;
            }
        }
        return true;
    }
好了,现在的ViewPager已经可以滑动了,现在,我们要实现一个弹性滑动的机制,这个就需要借助Scroller类了,所谓弹性滑动,就是当我滑动页面不到一半的时候,页面自动缓慢的滑回去,当滑动的页面超过一半的时候,页面自动缓慢的滑到下一屏。
完成Scroller类的第一步,初始化已经完成,下面,我们完成computeScroll()
看代码:
@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
        invalidate();
    }
}
这个可以看成是一个模板的代码,基本不需要改变,这里,我们直接调用的scrollTo表示移动的是这个ViewGroup中所有的子View, 然后在调用invalidate()方法,调用这个方法主要原因是想间接的调用computeScroll(),因为computeScroll()方法是不会自动调用,而是通过invalidate() -> draw()->computeScroll()
所以需要我们在这个模板代码中调用invalidate(),实现循环获取scrollX和scrollY的目的。最后通过computeScrollOffset来判断是否滑动结束。
接下来,只剩下最后一步,开启滑动,需要我们在上面的onTouchEvent基础,做出如下修改:
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int)event.getX();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //记录下起时点的位置
                mStart = getScrollX();
                //滑动结束,停止动画
                if (mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                //记录位置
                lastX = x;
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = lastX - x;
                Log.e("deltaX", deltaX + "");
                if (isMove(deltaX)) {
                    //在滑动范围内,允许滑动
                    scrollBy(deltaX, 0);
                }
                Log.e("getScrollX", getScrollX() + "");
                lastX = x;
                break;
            case MotionEvent.ACTION_UP:
                //判断滑动的距离
                int dScrollX = checkAlignment();
                //弹性滑动开启
                if(dScrollX > 0){//从左向右滑动
                    if (dScrollX < mScreenWidth / 2) {
                        Log.e("dScrollX", dScrollX + "");
                        mScroller.startScroll(getScrollX(),0,- dScrollX,0,500);
//                        scrollBy(-dScrollX,0);
                    }else {
                        mScroller.startScroll(getScrollX(),0,mScreenWidth - dScrollX,0,500);
//                        scrollBy(mScreenWidth - dScrollX,0);
                    }
                }else {//从右向左滑动
                    if (-dScrollX < mScreenWidth / 2) {
                        mScroller.startScroll(getScrollX(),0, - dScrollX,0,500);
//                        scrollBy(-dScrollX,0);
                    }else{
                        mScroller.startScroll(getScrollX(),0,-mScreenWidth - dScrollX,0,500);
//                        scrollBy(-mScreenWidth - dScrollX,0);
                    }
                }
                break;
        }
        //重绘
        invalidate();
        return true;
    }
 private int checkAlignment(){
        //判断滑动的趋势,是向左还是向右,滑动的偏移量是多少
        mEnd = getScrollX();
        boolean isUp = ((mEnd - mStart) > 0);
        int lastPrev = mEnd % mScreenWidth;
        int lastNext = mScreenWidth - lastPrev;
        if (isUp) {
            return lastPrev;
        }else{
            return -lastNext;
        }
    }
效果如下:

自此,我们实现了一个简单可以带弹性滑动的viewPager,我们距离掌控自定义ViewGroup又进了一步。
简单的ViewPager了解Scroller类的更多相关文章
- Android中滑屏实现----手把手教你如何实现触摸滑屏以及Scroller类详解
		
前言: 虽然本文标题的有点标题党的感觉,但无论如何,通过这篇文章的学习以及你自己的实践认知,写个简单的滑屏小 Demo还是just so so的. 友情提示: 在继续往下面读之前,希望您对以下知识点 ...
 - Android Scroller类的详细分析
		
尊重原创作者,转载请注明出处: http://blog.csdn.net/gemmem/article/details/7321910 Scroller这个类理解起来有一定的困难,刚开始接触Scrol ...
 - Android 仿 窗帘效果 和 登录界面拖动效果 (Scroller类的应用) 附 2个DEMO及源码
		
在android学习中,动作交互是软件中重要的一部分,其中的Scroller就是提供了拖动效果的类,在网上,比如说一些Launcher实现滑屏都可以通过这个类去实现.下面要说的就是上次Scroller ...
 - Android开发——打造简单的Viewpager指示器
		
准备工作: 1.两张不同颜色的小圆点图片,可以去阿里巴巴矢量图网站搜索 我把我使用的图片贴出来 2.一个简单的Viewpager的实现 下面是简单的Viewpager实现步骤: 1.布局文件使用Vie ...
 - Android开发——打造简单的Viewpager指示器(小圆点指示器)
		
准备工作: 1.两张不同颜色的小圆点图片,可以去阿里巴巴矢量图网站搜索 我把我使用的图片贴出来 2.一个简单的Viewpager的实现 下面是简单的Viewpager实现步骤: 1.布局文件使用Vie ...
 - Scroller类的使用总结
		
Scroll类之所以不好理解是因为没有搞清楚View的绘制流程. 1)简单来讲 viewgroup重绘时依次会调用 dispatchDraw -- drawChild --child.compute ...
 - Android中滑屏实现----触摸滑屏以及Scroller类详解 .
		
转:http://blog.csdn.net/qinjuning/article/details/7419207 知识点一: 关于scrollTo()和scrollBy()以及偏移坐标的设置/取值问 ...
 - C#反射技术的简单操作(读取和设置类的属性)
		
public class A { public int Property1 { get; set; } } static void Main(){ A aa = new A(); Type type ...
 - 简单实用的PHP验证码类
		
一个简单实用的php验证码类,分享出来 ,供大家参考. 代码如下: <?php /** @ php 验证码类 @ http://www.jbxue.com */ Class code { var ...
 
随机推荐
- 程序猿都没对象,JS竟然有对象?
			
现在做项目基本是套用框架,不论是网上的前端还是后端框架,也会寻找一些封装好的插件拿来即用,但还是希望拿来时最好自己过后再回过头了解里面的原理,学习里面优秀的东西,不论代码封装性,还是小到命名. 好吧, ...
 - 从啥也不会到可以胜任最基本的JavaWeb工作,推荐给新人的学习路线(二)
			
在上一节中,主要阐述了JavaScript方面的学习路线.先列举一下我朋友的经历,他去过培训机构,说是4个月后月薪过万,虽然他现在还未达到这个指标. 培训机构一般的套路是这样:先教JavaSE,什么都 ...
 - js callee,caller学习
			
原文地址:js callee,caller学习 /* * caller 返回一个对函数的引用,该函数调用了当前函数. * 如果函数是由顶层调用的,那么 caller包含的就是 null . * 如果在 ...
 - .NET面试题集锦②(Part 二)
			
一.前言部分 文中的问题及答案多收集整理自网络,不保证100%准确,还望斟酌采纳. 1.实现产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复. ]; ArrayList my ...
 - 【干货分享】流程DEMO-资产请购单
			
流程名: 资产请购 业务描述: 流程发起时,会检查预算,如果预算不够,流程必须经过总裁审批,如果预算够用,将发起流程,同时占用相应金额的预算,但撤销流程会释放相应金额的预算. 流程相关文件: 流程 ...
 - 解决使用IE8打开ADFS 3.0登录页面
			
系统上线前一天,发现客户竟然有XP系统和2003系统,这些系统都不能访问外网.测试时,客户端是IE8,打开我们系统ADFS的登录页面,一直在Loading,无法打开,也不报错.后来通过fiddler跟 ...
 - jquery.multiselect 多选下拉框实现
			
第一步:链接下列文件,如果没有,到此网页下载 https://github.com/ehynds/jquery-ui-multiselect-widget,此插件基于jquery ,所以jquery的 ...
 - 1199 Problem B: 大小关系
			
求有限集传递闭包的 Floyd Warshall 算法(矩阵实现) 其实就三重循环.zzuoj 1199 题 链接 http://acm.zzu.edu.cn:8000/problem.php?id= ...
 - C#执行异步操作的几种方式比较和总结
			
C#执行异步操作的几种方式比较和总结 0x00 引言 之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理.对XxxxxAsync()之类的方法也没去了解过, ...
 - 用IntelliJ IDEA创建Gradle项目简单入门
			
Gradle和Maven一样,是Java用得最多的构建工具之一,在Maven之前,解决jar包引用的问题真是令人抓狂,有了Maven后日子就好过起来了,而现在又有了Gradle,Maven有的功能它都 ...