Android 自定义悬浮拖动吸附按钮

一个悬浮的拨打电话按钮,使用CardView+ImageView可能会出现适配问题,也就是图片显示不全,出现这种问题,就直接替换控件了,因为上述的组合控件没有FloatingActionButton使用方便,还可以有拖动和吸附效果不是更好吗。

1.一般自定义就可以实现,看看第一种方式:直接上代码了:

java 复制代码
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;

import com.baijie.crm.activity.utils.Util;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

/**
 * 悬浮吸附可拖动按钮
 */
@SuppressLint("AppCompatCustomView")
public class DragFloatActionButton extends FloatingActionButton {

    private static final String TAG = "DragButton";
    private int parentHeight;
    private int parentWidth;

    private int lastX;
    private int lastY;

    private boolean isDrag;
    private ViewGroup parent;

    public DragFloatActionButton(Context context) {
        super(context);
    }

    public DragFloatActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public DragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                isDrag = false;
                this.setAlpha(0.9f);
                setPressed(true);
                getParent().requestDisallowInterceptTouchEvent(true);
                lastX = rawX;
                lastY = rawY;
                if (getParent() != null) {
                    parent = (ViewGroup) getParent();
                    parentHeight = parent.getHeight();
                    parentWidth = parent.getWidth();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                this.setAlpha(0.9f);
                int dx = rawX - lastX;
                int dy = rawY - lastY;
                int distance = (int) Math.sqrt(dx * dx + dy * dy);
                if (distance > 2 && !isDrag) {
                    isDrag = true;
                }

                float x = getX() + dx;
                float y = getY() + dy;
                //检测是否到达边缘 左上右下
                x = x < 0 ? 0 : x > parentWidth - getWidth() ? parentWidth - getWidth() : x;
                y = getY() < 0 ? 0 : getY() + getHeight() > parentHeight ? parentHeight - getHeight() : y;
                setX(x);
                setY(y);
                lastX = rawX;
                lastY = rawY;
                break;
            case MotionEvent.ACTION_UP:
                if (isDrag) {
                    //恢复按压效果
                    setPressed(false);
                    moveHide(rawX);
                }
                break;
        }
        //如果是拖拽则消耗事件,否则正常传递即可。
        return isDrag || super.onTouchEvent(event);
    }


    private void moveHide(int rawX) {
        if (rawX >= parentWidth / 2) {
            //靠右吸附
            animate().setInterpolator(new DecelerateInterpolator())
                    .setDuration(500)
                    //.xBy(parentWidth - getWidth() - getX())
//                    .xBy(parentWidth - getWidth() - getX() - DensityUtils.dp2px(getContext(), 20))
                    .xBy(parentWidth - getWidth() - getX() - Util.dp2px(getContext(), 20))
                    .start();
        } else {
            //靠左吸附
            //ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(), 0);
            // 设置左侧间距 20
            ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(),
                    Util.dp2px(getContext(), 20));
            oa.setInterpolator(new DecelerateInterpolator());
            oa.setDuration(500);
            oa.start();

        }
    }
}
java 复制代码
public static int dp2px(Context context, float dp)
    {
        return (int ) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
    }
java 复制代码
<com.666.widget.DragFloatActionButton
        android:id="@+id/dragFloatActionButton"
        android:visibility="visible"
        android:layout_width="58dp"
        android:layout_height="58dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_gravity="bottom|center"
        android:layout_marginRight="15dp"
        android:layout_marginBottom="350dp"
        android:src="@mipmap/sixsixsix"
        app:backgroundTint="?attr/colorPrimary"
        app:borderWidth="0.0dip"
        app:elevation="15.0dip"
        app:fabCustomSize="58dp"
        app:rippleColor="#BFEFFF"/>

2.下面是之前借鉴的写法,仅供参考

java 复制代码
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;

import com.alipay.pushsdk.util.log.LogUtil;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

/**
 * 可拖拽 吸附的悬浮按钮
 */
public class AiDragFloatActionButton extends FloatingActionButton {

    private int parentHeight;
    private int parentWidth;

    public AiDragFloatActionButton(Context context) {
        super(context);
    }

    public AiDragFloatActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    public AiDragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }


    private int lastX;
    private int lastY;

    private boolean isDrag;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                LogUtil.d("SGF","MotionEvent.ACTION_DOWN");
                setPressed(true);
                isDrag = false;
                getParent().requestDisallowInterceptTouchEvent(true);
                lastX = rawX;
                lastY = rawY;
                ViewGroup parent;
                if (getParent() != null) {
                    parent = (ViewGroup) getParent();
                    parentHeight = parent.getHeight();
                    parentWidth = parent.getWidth();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtil.d("SGF","MotionEvent.ACTION_MOVE");
                if (parentHeight <= 0 || parentWidth == 0) {
                    isDrag = false;
                    break;
                } else {
                    isDrag = true;
                }
                int dx = rawX - lastX;
                int dy = rawY - lastY;
                //这里修复一些华为手机无法触发点击事件
                int distance = (int) Math.sqrt(dx * dx + dy * dy);
                if (distance == 0) {
                    isDrag = false;
                    break;
                }
                float x = getX() + dx;
                float y = getY() + dy;
                //检测是否到达边缘 左上右下
                x = x < 0 ? 0 : x > parentWidth - getWidth() ? parentWidth - getWidth() : x;
                y = getY() < 0 ? 0 : getY() + getHeight() > parentHeight ? parentHeight - getHeight() : y;
                setX(x);
                setY(y);
                lastX = rawX;
                lastY = rawY;
                Log.i("aa", "isDrag=" + isDrag + "getX=" + getX() + ";getY=" + getY() + ";parentWidth=" + parentWidth);
                break;
            case MotionEvent.ACTION_UP:
                LogUtil.d("SGF","MotionEvent.ACTION_UP");
                if (!isNotDrag()) {
                    //恢复按压效果
                    setPressed(false);
                    //Log.i("getX="+getX()+";screenWidthHalf="+screenWidthHalf);
                    if (rawX >= parentWidth / 2) {
                        //靠右吸附
                        animate().setInterpolator(new DecelerateInterpolator())
                                .setDuration(500)
                                // 将松开后悬停的位置修改一下(右侧保留35的间距)
                                .xBy((parentWidth - getWidth() - getX()) - 35)
                                .start();
                    } else {
                        //靠左吸附
                        // 将松开后悬停的位置修改一下(左侧保留35的间距)
                        ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(), 35);
                        oa.setInterpolator(new DecelerateInterpolator());
                        oa.setDuration(500);
                        oa.start();

//                        animate().setInterpolator(new DecelerateInterpolator())
//                                .setDuration(500)
//                                // 将松开后悬停的位置修改一下(右侧保留35的间距)
//                                .xBy(50)
//                                .start();
                    }
                }
                break;
//            case MotionEvent.ACTION_CANCEL:
//                LogUtil.d("SGF","MotionEvent.ACTION_CANCEL");
//                getParent().requestDisallowInterceptTouchEvent(false);
//                break;
        }
        //如果是拖拽则消s耗事件,否则正常传递即可。
        return !isNotDrag() || super.onTouchEvent(event);
    }

    private boolean isNotDrag() {
        return !isDrag && (getX() == 0
                || (getX() == parentWidth - getWidth()));
    }
}

这种方式测试功能基本是一样的,但是就是无法直接设置控件的监听事件,可以看看。

java 复制代码
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

public class DraggableFloatingActionButton extends FloatingActionButton {

    private int lastX, lastY;
    private WindowManager.LayoutParams params;

    public DraggableFloatingActionButton(Context context) {
        super(context);
        init();
    }

    public DraggableFloatingActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public DraggableFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int dx = (int) event.getRawX() - lastX;
                        int dy = (int) event.getRawY() - lastY;

                        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
                        layoutParams.leftMargin += dx;
                        layoutParams.topMargin += dy;
                        setLayoutParams(layoutParams);

                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();

                        break;
                }
                return true;
            }
        });
    }
}

上面这种就是最原始的自定义方式了,没有什么好描述的了。

其它相关案例:

GitHub - liwenzhi/FloatingActionButton: 悬浮的按钮设计

相关推荐
韩仔搭建4 小时前
第二章:安卓端启动流程详解与疑难杂症调试手册
android·ui·娱乐
A-花开堪折4 小时前
Android7 Input(七)App与input系统服务建立连接
android
吃汉堡吃到饱4 小时前
【Android】从Choreographer到UI渲染(二)
android·ui
微信公众号:AI创造财富4 小时前
显示的图标跟UI界面对应不上。
android·ui
aningxiaoxixi5 小时前
安卓 Audio Stream 类型
android
奔跑吧 android5 小时前
【android bluetooth 协议分析 01】【HCI 层介绍 3】【NUMBER_OF_COMPLETED_PACKETS 事件介绍】
android·bluetooth·hci·bt·gd·aosp13
_龙小鱼_7 小时前
Kotlin扩展简化Android动画开发
android·开发语言·kotlin
奔跑吧 android8 小时前
【android bluetooth 协议分析 01】【HCI 层介绍 6】【WriteLeHostSupport命令介绍】
android·bluetooth·bt·gd·aosp13·writelehostsup·hcicmd
uwvwko8 小时前
ctfshow——web入门254~258
android·前端·web·ctf·反序列化