Android Scroll分析 (二) 教你使用七种方法实现滑动

实现滑动的基本思想是:当触摸View时,系统记下当前触摸点坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标,这样不断重复,从而实现滑动过程.

2.1 Layout方法

在View进行绘制时,会调用onLayout()方法来设置显示的位置

通过修改View的left,top,right,bottom四个属性来控制View的坐标,在每次回调onTouchEvent的时候,获取一下触摸点的坐标:

    // 视图坐标方式
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 记录触摸点坐标
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                // 计算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                // 在当前left、top、right、bottom的基础上加上偏移量
                layout(getLeft() + offsetX,
                        getTop() + offsetY,
                        getRight() + offsetX,
                        getBottom() + offsetY);
//                        offsetLeftAndRight(offsetX);
//                        offsetTopAndBottom(offsetY);
                break;
        }
        return true;
    }
    //使用getX(),getY()方法来获取坐标值,即通过视图坐标来获取偏移量

使用getRawX(),getRawY()来获取坐标,并使用绝对坐标来计算偏移量,要在每次执行完ACTION_MOVE的逻辑后,一定要重新设置初始坐标,这样才能准确地获取偏移量.

   // 绝对坐标方式
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) (event.getRawX());
        int rawY = (int) (event.getRawY());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 记录触摸点坐标
                lastX = rawX;
                lastY = rawY;
                break;
            case MotionEvent.ACTION_MOVE:
                // 计算偏移量
                int offsetX = rawX - lastX;
                int offsetY = rawY - lastY;
                // 在当前left、top、right、bottom的基础上加上偏移量
                layout(getLeft() + offsetX,
                        getTop() + offsetY,
                        getRight() + offsetX,
                        getBottom() + offsetY);
                // 重新设置初始坐标
                lastX = rawX;
                lastY = rawY;
                break;
        }
        return true;
    }

2.2 offsetLeftAndRight()与offsetTopAndBottom()

当计算出偏移量后,只需使用如下代码就可以完成View的重新布局:

//同时对left和right进行偏移
offsetLeftAndRight(offsetX);
//同时对top和bottom进行偏移
offsetTopAndBottom(offsetY);

2.3 LayoutParams

LayoutParams保存了一个View的布局参数,通过LayoutParams来动态改变View的位置参数,从而改变View位置效果.

使用getLayoutParams()来获取一个View的LayoutParams.

通过setLayoutParams来改变其LayoutParams.

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 记录触摸点坐标
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                // 计算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
//                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);
                break;
        }
        return true;
    }

通过getLayoutParams()获取LayoutParams时,需要根据View所在父布局的类型来设置不同的类型.

还可以使用ViewGroup.MarginLayoutParams来实现这样的功能.

2.4 scrollTo与scrollBy

在View中,提供了scrollTo,scrollBy两种方式来改变一个View的位置,两者的区别:与英文中To与By的区别类似,scroll(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(dx,dy)表示移动的增量为dx,dy.

scrollTo,scrollBy方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollTo,scrollBy方法,那么移动的将是所有子View,但如果在View中使用,那么移动的将是View的内容.

将scrollBy中的参数dx和dy设置为正数,那么content将向坐标轴负方向移动;如果将scrollBy中的参数dx和dy设置为负数,那么content将向坐标轴正方向移动.

因此,要实现跟随手指移动而滑动的效果,就必须将偏移量改为负值:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
        }
        return true;
    }

在使用绝对坐标时,也可以通过使用scroll方法来实现.

2.5 Scroller

使用Scroller对象,需要三个步骤:

1.初始化Scroller

首先,通过它的构造方法来创建一个Scroller对象:

//初始化Scroller
mScroller=new Scroller(context);

2.重写computeScroll()方法,实现模拟滑动

重写computeScroll()方法,它是Scroller类的核心,系统在绘制View的时候会在draw()方法中调用该方法:

 @Override
    public void computeScroll() {
        super.computeScroll();
        // 判断Scroller是否执行完毕
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(
                    mScroller.getCurrX(),
                    mScroller.getCurrY());
            // 通过重绘来不断调用computeScroll
            invalidate();
        }
    }

Scroller类提供了computeScrollOffset()方法来判断是否完成了整个滑动,通过getCurrX(),getCurrY()方法来获得当前的滑动坐标,注意invalidate()方法,因为只能在computeScroll()方法中获取模拟过程中的scrollX和scrollY坐标.但computeScroll()方法是不会自动调用的,只能通过invalidate()->draw()->computeScroll()来间接调用computeScroll()方法,所有需要调用invalidate()方法,实现循环获取scrollX和scrollY的目的,而当模拟过程结束后,scroller.computeScrollOffset()方法会返回false,从而中断循环,完成整个平滑移动过程.

3.startScroll开启模拟过程

在需要使用平滑移动的事件中,使用Scroller类的startScroll()方法来开启平滑移动过程.

startScroll()方法具有两个重载方法:

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

它们的区别在于:是否具有指定的持续时长.其它参数分别为起始坐标与偏移量.

在获取坐标时,使用getScrollX()和getScrollY()方法来获取父视图中content所滑动到的点的坐标.注意正负值,与scrollBy,scrollTo的情况相同.

例子:

演示一下如何使用Scroller类实现平滑移动.在这个实例中,让子View跟随手指的滑动而滑动,但是在手指离开屏幕是,让子View平滑的移动到初始位置,即屏幕左上角:

public class DragView extends View {

    private int lastX;
    private int lastY;
    private Scroller mScroller;

    public DragView(Context context) {
        super(context);
        ininView(context);
    }

    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ininView(context);
    }

    public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ininView(context);
    }

    private void ininView(Context context) {
        setBackgroundColor(Color.BLUE);
        // 初始化Scroller
        mScroller = new Scroller(context);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        // 判断Scroller是否执行完毕
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(
                    mScroller.getCurrX(),
                    mScroller.getCurrY());
            // 通过重绘来不断调用computeScroll
            invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
            case MotionEvent.ACTION_UP:
                // 手指离开时,执行滑动过程
                View viewGroup = ((View) getParent());
                mScroller.startScroll(
                        viewGroup.getScrollX(),
                        viewGroup.getScrollY(),
                        -viewGroup.getScrollX(),
                        -viewGroup.getScrollY());
                invalidate();
                break;
        }
        return true;
    }
}

在startScroll()方法中,获取子View移动的距离–getScrollX(),getScrollY(),并将偏移量设置为其相反数,从而将子View滑动到原位置.注意invalidate()方法,需要使用这个方法来通知View进行重绘,从而来调用computeScroll()的模拟过程.

2.6 ViewDragHelper

ViewDragHelper可以实现各种不同的滑动 拖放需求.

使用方法:

初始化ViewDragHelper

首先,自然是需要初始化ViewDragHelper.ViewDragHelper通常定义在一个ViewGroup的内部,并通过其静态工厂方法进行初始化.

mViewDragHelper=ViewDragHelper.create(this,callback);

第一个参数是要监听的View,通常需要是一个ViewGroup,即parentView;第二个参数是一个Callback回调,这个回调是整个ViewDragHelper核心.

拦截事件

接下来,重写事件拦截方法,将事件传递给ViewDragHelper进行处理:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将触摸事件传递给ViewDragHelper,此操作必不可少
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

处理computeScroll()

因为ViewDragHelp内部是通过Scroller来实现平滑移动的,所以需要实现处理computeScroll().

 @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

处理回调Callback

下面就是最关键的Callback实现:

private ViewDragHelper.Callback callback =
            new ViewDragHelper.Callback() {

                // 何时开始检测触摸事件
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    //如果当前触摸的child是mMainView时开始检测
                    return mMainView == child;
                }

                // 触摸到View后回调
                @Override
                public void onViewCaptured(View capturedChild,
                                           int activePointerId) {
                    super.onViewCaptured(capturedChild, activePointerId);
                }

                // 当拖拽状态改变,比如idle,dragging
                @Override
                public void onViewDragStateChanged(int state) {
                    super.onViewDragStateChanged(state);
                }

                // 当位置改变的时候调用,常用与滑动时更改scale等
                @Override
                public void onViewPositionChanged(View changedView,
                                                  int left, int top, int dx, int dy) {
                    super.onViewPositionChanged(changedView, left, top, dx, dy);
                }

                // 处理垂直滑动
                @Override
                public int clampViewPositionVertical(View child, int top, int dy) {
                    return 0;
                }

                // 处理水平滑动
                @Override
                public int clampViewPositionHorizontal(View child, int left, int dx) {
                    return left;
                }

                // 拖动结束后调用
                @Override
                public void onViewReleased(View releasedChild, float xvel, float yvel) {
                    super.onViewReleased(releasedChild, xvel, yvel);
                    //手指抬起后缓慢移动到指定位置
                    if (mMainView.getLeft() < 500) {
                        //关闭菜单
                        //相当于Scroller的startScroll方法
                        mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    } else {
                        //打开菜单
                        mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    }
                }
            };

实现侧滑菜单的完整例子:

public class DragViewGroup extends FrameLayout {

    private ViewDragHelper mViewDragHelper;
    private View mMenuView, mMainView;
    private int mWidth;

    public DragViewGroup(Context context) {
        super(context);
        initView();
    }

    public DragViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public DragViewGroup(Context context,
                         AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMenuView = getChildAt(0);
        mMainView = getChildAt(1);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = mMenuView.getMeasuredWidth();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将触摸事件传递给ViewDragHelper,此操作必不可少
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    private void initView() {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    private ViewDragHelper.Callback callback =
            new ViewDragHelper.Callback() {

                // 何时开始检测触摸事件
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    //如果当前触摸的child是mMainView时开始检测
                    return mMainView == child;
                }

                // 触摸到View后回调
                @Override
                public void onViewCaptured(View capturedChild,
                                           int activePointerId) {
                    super.onViewCaptured(capturedChild, activePointerId);
                }

                // 当拖拽状态改变,比如idle,dragging
                @Override
                public void onViewDragStateChanged(int state) {
                    super.onViewDragStateChanged(state);
                }

                // 当位置改变的时候调用,常用与滑动时更改scale等
                @Override
                public void onViewPositionChanged(View changedView,
                                                  int left, int top, int dx, int dy) {
                    super.onViewPositionChanged(changedView, left, top, dx, dy);
                }

                // 处理垂直滑动
                @Override
                public int clampViewPositionVertical(View child, int top, int dy) {
                    return 0;
                }

                // 处理水平滑动
                @Override
                public int clampViewPositionHorizontal(View child, int left, int dx) {
                    return left;
                }

                // 拖动结束后调用
                @Override
                public void onViewReleased(View releasedChild, float xvel, float yvel) {
                    super.onViewReleased(releasedChild, xvel, yvel);
                    //手指抬起后缓慢移动到指定位置
                    if (mMainView.getLeft() < 500) {
                        //关闭菜单
                        //相当于Scroller的startScroll方法
                        mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    } else {
                        //打开菜单
                        mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    }
                }
            };

    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}
时间: 2024-07-29 03:40:54

Android Scroll分析 (二) 教你使用七种方法实现滑动的相关文章

Android Binder分析二:Natvie Service的注冊

这一章我们通过MediaPlayerService的注冊来说明怎样在Native层通过binder向ServiceManager注冊一个service,以及client怎样通过binder向ServiceManager获得一个service,并调用这个Service的方法. Native Service的注冊 这里以MediaPlayerService举例来说明怎样在Native层注冊Service,首先来看main_mediaservice.cpp的main方法: int main(int a

Android之实现滑动的七种方法总结

在android开发中,滑动对一个app来说,是非常重要的,流畅的滑动操作,能够给用户带来用好的体验,那么本次就来讲讲android中实现滑动有哪些方式.其实滑动一个View,本质上是移动一个View,改变其当前所属的位置,要实现View的滑动,就必须监听用户触摸的事件,且获取事件传入的坐标值,从而动画的改变位置而实现滑动. *layout方法 *offsetLetfAndRight()与offsetTopAndBottom() *LayoutParams *scrollTo与scrollBy

Android TextView里直接显示图片的三种方法

方法一:重写TextView的onDraw方法,也挺直观就是不太好控制显示完图片后再显示字体所占空间的位置关系.一般如果字体是在图片上重叠的推荐这样写.时间关系,这个不付源码了. 方法二:利用TextView支持部分Html的特性,直接用api赋图片.代码如下: //第一种方法在TextView中显示图片 String html = "<img src='" + R.drawable.circle + "'/>"; ImageGetter imgGett

shell实例浅谈之三产生随机数七种方法

一.问题 Shell下有时需要使用随机数,在此总结产生随机数的方法.计算机产生的的只是"伪随机数",不会产生绝对的随机数(是一种理想随机数).伪随机数在大量重现时也并不一定保持唯一,但一个好的伪随机产生算法将可以产生一个非常长的不重复的序列. 二.随机数 1.生成随机数的七种方法 (1)通过内部系统变量($RANDOM) echo $RANDOM 生成0-32767之间的整数随机数,若超过5位可以加个固定10位整数,然后进行求余. 生成400000~500000的随机数: #!/bin

像素的存储方法、显式创建Mat对象的七种方法

一.像素的存储方法: 存储像素值需要指定颜色空间和数据类型.其中,颜色空间是指针对一个给定的颜色,如何组合颜色元素以对其编码. 最简单的颜色空间要属灰度级空间,只处理黑色和白色,对他们进行组合便可以产生不同程度的灰色. 对于彩色方式则有更多种类的颜色空间,但不论哪种方式都是把颜色分成三个或者四个基元素,通过组合基元素可以产 生所有的颜色.RGB颜色空间是最常见的一种颜色空间,这归功于它也是人眼内部构成颜色的方式.它的基色是红色.绿色 和蓝色,有时为了表明透明颜色也会加入第四个元素alpha. 颜

asp.net(c#)网页跳转七种方法小结

1.response.redirect  这个跳转页面的方法跳转的速度不快,因为它要走2个来回(2次 postback),但他可以跳 转到任何页面,没 有站点页面限制(即可以由雅虎跳到新浪),同时不能跳过登录保护.但速度慢是其最大缺陷!redirect跳转机制:首先是发送一个 http请求到客户端,通知需要跳转到新页面,然后客户端在发送跳转请求到服务器端.需要注意的是跳转后内部空间保存的所有数据信息将会丢失,所以需要用到session. 实例 : using System; using Syst

delphi导出数据至Excel的七种方法及比较

一;delphi 快速导出excel uses ComObj,clipbrd; function ToExcel(sfilename:string; ADOQuery:TADOQuery):boolean;const      xlNormal=-4143;var    y     :  integer;    tsList :  TStringList;    s,filename  :string;    aSheet  :Variant;    excel :OleVariant;   

Android成长之路-实现监听器的三种方法

第一种:  在Test类中  定义一个类接口OnClickListener 第二种:直接在Test类上写一个接口 其中的this相当于new OnClickListener()对象, 即class test 中的一个对象, 而如果要用这种方式的话,public void onClick 方法必须写在该test类中, 且在开头使用implements OnClickListener, 即this对象可以直接调用该方法 第三种:  匿名内部类 Android成长之路-实现监听器的三种方法

程序实现自我删除的七种方法

//第一种 利用CREATEFILE函数的FILE_FLAG_DELETE_ON_CLOSE,标志位实现 #include <windows.h> #include <tchar.h> int CommitSuicide(char *szCmdLine) { HANDLE hTemp; char szPath[MAX_PATH]; char szTemp[MAX_PATH]; static BYTE buf[1024]; STARTUPINFO si; PROCESS_INFORM