使用layout方法实现控件的移动并且不影响点击事件

控件移动的方法很多种,譬如layout,属性动画,scrollBy和scrollTo等等…

接下来我将使用layout方法来演示控件的移动,并且处理点击事件冲突,以及父布局刷新的时候空间回到原处的处理。

使用layout的好处是适合于有交互的view,相比属性动画,不能兼容到3.0以下,即使使用大神的nineoldandroids3.0下实现的仍旧是view动画;相比scrollBy和scrollTo,虽然操作简单但是只是对view内容的滑动。

接下来将演示对FloatActionButton的移动实现,因为在实际中有这样的需求,浏览商品是一瀑布流,然后item是一张商品图片加快速收藏和购买的按钮,当用户拉到底部全部加载完的时候,右下角的悬浮按钮就刚好挡住了购买的按钮,所以为了人性化,将悬浮按钮做成可移动的,让用户自己调整位置。

简单的效果图如下

实现原理相当简单,继承FloatActionButton重写onTouchEvent方法,记录坐标值调layout同时记录下移动后的坐标;然后继承FloatActionButton所在的父布局的类,重写onlayout,遍历子布局,拿到我们需要的子控件调layout,参数使用的是记录好的坐标,即可。

XML如下,MyCoordinatorLayout是重写onLayout的CoordinatorLayout;
DraggableFloatingButton是重写onTouchEvent的FloatActionButton

<?xml version="1.0" encoding="utf-8"?>
<com.oushangfeng.floatactionbuttondemo.MyCoordinatorLayout
    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"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"/>

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main"/>

    <com.oushangfeng.floatactionbuttondemo.DraggableFloatingButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email"/>

</com.oushangfeng.floatactionbuttondemo.MyCoordinatorLayout>

首先看下DraggableFloatingButton:
①ACTION_DOWN时候记录系统时间,再与ACTION_UP时间作比较,这里设定200ms,即按下松开200ms内,视为动作有效响应此事件;
②ACTION_MOVE会拿到控件的上下左右坐标,加上移动量,进行边界的判断,最后调研layout移动,并记录下移动后的上下左右坐标值

 // 记录上次的xy坐标
    private int mLastX, mLastY;
    // 记录按下和松开手指的时间
    private long mDownTime, mUpTime;

    // 记录上次移动完的上下左右坐标
    private int mLastLeft = -1;
    private int mLastRight = -1;
    private int mLastTop = -1;
    private int mLastBottom = -1;

    public int getLastLeft() {
        return mLastLeft;
    }

    public int getLastRight() {
        return mLastRight;
    }

    public int getLastTop() {
        return mLastTop;
    }

    public int getLastBottom() {
        return mLastBottom;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownTime = System.currentTimeMillis();
                break;
            case MotionEvent.ACTION_MOVE:

                int deltaX = x - mLastX;
                int deltaY = y - mLastY;

                // 移动后的上下左右坐标
                int left = getLeft() + deltaX;
                int right = getRight() + deltaX;
                int top = getTop() + deltaY;
                int bottom = getBottom() + deltaY;

                int marginLeft = 0;
                int marginRight = 0;
                int marginTop = 0;
                int marginBottom = 0;

                if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
                    // 获取margin值,做移动的边界判断
                    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) 
                    getLayoutParams();
                    marginLeft = lp.leftMargin;
                    marginRight = lp.rightMargin;
                    marginTop = lp.topMargin;
                    marginBottom = lp.bottomMargin;
                }

                int parentWidth = 0;
                int parentHeight = 0;

                if (getParent() != null && getParent() instanceof ViewGroup) {

                    ViewGroup parent = (ViewGroup) getParent();

                    // 拿到父布局宽高
                    parentWidth = parent.getWidth();
                    parentHeight = parent.getHeight();

                    if (left < marginLeft) {
                        // 移到到最左的时候,限制在marginLeft
                        left = marginLeft;
                        right = getWidth() + left;
                    }

                    if (right > parentWidth - marginRight) {
                        // 移动到最右
                        right = parentWidth - marginRight;
                        left = right - getWidth();
                    }

                    if (top < marginTop) {
                        // 移动到顶部
                        top = marginTop;
                        bottom = getHeight() + top;
                    }

                    if (bottom > parentHeight - marginBottom) {
                        // 移动到底部
                        bottom = parentHeight - marginBottom;
                        top = bottom - getHeight();
                    }

                    layout(left, top, right, bottom);

                    // 记录移动后的坐标值
                    mLastLeft = left;
                    mLastRight = right;
                    mLastTop = top;
                    mLastBottom = bottom;

                }

                break;
            case MotionEvent.ACTION_UP:
                mLastX = x;
                mLastY = y;
                mUpTime = System.currentTimeMillis();
                // 点击事件的处理
                return mUpTime - mDownTime > 200 || super.onTouchEvent(event);
        }

        mLastX = x;
        mLastY = y;

        return super.onTouchEvent(event);
    }

接下来看下MyCoordinatorLayout:
这个更简单,在刷新子控件位置的时候,去遍历一下,找到DraggableFloatingButton,如果left不为-1则说明之前移动过了,子控件再自己调次layout用自己记录的上次移动后的坐标,即可保证位置在父布局刷新的时候不受影响

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        for (int i = 0; i < getChildCount(); i++) {
            if (getChildAt(i) instanceof DraggableFloatingButton) {
                // 为了防止浮动按钮恢复原位,布局子控件位置时使用上次记录的位置
                DraggableFloatingButton child = (DraggableFloatingButton) getChildAt(i);
                if (child.getLastLeft() != -1) {
                    child.layout(child.getLastLeft(), child.getLastTop(), child.getLastRight(), 
                    child.getLastBottom());
                }
                break;
            }
        }
    }

实在是很简单,附demo
可拖动的FloatActionButton

你可能感兴趣的:(移动,布局,控件,悬浮按钮)