您的位置:首页 > 移动开发 > Android开发

Android中实现滑动(拖动)的几种方法,玩转SlideMenu

2015-11-15 00:26 489 查看
潜水已久,看了CSDN上很多大牛的博客,学了不少东西,很钦佩这种无私奉献共享的精神。

自己平时有些笔记的习惯,虽然都是一些很基础的东西,最近安定了下来,整理一下复习复习,毕竟自己一个马大哈来着。。。各种健忘

滑动一个View,改变其当前所处的位置,这个在我们APP开发中实现常用的,例如侧滑菜单,购物车的拖动等等,所以学习一下是非常有必要的

CSDN客户端上也有一个侧滑的效果



在学习如何实现滑动之前,首先要对Android中窗口坐标体系有一个大概的了解

Android坐标系:

这个没什么好说的,已屏幕最左上角的顶点作为Android坐标系的原点,往右是X轴的正方向,往下是Y轴的正方向

视图坐标系:

描述了子View与父容器之前的位置关系,在这里,原点就不再是屏幕的最上角了,而是父视图左上角为原点




MotionEvent对象:封装了触控事件中一些事件常量和常用的坐标方法

事件常量:

ACTION_DOWN 单点按下动作

ACTION_UP 单点离开动作

ACTION_MOVE 触摸移动动作

ACTION_ACTION_OUTSIDE 触摸动作超出边界

ACTION_POINTER_DOWN 多点触摸按下动作

ACTION_CANCEL 触摸动作取消

ACTION_POINTER_UP 多点离开动作

此外,还提供了很多方法来获取相应的坐标值,基本上就是通过这些坐标值来实现滑动的效果

为了防止遗忘也便于以后查询,画了个图来加深下记忆



View提供获取坐标的方法:

getTop:View 顶部 到父容器 定边的距离

getLeft:View 左边 到父容器 左边的距离

getRight:View 右边 到父容器 左边的距离(将参考点想成父容器的左边,即父容器到该View右边的距离)

etBottom:View 底部 到父容器 顶边的距离

MotionEvent提供的方法:

getX:点击位置 到View 左边的距离(视图坐标)

getY:点击位置 到View 顶部的距离

getRawX:点击位置 到屏幕 左边的距离 (绝对坐标)

getRawY:点击位置 到屏幕 顶边的距离



实现移动:

实现移动的方法有很多,但是基本的原理都是一样的。当触摸View的时候,记录下触摸点的坐标,手指移动的时候,再获取一次坐标,这时将移动时获取到的坐标与前一次坐标做比较,即可得到偏移量,通过这个便宜量修改View的坐标即可实现移动。

下面通过代码来做一个简单的实现

继承View,重写onTouchEvent方法

int downX = 0;
    int downY = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //当触摸时,记录坐标
                downX = rawX;
                downY = rawY;
                break;
            case MotionEvent.ACTION_MOVE:
                //移动时,记录坐标,并计算出偏移量
                int offsetX = rawX-downX;
                int offsetY = rawY-downY;
                //在当前left  top  bottom  right上加上偏移量,得到的就是新的坐标
                layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
                downX = rawX;
                downY = rawY;
                break;
        }
        return true;
    }
效果





方法二:
使用LayoutParams

原理:LayoutParams封装了View的所有布局参数,可以通过改变他的leftManager和topManager来实现移动效果

case MotionEvent.ACTION_MOVE:

                int offsetX = rawX - downX;
                int offsetY = rawY - downY;
                ViewGroup.MarginLayoutParams layoutP = (ViewGroup.MarginLayoutParams)getLayoutParams();
                layoutP.leftMargin = getLeft()+offsetX;
                layoutP.topMargin = getTop()+offsetY;
                setLayoutParams(layoutP);
                downX = rawX;
                downY = rawY;
                break;


实现的效果是一样的

另外,谷歌还为我们提供了左右,上下移动的API封装,offsetLeftAndRight()和offsetTopAndBottom()

方法三:

ScrollBy和ScrollTo

在View中使用:移动的就是View的内容,(如在Button中,移动的就是button中的text)

在ViewGroup中,移动的将是所有的View(移动可见视图)

在View中使用的效果:



移动的其实就是视图的可视区域

那么什么是可视区域呢?

假如我们要移动的View布局是这样的



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <com.czp.view.ScrollView
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:background="#f00"
        >

    </com.czp.view.ScrollView>
</LinearLayout>


使用((View)getParent()).scrollBy(-offsetX,-offsetY)来实现移动,(先不管为什么这里要将计算出来的偏移量设置成负值)

ScorllerTo(by)移动的是可视区域,而不是内容

要知道,Android的坐标系是没有边界的,屏幕显示内容的框也是一个可视区域



是不是挺形象的?到了这里也就能明白scrollBy(-offsetX,-offsetY)为什么要写负值了

本来我们View往右边移动的,偏移量应该是正的,但是scrollBy移动是的可视区域,但是可视区域

向左边移动的,View是往右边的,位置反向

Scroller实现:

前面介绍的几种方法,在完成移动的时候,都是瞬间完成的,看起来非常生硬,让人感觉很粗糙,应该给他加一点动画让他看起来平滑一下

Scroller就是来完成这项工作的。

使用Scroller的步骤

初始化

scroller = new Scrollr(context)

重写computeScroll方法,在使用startScoller后,回不断调用该方法进行重绘

使用StartScroll开发滑动

public void startScroll(int startX,int StartY,int dx,int dy)

参数

  startX 水平方向滚动的偏移值,以像素为单位。正值表明滚动将向左滚动

  startY 垂直方向滚动的偏移值,以像素为单位。正值表明滚动将向上滚动

  dx 水平方向滑动的距离,正值会使滚动向左滚动

  dy 垂直方向滑动的距离,正值会使滚动向上滚动

下面通过一例子理解一下

@Override
    public void computeScroll() {
        super.computeScroll();
        //判断移动是否执行完毕
        if(scroller.computeScrollOffset()){
            ((View)getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY());
        }
    }

    int downX = 0;
    int downY = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //当触摸时,记录坐标
                downX = rawX;
                downY = rawY;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = rawX - downX;
                int offsetY = rawY - downY;
                ((View)getParent()).scrollBy(-offsetX,-offsetY);
                downX = rawX;
                downY = rawY;
                break;
            case MotionEvent.ACTION_UP:
                scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), -scroller.getCurrX(), -scroller.getCurrY(), 400);
                invalidate();
                break;
        }
        return true;
    }




综合以上的知识,我们完成可以自己实现一个SlideMenu侧滑控件

代码相当简单,没有什么难点

public class SlideMenu extends ViewGroup {
    private View mMainLayout, mMenuLayout;
    private Scroller scroller;
    private Context context;
    public SlideMenu(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        init();
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        //判断动画是否执行结束
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),0);
            invalidate();
        }
    }

    public void init(){
        scroller = new Scroller(context);
    }
    /**
     * 当一级的子View加载完的时候调用
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMenuLayout = getChildAt(0);
        mMainLayout = getChildAt(1);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mMenuLayout.measure(mMenuLayout.getLayoutParams().width,heightMeasureSpec);
        mMainLayout.measure(widthMeasureSpec,heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mMenuLayout.layout(-mMenuLayout.getLayoutParams().width,0,0,b);
        mMainLayout.layout(0,0,r,b);
    }
    int downX = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("DEMO","downX = "+downX  +"  MOVE == "+event.getRawX());
                int offsetX = (int) (event.getRawX() - downX);
                //当前滚动的位置加要滚动的距离,即得到目标位置
                int scrollX = getScrollX()-offsetX;
                Log.e("DEMO","scrollX == "+scrollX);
                if(scrollX>=-mMenuLayout.getLayoutParams().width&&scrollX<=0){
                    scrollTo(scrollX,0);
                    downX = (int) event.getX();
                }
                break;
            case MotionEvent.ACTION_UP:
                if(getScrollX()>-mMenuLayout.getLayoutParams().width/2){
                    closeMenu();
                }else{
                    openMenu();
                }
                break;
        }
        return true;
    }
    /**
     * 关闭菜单
     */
    public void closeMenu(){
//        scrollTo(0,0);
        scroller.startScroll(getScrollX(),0,0-getScrollX(),0,400);
        invalidate();
    }
    /**
     * 打开菜单
     */
    public void openMenu(){
//        scrollTo(-mMenuLayout.getLayoutParams().width,0);
        scroller.startScroll(getScrollX(), 0, -mMenuLayout.getLayoutParams().width-getScrollX(), 0, 400);
        invalidate();
    }
}


布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" tools:context=".MainActivity">

    <com.example.administrator.myapplication.SlideMenu
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
        <LinearLayout
            android:layout_width="150dp"
            android:background="#f00"
            android:layout_height="fill_parent">
        </LinearLayout>

        <LinearLayout
            android:layout_width="fill_parent"
            android:background="#88dc73"
            android:layout_height="fill_parent">
        </LinearLayout>
    </com.example.administrator.myapplication.SlideMenu>

</RelativeLayout>


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: