Android实现滑动的七种方式

来源:互联网 发布:快网域名 编辑:程序博客网 时间:2024/06/06 03:51

1. layout方法

根据用户手指滑动的位置(ACTION_MOVE),记录每次小段的偏移量(offset),通过layout不停对view进行重新布局,完成view的移动效果。

a). 使用视图坐标系:getX(),getY()

    @Override    public boolean onTouchEvent(MotionEvent event) {        // 这里需要转换为int值,保证layout方法参数        int x = (int) event.getX();        int y = (int) event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastX = x;                mLastY = y;                break;            case MotionEvent.ACTION_MOVE:                int offsetX = x - mLastX;                int offsetY = y - mLastY;                layout(getLeft() + offsetX, getTop() + offsetY,                        getRight() + offsetX, getBottom() + offsetY);                break;        }        // 返回false不能达到滑动的目标        return true;    }}

由于每次layout后,触摸点相对于父控件的位置不变,因此滑动期间不需要更新mLastX和mLastY的值。

b). 使用Android坐标系:getRawX(),getRawY()

    @Override    public boolean onTouchEvent(MotionEvent event) {        // 这里需要转换为int值,保证layout方法参数        int x = (int) event.getRawX();        int y = (int) event.getRawY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastX = x;                mLastY = y;                break;            case MotionEvent.ACTION_MOVE:                int offsetX = x - mLastX;                int offsetY = y - mLastY;                layout(getLeft() + offsetX, getTop() + offsetY,                        getRight() + offsetX, getBottom() + offsetY);                // 需要更新mLastX与mLastY的值                mLastX = x;                mLastY = y;                break;        }        // 返回false不能达到滑动的目标        return true;    }

需要注意的是,每次move更新界面后都要更新上一次的坐标值,否则下一次布局时会加上控件到屏幕边缘的距离。

2. offsetLeftAndRight()、offsetTopAndBottom()方法:和第一种方法没啥区别

    @Override    public boolean onTouchEvent(MotionEvent event) {        // 这里需要转换为int值,保证layout方法参数        int x = (int) event.getX();        int y = (int) event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastX = x;                mLastY = y;                break;            case MotionEvent.ACTION_MOVE:                int offsetX = x - mLastX;                int offsetY = y - mLastY;                offsetLeftAndRight(offsetX);                offsetTopAndBottom(offsetY);                break;        }        // 返回false不能达到滑动的目标        return true;    }

3. layoutParams: 利用设置边距完成view的移动

    @Override    public boolean onTouchEvent(MotionEvent event) {        // 这里需要转换为int值,保证layout方法参数        int x = (int) event.getX();        int y = (int) event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastX = x;                mLastY = y;                break;            case MotionEvent.ACTION_MOVE:                int offsetX = x - mLastX;                int offsetY = y - mLastY;                ViewGroup.MarginLayoutParams layoutParams =                        (ViewGroup.MarginLayoutParams) getLayoutParams();                layoutParams.leftMargin = getLeft() + offsetX;                layoutParams.topMargin = getTop() + offsetY;                setLayoutParams(layoutParams);                break;        }        // 返回false不能达到滑动的目标        return true;    }

注意:使用ViewGroup必须保证该控件有一个父布局,否则不能使用

4. scrollBy、scrollTo:瞬间移动

    @Override    public boolean onTouchEvent(MotionEvent event) {        // 这里需要转换为int值,保证layout方法参数        int x = (int) event.getX();        int y = (int) event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastX = x;                mLastY = y;                break;            case MotionEvent.ACTION_MOVE:                int offsetX = x - mLastX;                int offsetY = y - mLastY;                // scrollBy和scrollTo的移动方向为屏幕移动方向,与控件移动方向相反                // scrollBy移动的是content,不是view                ((View) getParent()).scrollBy(-offsetX, -offsetY);                break;        }        // 返回false不能达到滑动的目标        return true;    }

注意:scroll方法移动的不是控件本身,而是其内容。举例来说,View为ViewGroup时,移动的是其全部子控件;View为TextView时,移动的是其文字内容。

5. Scroller:实现平滑移动,在移动模块的同时增加松手后回弹至初始位置的功能。

public class Rect extends View {    private int mLastX;    private int mLastY;    private Scroller mScroller;    public Rect(Context context, AttributeSet attrs) {        super(context, attrs);        mScroller = new Scroller(context);    }    @Override    public void computeScroll() {        super.computeScroll();        // 如果Scroller还在计算中,则另其移动        if (mScroller.computeScrollOffset()) {            ((View)getParent()).scrollTo(mScroller.getCurrX(),                    mScroller.getCurrY());            invalidate();        }    }    @Override    public boolean onTouchEvent(MotionEvent event) {        // 这里需要转换为int值,保证layout方法参数        int x = (int) event.getX();        int y = (int) event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastX = x;                mLastY = y;                break;            case MotionEvent.ACTION_MOVE:                int offsetX = x - mLastX;                int offsetY = y - mLastY;                // scrollBy和scrollTo的移动方向为屏幕移动方向,与控件移动方向相反                // scrollBy移动的是content,不是view                ((View) getParent()).scrollBy(-offsetX, -offsetY);                break;            case MotionEvent.ACTION_UP:                View viewGroup = (View) getParent();                // 这里的起始坐标为content的起始坐标,而不是view的起始坐标                mScroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(),                        -viewGroup.getScrollX(), -viewGroup.getScrollY());                invalidate();        }        // 返回false不能达到滑动的目标        return true;    }}
步骤分为:初始化Scroller对象——复写computeScroll函数——调用Scroller对象的startScroll函数开启滑动过程

6. 属性动画

后期待添加

7. ViewDragHelper

xml文件:

<?xml version="1.0" encoding="utf-8"?><com.example.tianshuhe.learningcomponent.DragFrameLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.example.tianshuhe.learningcomponent.MainActivity">    <View        android:id="@+id/menu_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="@android:color/black"/>    <View        android:id="@+id/main_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="@android:color/holo_red_light"/></com.example.tianshuhe.learningcomponent.DragFrameLayout>

自定义的DragFrameLayout文件:

package com.example.tianshuhe.learningcomponent;import android.content.Context;import android.support.v4.view.ViewCompat;import android.support.v4.widget.ViewDragHelper;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.widget.FrameLayout;/** * Created by tianshuhe on 17/8/22. */public class DragFrameLayout extends FrameLayout {    private ViewDragHelper mViewDragHelper;    private ViewDragHelper.Callback mCallback;    private View mMainView, mMenuView;    public DragFrameLayout (Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    private void initView() {        mCallback = new ViewDragHelper.Callback() {            @Override            // 只拦截主界面的滑动事件            public boolean tryCaptureView(View child, int pointerId) {                return mMainView == 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;            }            @Override            public void onViewReleased(View releasedChild, float xvel, float yvel) {                super.onViewReleased(releasedChild, xvel, yvel);                // 根据滑动的大小,设置是否显示全部的菜单                // 这种实现方式实际上是一开始menu被main阻挡,后期main被拿开                if (mMainView.getLeft() < 500) {                    mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);                    ViewCompat.postInvalidateOnAnimation(DragFrameLayout.this);                } else {                    mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);                    ViewCompat.postInvalidateOnAnimation(DragFrameLayout.this);                }            }        };        mViewDragHelper = ViewDragHelper.create(this, mCallback);    }    // 拦截点击事件    @Override    public boolean onTouchEvent(MotionEvent event) {        mViewDragHelper.processTouchEvent(event);        return true;    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        // 这里一开始main和menu使用自定义的滑动view,则返回true,否则不能实现效果        return mViewDragHelper.shouldInterceptTouchEvent(ev);    }    @Override    public void computeScroll() {        if (mViewDragHelper.continueSettling(true)) {            ViewCompat.postInvalidateOnAnimation(this);        }    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        mMainView = findViewById(R.id.main_view);        mMenuView = findViewById(R.id.menu_view);    }}

一个奇怪的现象:onInterceptTouchEvent中,如果本身的控件带有滑动效果,这种方式返回后并不会对其事件进行拦截,这种情况待后续分析。