实现滑动的7种方法

来源:互联网 发布:手机新浪微博盗号软件 编辑:程序博客网 时间:2024/06/05 20:13

          当了解了Android坐标系和触控事件后,我们再来看看如何使用系统提供的API来实现动态地去修改一个View的坐标,即实现滑动效果。而不管采用哪一种方式,其实现的思想基本是一致的,当触摸View时,系统记下当前的触摸点坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标,这样不断重复,从而实现滑动过程。

         下面我们就通过一个实例,来看看在Android中该如何实现滑动效果。定义一个View,并置于一个LinearLayout中,实现一个简单的布局,代码如下所示。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="test.chenj.study_5_2.MainActivity">    <test.chenj.study_5_2.MoveViewLayout        android:layout_width="100dp"        android:layout_height="100dp"        android:background="#000"        /></LinearLayout>

          我们的目的就是让这个自定义的View随着手指在屏幕上的滑动而滑动。

1.layout方法

         我们知道,在View进行绘制时,会调用onLayout()方法来设置显示的位置。同样,可以通过修改View的left,top,right,bottom四个属性来控制View的坐标。与前面提供的模板代码一样,在每次回调onTouchEvent的时候,我们都来获取一下触摸点的坐标,代码如下所示。

        int x = (int)event.getX();        int y = (int)event.getY();

         接着,在ACTION_DOWN事件中记录触摸点的坐标,代码如下所示。

 

            case MotionEvent.ACTION_DOWN:                //记录触摸点                mLastX = x;                mLastY = y;                break;

         最后,可以在ACTION_MOVE事件中计算偏移量,并将偏移量作用到Layout方法中,在目前Layout的left,top,right,bottom基础上,增加计算出来的偏移量,代码如下所示.

            case MotionEvent.ACTION_MOVE:                //计算偏移量                int offsetX = x - mLastX;                int offsetY = y - mLastY;                //使用layout方法滑动控件--layout()方法相对于父布局的left,top,right,bottom                layout(getLeft()+offsetX, getTop()+offsetY, getRight()+offsetX,getBottom()+offsetY);                mLastX = x;                mLastY = y;                break;

        这样每次移动后,View都会调用Layout方法来对自己重新布局,从而达到移动View的效果。

         在上面的代码中,使用的是getX()、getY()方法来获取坐标值,即通过视图来获取偏移量。当然,同样可以使用getRawX()、getRawY()来获取坐标,并使用绝对坐标来计算偏移量,代码如下所示。

    @Override    public boolean onTouchEvent(MotionEvent event) {        int rawX = (int)event.getRawX();        int rawY = (int)event.getRawY();        switch (event.getAction()){            case MotionEvent.ACTION_DOWN:                //记录触摸点                mLastRawX = rawX;                mLastRawY = rawY;                break;            case MotionEvent.ACTION_MOVE:                //计算偏移量                int offsetX = rawX - mLastRawX;                int offsetY = rawY - mLastRawY;                //使用layout方法滑动控件--layout()方法相对于父布局的left,top,right,bottom                layout(getLeft()+offsetX, getTop()+offsetY, getRight()+offsetX,getBottom()+offsetY);                mLastRawX = rawX;                mLastRawY = rawY;                break;        }        return true;    }

         使用绝对坐标系,有一点非常需要注意的地方,就是在每次执行完ACTION_MOVE的逻辑后,一定要重新设置初始坐标值,这样才能准确获取偏移量。

2.offsetLeftAndRight()与offsetTopAndBottom()

         这个方法相当于系统提供一个对左右、上下移动的API的封装。当计算出偏移量后,只需要使用如下代码就可以完成View的重新布局,效果与使用Layout方法一个样,代码如下所示。

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

3.LayoutParams

         LayoutParams保存了一个View的布局参数。因此可以在程序中通过修改LayoutParams来动态地修改一个布局的位置参数,从而达到改变View位置的效果。我们可以很方便地在程序中使用getLayoutParams()来获取一个View地LayoutParams。当然,计算偏移量的方法与在Layout方法中计算的offset也是一样的。每当获取到偏移之后,就可以通过setLayoutParams来改变其LayoutParams,代码如下所示。

        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)getLayoutParams();        layoutParams.leftMargin = getLeft() + offsetX;        layoutParams.rightMargin = getRight() + offsetX;        layoutParams.topMargin = getTop() + offsetY;        layoutParams.bottomMargin = getBottom() + offsetY;        setLayoutParams(layoutParams);

         不过这里需要注意的是,通过getLayoutParams()获取LayoutParams时,需要根据View所在父布局的类型来设置不同的类型,比如这里将View放在LinearLayout中,那么就可以使用LinearLayout.LayoutParams。类似的,如果在RelativeLayout中,就要使用RelativeLayout.LayoutParams。当然,这一切的前提是你必须要有一个父布局,不然系统无法获取LayoutParams。

        在通过改变LayoutParams改变一个View的位置时,通常改变的时这个View的Margin属性,所以除了使用布局的LayoutParams之外,还可以使用ViewGroup.MarginLayoutParams来实现这样一个功能,代码如下所示。

        ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams)getLayoutParams();        marginLayoutParams.leftMargin = getLeft() + offsetX;        marginLayoutParams.topMargin = getTop() + offsetY;        setLayoutParams(marginLayoutParams);

         我们可以发现,使用ViewGroup.MarginLayoutParams更加的方便,不需要考虑父布局的类型,当然他们的本质都是一样的。

4.scrollTo与scrollBy

         在一个View中,系统提供了scrollTo、scrollBy两种方式来改变一个View的位置。这两个方法的区别非常的好理解,与英文的to与by的区别类似,scrollTo(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(dx,dy)表示移动的增量为dx,dy。

        与前面的几种方式相同,在获取偏移量后使用scrollBy来移动View,代码如下失所。

scrollBy(offsetX, offsetY);

         但是当我们拖动View的时候,你会发现View并没有移动!难道是我们的方法写错了吗?其实,方法没有写错,View也的确移动了,只是它移动的并不是我们想要移动的东西。scrollTo、scrollBy方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollTo、scrollBy方法,那么移动的是所有的子View,但如果在View中使用,那么移动的将是View的内容,例如TextView,content就是它的文本;ImageView,content就是它的drawable对象。

         相信通过上面的分析,应该知道为什么不能在View 中使用这两个方法来拖动这个View了。那么我们就该View所在的ViewGroup中来使用scrollBy方法,移动它的子View,
代码如下所示。

        ((View)getParent()).scrollBy(offsetX, offsetY);

         但是,当再次拖动View的时候,你就会发现View虽然移动了,但却在乱动,并不是我们想要的跟随触摸点的移动而移动。这里需要先了解下视图移动的一些只是。大家在理解这个问题的时候,不妨这样想象,手机屏幕是一个中空的盖板,盖板下面是一个巨大的画布,也就是我们想要显示的视图。当把这个盖板盖在画布上的某一处时,透过中间空的矩形,我们看见了手机屏幕上显示的视图,而画布上其他地方的视图,则被盖板盖住了无法看见。我们的视图与这个例子非常类似,我们没有看见视图并不代表它就不存在,有可能只是屏幕外而已。当调用scrollBy方法时,可以想象为外面的盖板在移动,这么说比较抽象,来看一个具体的例子。如下图所示。



         在途中,中间的矩形相当于屏幕,即可视区域。后面的content就相当于画布,代表视图。可以看到,只有视图的中间部分目前时可视的,其他的部分都不可见。在可见区域中,我们设置了一个Button,它的坐标时(20,10).

         下面使用scrollBy方法,将盖板(屏幕,可视区域),在水平方向上向X轴正方向(右方)平移20,在竖直方向上向Y轴正方向(下方)平移10,那么平移之后的可视区域图上右图所示。

        我们可以发现,索然设置scrollBy(20,10)偏移量均为X轴、Y轴正方向上的正数,但是在屏幕的可视区域内,Button却向X轴、Y轴负方向上移动了。这就是因为参考系选择的不同而产生的不同的效果。

         通过上面的分析可以发现,如果将scrollBy中的参数dx和dy设置为正数,那么content将向坐标的负方向移动;如果将dx和dy设置为负数,那么content将向坐标轴正方向移动。因此回到前面的例子,要实现跟随手指移动而滑动的效果,就必须将偏移量改为负值,代码如下所示。

    ((View)getParent()).scrollBy(-offsetX, -offsetY);
         再去实验一下,大家就可以发现,效果与前面几种方式的效果相同了。类似地,在使用绝对坐标时,也可以通过scrollTo方法来实现这一效果。

5.Scroller

         既然提到了scrollTo、scrollBy方法,就不得不说下Scroller类。Scroller类与scrollTo、scrollBy方法十分相似,有着千丝万缕的联系。那么他们之间的具体右什么区别呢?要解答这个问题,首先来看一个小例子。假如要完成这一一个效果:通过点击按钮,让一个ViewGroup的子View向右移动100个像素。问题看似非常简单,只要在按钮的点击事件中使用最前面讲的scrollBy方法设置下偏移量不久可以了吗?的确,通过这一一个方法可以让ViewGroup中的子View平移。但是我们可以发现,不管使用scrollTo还是scrollBy方法,子View的平移都是瞬间方生的,在事件执行的时候平移就已经完成了,这样的效果会让人觉得非常突兀。Google建议使用自然的过度动画来实现移动效果,当然也要遵循这已原则。因此,Scroller类就这一应运而生了,通过Scroller类可以实现平滑的移动效果,而不再是瞬间完成的移动。

         说到Scroller类的实现原理,其实它与前面使用的scrollTo和scrollBy方法来实现子View跟随手指移动的原里基本类似。虽然scrollBy方法时让子View瞬间从某点移动到另一个点,但是由于在ACTION_MOVE事件中不断获取手指移动的微笑偏移量,这一就i将一段距离划分成了N个非常小的偏移量。虽然在每个偏移量里面,通过scrollBy方法进行了瞬间移动,但是在整体上却可以获得一个平滑移动的效果。这个原理与动画的实现原理也是基本类似,他们都是利用了人眼的视觉暂留特性。

         下面我们就在本节的例子中,演示下一下如何使用Scroller类实现平滑移动。在这个实例中,同样让子View跟随这手指滑动而滑动,但是手指离开屏幕时,让子View平滑的移动到初始位置,即屏幕左上角。一般清空下,使用Scroller类需要如下三个步骤。

  • 初始化Scroller

         首先,通过它的构造放来来创建一个Scroller对象,代码如下所示。

//初始化ScrollermScroller = new Scroller(context);
  • 重写computeScroll(),实现模拟滑动

         下面我们需要重写computeScroll()方法,它是使用Scroller类的核心,系统会在绘制View的时候会在draw()方法中调用该方法。这个方法实际上就是使用的scrollTo方法。这个方法实际上就是使用scrollTo方法。在结合Scroller对象,帮助回去到当前的滚动值。我们可以通过不断地瞬间移动一个小的距离来实现整体上的平滑移动效果。通常清空下,computeScroll的代码可以利用如下模板代码来实现。

        

    @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.computeScroll()方法会返回false,从而终端循环,完成整个平滑移动过程。

  • startScroll开启模拟过程

         最后,万事俱备只欠东风。我们需要在使用平滑移动的事件中,使用Scroller类的startScroll()方法来开始平滑移动过程。startScroll()方法具有两个重载方法。

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

         可以看到它们的区别就是一个具有指定的持续时长,而另一个没有。这个非常好理解,与动画中设置的duration和使用默认的显示时长是一个道理。而其他四个坐标,则与他们命名含义相同,就是其实坐标与偏移量。在获取坐标时,通过可以使用getScrollX()个getScrollY()方法来获取父视图中content所滑动到的点的坐标,不过要注意的时这个值的正负,它与咋爱scrollBy、scrollTo中讲解的情况时一样的。

         通过上面的三个步骤,我们就可以使用Scroller类来实现平滑移动了,下面回到实例中,在构造方法中初始化Scroller对象,并重写View的computeScroll()方法。最后,需要监听手指离开屏幕的事件,并在该事件中通过调用startScroll()方法完成平移滑动。那么要监听手指离开屏幕的事件,需要在onTouchEvent中增加一个ACTION_UP监听选项即可。

            case MotionEvent.ACTION_UP:                View viewGroup = (View)getParent();                mScroller.startScroll(viewGroup.getScrollX(),viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY());                invalidate();                break;

         在startScroll()方法中,我们获取子View移动的距离----getScrollX()、getScrollY(),并将偏移量设置为其反数,从而子View滑动到原位置。这里需要注意的还是invalidate()方法,需要使用这个方法来通知View进行重绘,从而来调用computeScroll()的模拟过程。当然,也可以给startScroll()方法增加一个duration的参数来设置滑动的持续时长。

6.属性动画

         在第7章中,我们将详细讲解了如何使用属性动画来控制一个View的移动效果,在这里,同样可以使用属性动画来完成View的滑动特效,这与属性动画中讲解的方法基本一直,这里就不重复了。

7.ViewDragHelper

         Google在其support库中为我们提供了DrawerLayout和SlidingPaneLayout两个布局来帮助开发这实现侧边栏滑动的效果。这两个新的布局,大大方便了我们创建自己的滑动布局界面。然而,这两个功能强大的布局背后,却隐藏着一个鲜为人知却功能强大的类------ViewDragHelper。通过ViewDragHelper,基本可以实现各种不同的滑动、拖放需求,因此这个方法也是各种滑动解决方案中的终极绝招。

         ViewDragHelper虽然功能强大,但其使用方法也是本章中最复杂的。读者朋友需要在理解ViewDragHelper基本使用方法的基础上,通过不断练习来掌握它的使用技巧。下面通过一个实例,来演示一下如何使用ViewDragHelper创建一个滑动布局。在这个例子中,准备实现类似QQ滑动侧边栏的布局,初始时显示内容界面,当用户手指滑动超过一段距离时,内容界面侧滑显示菜单界面。
         下面来看具体的代码时如何实现的。

  • 初始化ViewDragHelper

         首先,自然时需要初始化ViewDragHelper。ViewDragHelper通常定义在一个ViewGroup的内部,并通过其静态工厂方法初始化,代码如下所示。

        mViewDragHelper = ViewDragHelper.create(this, callback);

         它的第一参数时要监听的View,通常需要一个ViewGroup,即parentView;第二个参数时一个Callback回调,这个回调就是整个ViewDragHelper的逻辑核心,后面再来详细讲解。

  • 事件拦截

        接下来,要重写事件拦截方法,将事件传递给ViewDragHelper进行处理,代码如下所示。

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

        这一点我们在讲解Android事件机制的时候已经进行了详细的讲解。

  • 处理computeScroll()

         没错,使用ViewDragHelper同样需要重写下computeScroll()方法,因为ViewDragHelper内部也是通过Scroller来实现平滑移动的。通常情况下,可以使用如下的模板代码。

    @Override    public void computeScroll() {        if(viewDragHelper.continueSettling(true)){            ViewCompat.postInvalidateOnAnimation(this);        }    }
  • 处理回调CallBack

         下面就是最关键的CallBack实现,通过如下代码来创建一个ViewDragHelper.Callback.

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {        @Override        public boolean tryCaptureView(View child, int pointerId) {            return false;        }}

         tryCaptureView()这个方法,我们可以指定在创建ViewDragHelper时,参数parentView中的哪一个子View可以被移动,例如,在这个实例中自定义了一个ViewGroup,里面定义了两个子View------MenuView和MainView,当指定如下代码时,只有MenuView可以被拖动。

        @Override        public boolean tryCaptureView(View child, int pointerId) {            //当前触摸child时mainView时,开始检测            return menuView == child;        }

         下面来看具体的滑动方法------clampViewPositionHorizontal()和clampViewPositionVertical(),分别对应水平和垂直方向上的滑动。如果要实现滑动效果,那么这两个方法时必须要重写的。因为它默认返回值时0,即不生滑动。当然,如果只重写clampViewPositionHorizontal()和clampViewPositionVertical()中的一个,那么就会只会实现该方向上的滑动效果了,代码如下所示。

@Override        public int clampViewPositionHorizontal(View child, int left, int dx) {            return left;        }        @Override        public int clampViewPositionVertical(View child, int top, int dy) {            return 0;        }

         clampViewPositionVertical(View child, int top, int dy)中的参数top,代表垂直方向上child移动的距离,而dy则表示比较前一次的增量。同理,clampViewPositionHorizontal(View child, int left, int dx)也是类似的含义。通常情况下,只需要返回top和left即可,但当需要更加精确地计算padding等属性的时候,需要对left进行一些处理,并返回合适大小的值。

         仅仅时通过重写上面的三个方法,就可以实现一个基本的滑动效果了。当手动拖动MainView的时候,他就可以跟着手指滑动而滑动了,代码如下所示。

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {        @Override        public boolean tryCaptureView(View child, int pointerId) {            //当前触摸child时mainView时,开始检测            return menuView == child;        }        @Override        public int clampViewPositionHorizontal(View child, int left, int dx) {            return left;        }        @Override        public int clampViewPositionVertical(View child, int top, int dy) {            return 0;        }    };

         下面来继续优化这个实例。在讲解Scroller类时,曾实现一个这一的效果,当手指离开屏幕后,子View滑动回初始位置。当时我们时通过监听ACTION_UP事件,并通过调用Scroller类来实现的,这里使用ViewDragHelper类实现这样效果。在ViewDragHelper.Callback中,系统提供了这样的方法-----onViewReleased(),通过重写这个方法,可以非常简单地实现当手指离开屏幕后实现的操作。当然这个方法内部时通过Scroller类来实现的,也就是前面重写的computeScroll()方法的原因,这部分代码如下所示。

        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            if(menuView.getLeft() < 0) {                //关闭菜单,相当于调用Scroller的startScroll方法                viewDragHelper.smoothSlideViewTo(menuView,-width,0);                ViewCompat.postInvalidateOnAnimation(DragLayout.this);            } else {                //打开菜单                viewDragHelper.smoothSlideViewTo(menuView,0,0);                ViewCompat.postInvalidateOnAnimation(DragLayout.this);            }        }

         设置让MenuView移动到左边距小于0像素的时候,就使用smoothSlideViewTo()方法来将MenuView移动到-width位置,也就是将MenuView隐藏。否则将MenuView移动到0,0的位置,也就是将其显示。

         通过前面的一步步分析,现在要实现类似QQ侧滑栏的效果,是不是就非常简单了呢。 下面自定义了一个ViewGroup来完成整个实例的编写,滑动的处理部分前面已经讲解过了,在自定义ViewGroup的onFinishInflate()方法中,按顺序将子View分别定义成MenuView和MainView,并在onSizeChanged()方法中获得View的宽度。如果你需要根据View的宽度来处理滑动后的效果,就可以使用这个值来进行判断。这部分代码如下所示。

    @Override    protected void onFinishInflate() {        super.onFinishInflate();        int count = getChildCount();        if(count >= 2){            //简单写了  直接写死            menuView = getChildAt(1);            mainView = getChildAt(0);        }    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        //获取菜单控件宽度        width = menuView.getMeasuredWidth();    }

         最后,整个通过ViewDragHelper实现QQ侧滑功能的代码如下所示。

package test.chenj.study_5_2;import android.content.Context;import android.support.annotation.Nullable;import android.support.v4.view.ViewCompat;import android.support.v4.widget.ViewDragHelper;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.widget.FrameLayout;import static android.support.v4.widget.ViewDragHelper.EDGE_LEFT;/** * Created by 72312 on 2017/12/25. */public class DragLayout extends FrameLayout {    private ViewDragHelper viewDragHelper;    private View mainView;    private View menuView;    private int width;    public DragLayout(Context context) {        this(context, null);    }    public DragLayout(Context context, @Nullable AttributeSet attrs) {        this(context, attrs,0);    }    public DragLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        viewDragHelper = ViewDragHelper.create(this, callback);        viewDragHelper.setEdgeTrackingEnabled(EDGE_LEFT);//如果不设置此参数,边界拖动时,会没有反应    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);        //用来隐藏菜单显示项        if (menuView != null){            menuView.layout(-menuView.getMeasuredWidth(),t,0,menuView.getMeasuredHeight());        }        //将主屏幕显示        if (mainView != null){            mainView.layout(0,0,mainView.getMeasuredWidth(),mainView.getMeasuredHeight());        }    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        //拦截事件        return viewDragHelper.shouldInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        //将触摸事件传递给ViewDragHelper,此操作必不可少        viewDragHelper.processTouchEvent(event);        return true;    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        int count = getChildCount();        if(count >= 2){            //简单写了  直接写死            menuView = getChildAt(1);            mainView = getChildAt(0);        }    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        //获取菜单控件宽度        width = menuView.getMeasuredWidth();    }    @Override    public void computeScroll() {        if(viewDragHelper.continueSettling(true)){            ViewCompat.postInvalidateOnAnimation(this);        }    }    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {        /**         * 该方法有必要实现,因为该方法在Callback内部默认返回0,也就是说,如果的view的click事件为true,         * 那么会出现整个子View没法拖拽移动的情况了。那么这边直接返回left view宽度了,表示水平方向滑动的最远距离了。         * @param child         * @return         */        @Override        public int getViewHorizontalDragRange(View child) {            return width;        }        @Override        public void onEdgeDragStarted(int edgeFlags, int pointerId) {            Log.d("drag","onEdgeDragStarted");            viewDragHelper.captureChildView(menuView, pointerId);        }        @Override        public boolean tryCaptureView(View child, int pointerId) {            //当前触摸child时mainView时,开始检测            return menuView == child;        }        @Override        public int clampViewPositionHorizontal(View child, int left, int dx) {            //如果不重写此方法,边界拖动时,会没有反应            int newLeft = Math.max(-child.getWidth(), Math.min(left, 0));            //始终都是取left的值,初始值为-child.getWidth(),当向右拖动的时候left值增大,当left大于0的时候取0            //保证屏幕左侧没有边界            return newLeft;        }        @Override        public int clampViewPositionVertical(View child, int top, int dy) {            return 0;        }        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            if(menuView.getLeft() < 0) {                //关闭菜单,相当于调用Scroller的startScroll方法                viewDragHelper.smoothSlideViewTo(menuView,-width,0);                ViewCompat.postInvalidateOnAnimation(DragLayout.this);            } else {                //打开菜单                viewDragHelper.smoothSlideViewTo(menuView,0,0);                ViewCompat.postInvalidateOnAnimation(DragLayout.this);            }        }    };}

        以下是xml布局文件的代码。

<?xml version="1.0" encoding="utf-8"?><test.chenj.study_5_2.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <FrameLayout        android:id="@+id/main"        android:layout_width="match_parent"        android:layout_height="match_parent">        <TextView            android:text="main"            android:textSize="20sp"            android:layout_width="match_parent"            android:gravity="center"            android:layout_height="match_parent" />    </FrameLayout>    <FrameLayout        android:id="@+id/menu"        android:background="@color/colorPrimaryDark"        android:layout_width="wrap_content"        android:layout_height="match_parent">        <TextView            android:padding="40dp"            android:text="menu"            android:textColor="#fff"            android:textSize="20sp"            android:layout_width="wrap_content"            android:gravity="center"            android:layout_height="match_parent" />    </FrameLayout></test.chenj.study_5_2.DragLayout>


最后附上app运行的图片。


        

         当然,这里只是非常简单的模拟了QQ侧滑的功能。ViewDragHelper的很多强大的功能还没有能够得到展示。在ViewDragHelper.Callback中,系统定义了大量的监听事件来帮助我们处理各种事件,下面就列举些事件。

  • onViewCaptured()

         这个事件在用户触摸到View后回调。

  • onViewDragStateChanged()

         这个事件在拖拽状态改变时回调,比如idle,dragging等状态。

  • onViewPositionChanged()

        这个事件在位置改变时回调,常用于滑动时更改scale进行缩放等效果。
        使用ViewDragHelper可以帮助我们非常号的处理程序中的滑动效果。但同时ViewDragHelper的使用也比较复杂,需要开发者对事件拦截、滑动处理都有比较清楚的认识。所以建议初学者循序渐进,在掌握前面几种解决方法的基础上,再来学习ViewDragHelper,已实现更加丰富的滑动效果。




原创粉丝点击