Scroll滑动分析-《Android群英传》第五章

来源:互联网 发布:人际网络网上商城 编辑:程序博客网 时间:2024/05/17 09:16

本文选自《android群英传》第五章。主要是滑动方面知识,讲解了坐标系、MotionEvent剄七种滑动的实现方法。

    • 1-滑动的产生
      • 1-Android坐标系
      • 2-视图坐标系
      • 3-MotionEvent
        • View提供的获取坐标的方法
        • MotionEvent提供的方法
    • 2-实现滑动
      • 1-Layout实现滑动
      • 2-offsetLeftAndRight和offsetTopAndBottom
      • 3-LayoutParams
      • 4-scrollToscrollBy
      • 5-Scroller
      • 6-属性动画
      • 7-ViewDragHelper
        • QQ侧滑菜单

1-滑动的产生

滑动一个View本质就是移动View,通过改变View的坐标去实现这个效果。这里就需要监听用户触摸的事件。下面包含两个部分:Android窗口坐标系和屏幕触控事件MotionEvent

1-Android坐标系

以屏幕左上角为原点,向右为X正方向,向下为Y轴正方向。
* Android中通过getLocationOnScreen(intlocation[])能获得当前视图的左上角在Andriod坐标系中的坐标。
* 触控事件中,getRawX()和getRawY(),获得的同样是android坐标系中的坐标

2-视图坐标系

是当前视图以父视图左上角为原点建立的坐标系,用于描述当前视图在父视图中的坐标。
* 触控事件中通过getX()、getY()获得在视图坐标系中的坐标。

3-MotionEvent

一共有几种常用事件常量:ACTION_DOWN单点按下\UP单点离开\MOVE单点移动\CANCEL动作取消\OUTSIDE动作超出边界\POINTER_DOWN多点触摸按下\POINTER_UP多点触摸离开

一般情况通过onTouchEvent中的event.getAction()来获取事件类型的常量。

系统有很多获取坐标值和相对距离的方法。主要分为两类。

View提供的获取坐标的方法

getTop:View的顶部到父控件顶边的距离。
getLeft/Right/Bottom:对应View的左边/右边/底部分别到父控件左边/右边/底部的距离。

MotionEvent提供的方法

getX()获得点击事件event距离控件左边的距离。视图坐标。
getY()获得点击事件event距离控件顶部的距离。视图坐标。
getRawX()/RawY()获得点击事件距离整个屏幕左边/顶部的距离。绝对坐标。

2-实现滑动

1-Layout实现滑动

View控件有layout决定View的位置。在View控件的onTouchEvent()方法中去获得控件滑动前后的偏移。通过layout方法去重新设置。
自定义View

public class ScrollByLayoutView extends AppCompatImageView{    float downX;    float downY;  /**   * 三个构造函数千万不能少   */    public ScrollByLayoutView(Context context) {        super(context);    }    public ScrollByLayoutView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public ScrollByLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }   /**   * 进行偏移计算,之后调用layout   */    public boolean onTouchEvent(MotionEvent event) {        float curX = event.getX(); //手指实时位置的X        float curY = event.getY(); //Y        switch(event.getAction()){            case MotionEvent.ACTION_DOWN:                downX = curX; //按下时的坐标                downY = curY;                Log.i("ScrollByLayoutView", "onTouchEvent: ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                int offsetX = (int)(curX - downX); //X偏移                int offsetY = (int)(curY - downY); //Y偏移                //用getLeft得到当前控件距离父控件左边的距离+偏移量,得到变化后的距离,然后调用layout                layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);                Log.i("ScrollByLayoutView", "onTouchEvent: ACTION_MOVE");                break;        }        return true;    }}

使用控件:

<com.example.xxx.ScrollByLayoutView        android:id="@+id/scroll_way1_imageview"        android:layout_width="100dp"        android:layout_height="100dp"        android:src="@drawable/jide"/>

这样就实现了滑动,对于offset偏移值的计算,也可以使用event的getRawX/Y来计算。
* 特别注意:使用绝对坐标需要在每次调用layout之后重新设置初始值(downX = curX)

2-offsetLeftAndRight和offsetTopAndBottom

直接替换layout

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

3-LayoutParams

利用布局的参数来移动View控件。

//方法三:通过布局设置在父控件的位置。但是必须要有父控件, 而且要指定父布局的类型,不好的方法。RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();layoutParams.leftMargin = getLeft() + offsetX;layoutParams.topMargin = getTop() + offsetY;setLayoutParams(layoutParams);
//方法四:用ViewGroup的MarginLayoutParams的方法去设置marign// 相比于上面方法, 就不需要知道父布局的类型。// 缺点:滑动到右侧控件会缩小ViewGroup.MarginLayoutParams mlayoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();mlayoutParams.leftMargin = getLeft() + offsetX;mlayoutParams.topMargin = getTop() + offsetY;setLayoutParams(mlayoutParams);

4-scrollTo\scrollBy

scrollTo去移动到制定坐标
scrollBy表示移动的增量
* 这两个方法是移动View的内容,因此需要在View的父控件中调用。

//方法五:scrollTo/scrollBy,在父控件中调用来操作父控件内部的控件                ((View)getParent()).scrollBy(offsetX, offsetY);

然而结果是错误的,因为滑动的参考物并不是之前的。
这里的移动类似于移动了玻璃,所以View控件移向了完全相反的地方。需要取反。

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

5-Scroller

和scrollTo/By比较类似,但是去别在于scrollTo/By的位移是瞬间完成的。而Scroller却是平滑移动的。减少了突兀感。

这里实现一个效果,就是滑动后,控件自动返回最初始位置,这里在ACTION_UP中实现。
三个步骤:
1.初始化scroller
自定义View构造中初始化。

Scroller mScroller;public ScrollByLayoutView(Context context) {       super(context);       mScroller = new Scroller(context);}

2.重载自定义View的computeScroll

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

判断是否执行完毕

3.ACTION_UP中将View滑动回初始位置

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

6-属性动画

参考动画章节

7-ViewDragHelper

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

QQ侧滑菜单

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();    }    /**-------------------------------------------     * 1、初始化数据:调用ViewDragHelper.create方法     * ------------------------------------------*/    private void initView() {        mViewDragHelper = ViewDragHelper.create(this,callback); //需要监听的View和回调callback    }    /**-------------------------------     * 2、事件拦截和触摸事件全部交给ViewDragHelper进行处理     * ------------------------------*/    //事件拦截    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return mViewDragHelper.shouldInterceptTouchEvent(ev);    }    //触摸事件    @Override    public boolean onTouchEvent(MotionEvent event) {        //将触摸事件传递给ViewDragHelper        mViewDragHelper.processTouchEvent(event);        return true;    }    /**--------------------------------------------     * 3、也需要重写computeScroll()     *    内部也是通过scroller来进行平移滑动, 这个模板可以照搬     * -------------------------------------------*/    @Override    public void computeScroll() {        if(mViewDragHelper.continueSettling(true)){            ViewCompat.postInvalidateOnAnimation(this);        }    }    /**------------------------------     * 4、处理的回调:侧滑回调     * ----------------------------*/    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {        /*-------------------------------        * 何时开始触摸:        *  1.指定哪一个子View可以被移动.        *  2.如果直接返回true,在该布局之内的所有子View都可以随意划动        * ------------------------------*/        @Override        public boolean tryCaptureView(View child, int pointerId) {            //如果当前触摸的child是mMainView开始检测            return mMainView == child;        }        /*-------------------------------        * 处理水平滑动:        *  1. 返回值默认为0,如果为0则不处理该方向的滑动。        *  2. 一般直接返回left,当需要精准计算pading等值时,可以先对left处理再返回        * ------------------------------*/        @Override        public int clampViewPositionHorizontal(View child, int left, int dx) {            return left;        }        /*-------------------------------        * 处理垂直滑动:        *  1. 返回值默认为0,如果为0则不处理该方向的滑动。        *  2. 一般直接返回top,,当需要精准计算pading等值时,可以先对left处理再返回        * ------------------------------*/        @Override        public int clampViewPositionVertical(View child, int top, int dy) {            return 0;        }        /*---------------------------------------------        *  拖动结束后调用,类似ACTION_UP。        *   这里是实现侧滑菜单,一般滑动可以不用这段代码        * ---------------------------------------------*/        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            super.onViewReleased(releasedChild, xvel, yvel);            //手指抬起后缓慢的移动到指定位置            if(mMainView.getLeft() <500){                //关闭菜单                mViewDragHelper.smoothSlideViewTo(mMainView,0,0);                ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);            }else{                //打开菜单                mViewDragHelper.smoothSlideViewTo(mMainView,300,0);                ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);            }        }    };    /**---------------------------------------------------     * 5、获取子控件用于处理     *  1. 上面完成了滑动功能,这里简单的按照第1、2的顺序指定子控件View的内容     *  2. onSizeChanged能够获得menu等子控件的宽度等信息,有需求可以后续处理     * ----------------------------------------------*/    //XML加载组建后回调    @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();    }}

使用(作为父控件,里面依次放menu和main):

    <com.example.xxxx.DragViewGroup        android:layout_width="match_parent"        android:layout_height="match_parent">        <TextView            android:layout_width="match_parent"            android:layout_height="match_parent"            android:background="@color/colorAccent"/>        <TextView            android:layout_width="match_parent"            android:layout_height="match_parent"            android:background="@color/colorPrimary"/>    </com.example.xxxx.DragViewGroup>
原创粉丝点击