自定义悬浮可拖拽Layout

自定义可拖拽悬浮Layout

float_button1.gif
float_button2.gif

DragFloatActionLayout.java

/**
 * author: roc
 * time: 2020-03-25 10:14
 * explain:  可拖拽的悬浮View
 */
public class DragFloatActionLayout extends LinearLayout {

    private Context mContext;

    private int parentHeight;//父控件的高度
    private int parentWidth;//父控件的宽度

    private int lastX;
    private int lastY;

    private int downX = 0;
    private int downY = 0;
    private int upX = 0;
    private int upY = 0;

    private int borderMargin;//边界停留距离
    private int stayTime;//停留时间
    private float bottomMargin;//控件距离底部的距离
    private float topMargin;//控件距离顶部的距离
    private int remainWidth;//隐藏时边界剩余宽度

    private boolean isBerth;//是否隐藏到边缘

    private boolean isHide = false;//是否隐藏
    private Timer mTimer;

    private ViewGroup parent;
    private OnClickListener onClickListener;

    public DragFloatActionLayout(Context context) {
        this(context, null);
    }

    public DragFloatActionLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

        mContext = context;

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DragFloatActionLayout, defStyleAttr, 0);
        borderMargin = typedArray.getDimensionPixelSize(R.styleable.DragFloatActionLayout_base_float_action_border_margin, 0);
        bottomMargin = typedArray.getDimensionPixelSize(R.styleable.DragFloatActionLayout_base_float_action_bottom_margin, DisplayUtil.dip2px(context, 50));
        topMargin = typedArray.getDimensionPixelSize(R.styleable.DragFloatActionLayout_base_float_action_top_margin, DisplayUtil.dip2px(context, 50));
        remainWidth = typedArray.getDimensionPixelSize(R.styleable.DragFloatActionLayout_base_float_action_remain_width, DisplayUtil.dip2px(context, 15));
        stayTime = typedArray.getInteger(R.styleable.DragFloatActionLayout_base_float_action_stay_time, 5);
        isBerth = typedArray.getBoolean(R.styleable.DragFloatActionLayout_base_float_action_is_berth, true);
        typedArray.recycle();

    }


    @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:
                if (mTimer != null) {//当手指按下时,应结束倒计时
                    mTimer.cancel();
                    mTimer = null;
                }

                ViewParent viewParent = getParent();

                downX = lastX = rawX;
                downY = lastY = rawY;
                if (viewParent != null) {
                    //请求父控件不中断事件
                    viewParent.requestDisallowInterceptTouchEvent(true);
                    this.parent = (ViewGroup) viewParent;
                    //获取父控件的高度
                    parentHeight = this.parent.getHeight();
                    //获取父控件的宽度
                    parentWidth = this.parent.getWidth();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int dx = rawX - lastX;
                int dy = rawY - lastY;
                float x = getX() + dx;
                float y = getY() + dy;
                //检测是否到达边缘 左上右下
                x = x < 0 ? 0 : x > parentWidth - getWidth() ? parentWidth - getWidth() : x;
                //控件距离底部的margin
                y = y < 0 ? 0 : (float) (y > parentHeight - getHeight() - bottomMargin ? parentHeight - getHeight() - bottomMargin :
                        (y < topMargin ? topMargin : y));
                setX(x);
                setY(y);
                lastX = rawX;
                lastY = rawY;
                break;
            case MotionEvent.ACTION_UP:
                upX = (int) event.getRawX();
                upY = (int) event.getRawY();
                int distanceX = Math.abs(Math.abs(upX) - Math.abs(downX));
                int distanceY = Math.abs(Math.abs(upY) - Math.abs(downY));
                //当手指按下的事件跟手指抬起事件之间的距离小于10时执行点击事件
                if (Math.max(distanceX, distanceY) <= 10) {
                    if (isHide) {
                        showView();
                    } else if (onClickListener != null) {
                        onClickListener.onClick();
                    }
                } else {
                    moveHide(rawX);
                }
                if (isBerth)
                    hideView();//手指抬起n(n为自定义属性)秒后隐藏
                break;
        }
        //如果是拖拽则消s耗事件,否则正常传递即可。
        return true;
    }


    private void moveHide(int rawX) {
        isHide = false;
        if (rawX >= parentWidth / 2) {

            //靠右吸附
            animate().setInterpolator(new DecelerateInterpolator())
                    .setDuration(500)
                    .xBy(parentWidth - getWidth() - getX() - borderMargin)
                    .start();
        } else {
            //靠左吸附
            ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(), borderMargin);
            oa.setInterpolator(new DecelerateInterpolator());
            oa.setDuration(500);
            oa.start();

        }
    }

    /**
     * 显示View
     */
    private void showView() {
        if (getX() >= parentWidth / 2) {//靠右显示

            ObjectAnimator oa = ObjectAnimator.ofFloat(DragFloatActionLayout.this, "x", getX(), parentWidth - getWidth() - borderMargin);
            oa.setInterpolator(new DecelerateInterpolator());
            oa.setDuration(500);
            oa.start();

        } else {//靠左显示

            ObjectAnimator oa = ObjectAnimator.ofFloat(DragFloatActionLayout.this, "x", getX(), borderMargin);
            oa.setInterpolator(new DecelerateInterpolator());
            oa.setDuration(500);
            oa.start();
        }
        isHide = false;
    }


    /**
     * 隐藏View
     */
    private void hideView() {
        if (mTimer == null)
            mTimer = new Timer();
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                ((Activity) mContext).runOnUiThread(new Runnable() {
                    @Override
                    public void run() {

                        if (getX() >= parentWidth / 2) {//靠右隐藏

                            ObjectAnimator oa = ObjectAnimator.ofFloat(DragFloatActionLayout.this, "x", getX(), parentWidth - remainWidth);
                            oa.setInterpolator(new DecelerateInterpolator());
                            oa.setDuration(500);
                            oa.start();

                        } else {//靠左隐藏

                            ObjectAnimator oa = ObjectAnimator.ofFloat(DragFloatActionLayout.this, "x", getX(), -getWidth() + remainWidth);
                            oa.setInterpolator(new DecelerateInterpolator());
                            oa.setDuration(500);
                            oa.start();

                        }

                        isHide = true;
                    }
                });
            }
        }, stayTime * 1000);

    }

    /**
     * 设置顶部距离
     *
     * @param topMargin
     */
    public void setMarginTop(float topMargin) {
        this.topMargin = topMargin;
    }

    public void setOnClickListener(OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }

    public interface OnClickListener {
        void onClick();
    }

    /**
     * 释放资源
     */
    public void release() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }
    }

}

attr.xml

 <!-- 自定义 可拖拽的悬浮View -->
    <declare-styleable name="DragFloatActionLayout">
        <attr name="base_float_action_border_margin" format="dimension" /><!-- 边界距离 -->
        <attr name="base_float_action_bottom_margin" format="dimension" /><!-- 底部距离 -->
        <attr name="base_float_action_top_margin" format="dimension" /><!-- 顶部距离 -->
        <attr name="base_float_action_stay_time" format="integer" /><!-- 隐藏时间 -->
        <attr name="base_float_action_remain_width" format="dimension" /><!-- 隐藏时边界露出的宽度 -->
        <attr name="base_float_action_is_berth" format="boolean" /><!-- 是否停靠边缘隐藏 -->
    </declare-styleable>
使用方式,像正常的Linearlayout一样使用就行
<DragFloatActionLayout
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_above="@id/product_place_bottom_bar"
        android:layout_alignParentEnd="true"
        android:layout_marginEnd="15dp"
        android:layout_marginBottom="40dp"
        android:background="@drawable/consult_shape"
        android:gravity="center"
        android:orientation="vertical"
        app:base_float_action_border_margin="15dp"
        app:base_float_action_bottom_margin="50dp"
        app:base_float_action_is_berth="false"
        app:base_float_action_stay_time="5"
        app:base_float_action_top_margin="200dp">


        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="center"
            android:src="@mipmap/consult_img" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:gravity="center"
            android:text="咨询"
            android:textColor="@color/textColor3"
            android:textSize="11sp" />


    </DragFloatActionLayout>

推荐阅读更多精彩内容

  • 自定义View/ViewGroup基本步骤 选择和设置构造方法;重写onMeasure()方法;重写onDraw(...
    不会敲代码的好代码阅读 2,177评论 2 3
  • 自定义View之自定义设置界面栏位 在app的应用设置中经常会有如下所示的设置样式,正好学习自定义view,下面我...
    Ugly_K阅读 306评论 0 1
  • 简介 本篇,接上一篇自定义一个简易版本的 RelativeLayout ,还是自定义 ViewGroup ,以此来...
    阿瑞921阅读 1,917评论 0 3
  • 经过前面几篇文章 View 基础 View 的测量过程 View 的布局和绘制 Android 滑动原理与方式 A...
    任教主来也阅读 308评论 0 8
  • 一.概述 通过本篇文章的学习,你将学会:1.自定义View的流程2.自定义View分类 二.自定义View的流程 ...
    潇洒哥gyq阅读 193评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 5,399评论 16 21
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 7,984评论 0 9
  • 可爱进取,孤独成精。努力飞翔,天堂翱翔。战争美好,孤独进取。胆大飞翔,成就辉煌。努力进取,遥望,和谐家园。可爱游走...
    赵原野阅读 1,498评论 1 1
  • 在妖界我有个名头叫胡百晓,无论是何事,只要找到胡百晓即可有解决的办法。因为是只狐狸大家以讹传讹叫我“倾城百晓”,...
    猫九0110阅读 1,431评论 2 3