Android实现滑动的七种方式

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中,如果本身的控件带有滑动效果,这种方式返回后并不会对其事件进行拦截,这种情况待后续分析。


  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值