添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

小知识,大挑战!本文正在参与 “ 程序员必备小知识 ” 创作活动

本文已参与「 掘力星计划 」,赢取创作大礼包,挑战创作激励金。

类似文章在CSDN上有很多,但是几经查找之后原文其实产于简书的一位 作者

综合几篇文章,在原有基础上我会尽可能全面总结一下

  • 通过重写控件的onTouchEvent方法监听触摸效果
  • 通过View的setX()和setY()方法实现移动
  • 使用属性动画实现边缘吸附效果
  • onTouch三种状态

    首先是处理手指按压下的事件,这里我们把拖拽标识符设置为false并记录当前点击的屏幕坐标。然后我们在移动事件处

    这里我们把拖拽标识符设置为true,因为手指移动了。然后我们需要计算手指移动了多少偏移量

    //计算手指移动了多少
    int dx=rawX-lastX;
    int dy=rawY-lastY;
    

    而后的两行代码表示控件需要移动的具体距离,后面有一个简单的边缘检测计算。最终通过调用setX以及setY方法实现控件的移动

    这里如果是拖拽动作我们才需要处理自己的逻辑否则直接跳过即可。在这里我们首先恢复了按钮的按压效果,在源代码中找到setPressed(boolean)方法,这是处理按钮点击效果用的,在这里当手指松开后我们需要恢复按钮原来的效果。然后在判断控件需要往哪边吸附,吸附的过程就是做属性动画而已,原理还是不断的改变setX方法让按钮靠边移动

    关于自定义的拖拽View (核心部分)

    继承类(均可实现拖拽)

  • 有的继承AppCompatImageView下的ImageView
  • 有的继承FloatingActionButton(如果继承此类需要导入以下依赖)
  •  compile 'com.android.support:design:26.1.0'
     implementation 'com.android.support:appcompat-v7:26.1.0'
    

    DragFloatActionButton (java版本)

    package com.advance.yongliu.haulview;
    import android.animation.ObjectAnimator;
    import android.annotation.SuppressLint;
    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 android.widget.ImageView;
     * author  YongLiu
     * date  2019/2/22.
     * desc:
    @SuppressLint("AppCompatCustomView")
    public class DragFloatActionButton extends ImageView {
        private int parentHeight;
        private int parentWidth;
        public DragFloatActionButtonJava(Context context) {
            super(context);
        public DragFloatActionButtonJava(Context context, AttributeSet attrs) {
            super(context, attrs);
        public DragFloatActionButtonJava(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:
                    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:
                    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:
                    if (!isNotDrag()) {
                        //恢复按压效果
                        setPressed(false);
                        if (rawX >= parentWidth / 2) {
                            //靠右吸附
                            animate().setInterpolator(new DecelerateInterpolator())
                                    .setDuration(500)
                                    .xBy(parentWidth - getWidth() - getX())
                                    .start();
                        } else {
                            //靠左吸附
                            ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(), 0);
                            oa.setInterpolator(new DecelerateInterpolator());
                            oa.setDuration(500);
                            oa.start();
                    break;
                default:
                    break;
            //如果是拖拽则消s耗事件,否则正常传递即可。
            return !isNotDrag() || super.onTouchEvent(event);
        private boolean isNotDrag() {
            return !isDrag && (getX() == 0|| (getX() == parentWidth - getWidth()));
    

    MainActivity

    package com.advance.yongliu.haulview;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.widget.Toast;
    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DragFloatActionButtonJava mBtn = findViewById(R.id.img_btn);
            mBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this,"点击了可拖拽按钮的点击事件",Toast.LENGTH_SHORT).show();
    

    activity_main

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        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"
        tools:context="com.advance.yongliu.haulview.MainActivity">
        <com.advance.yongliu.haulview.DragFloatActionButton
            android:id="@+id/img_btn"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:src="@mipmap/ic_launcher_round"
    </android.support.constraint.ConstraintLayout>
    
    DragFloatActionButton (kotlin版本)
    * 可拖拽的悬浮控件按钮 * 2018-08-27 * 直接xml布局里引用即可。 * 要设置setOnClickListener点击事件,即可实现拖拽和点击功能。 * 尺寸大小,样式及背景图片遵循ImageView即可。 class DragFloatActionButton : ImageView { private var parentHeight: Int = 0 private var parentWidth: Int = 0 constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) private var lastX: Int = 0 private var lastY: Int = 0 private var isDrag: Boolean = false override fun onTouchEvent(event: MotionEvent): Boolean { val rawX = event.rawX.toInt() val rawY = event.rawY.toInt() when (event.action and MotionEvent.ACTION_MASK) { MotionEvent.ACTION_DOWN -> { isPressed = true isDrag = false parent.requestDisallowInterceptTouchEvent(true) lastX = rawX lastY = rawY val parent: ViewGroup if (getParent() != null) { parent = getParent() as ViewGroup parentHeight = parent.height parentWidth = parent.width MotionEvent.ACTION_MOVE -> { if (parentHeight <= 0 || parentWidth === 0) { isDrag = false } else { isDrag = true val dx = rawX - lastX val dy = rawY - lastY //这里修复一些华为手机无法触发点击事件 val distance = Math.sqrt((dx * dx + dy * dy).toDouble()).toInt() if (distance == 0) { isDrag = false } else { var x = x + dx var y = y + dy //检测是否到达边缘 左上右下 x = if (x < 0) 0F else if (x > parentWidth - width) (parentWidth - width).toFloat() else x y = if (getY() < 0) 0F else if (getY() + height > parentHeight) (parentHeight - height).toFloat() else y setX(x) setY(y) lastX = rawX lastY = rawY Log.i("aa", "isDrag=" + isDrag + "getX=" + getX() + ";getY=" + getY() + ";parentWidth=" + parentWidth) MotionEvent.ACTION_UP -> if (!isNotDrag()) { //恢复按压效果 isPressed = false //Log.i("getX="+getX()+";screenWidthHalf="+screenWidthHalf); if (rawX >= parentWidth / 2) { //靠右吸附 animate().setInterpolator(DecelerateInterpolator()) .setDuration(500) .xBy(parentWidth - width - x) .start() } else { //靠左吸附 val oa = ObjectAnimator.ofFloat(this, "x", x, 0F) oa.setInterpolator(DecelerateInterpolator()) oa.setDuration(500) oa.start() //如果是拖拽则消s耗事件,否则正常传递即可。 return !isNotDrag() || super.onTouchEvent(event) private fun isNotDrag(): Boolean { return !isDrag && (x == 0f || x == (parentWidth - width).toFloat())

    用WindowManager实现Android悬浮框以及拖动事件 (未进行亲自测试)

     private void createFloatView() {
            //获取LayoutParams对象
            wmParams = new WindowManager.LayoutParams();
            //获取的是LocalWindowManager对象
            mWindowManager = this.getWindowManager();
            //设置window type
            wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            //设置图片格式,效果为背景透明
            wmParams.format = PixelFormat.RGBA_8888;
            //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
            wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            //调整悬浮窗显示的停靠位置为左侧置顶
            wmParams.gravity = Gravity.LEFT | Gravity.TOP;
            // 以屏幕左上角为原点,设置x、y初始值,相对于gravity
            wmParams.x = 0;
            wmParams.y = 0;
            //设置悬浮窗口长宽数据
            wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
             /*// 设置悬浮窗口长宽数据
            wmParams.width = 200;
            wmParams.height = 80;*/
            LayoutInflater inflater = this.getLayoutInflater();
            //获取浮动窗口视图所在布局
            mFloatLayout = (LinearLayout) inflater.inflate(R.layout.layout_item, null);
            //浮动窗口按钮
            mFloatView = mFloatLayout.findViewById(R.id.float_id);
            //添加mFloatLayout
            mWindowManager.addView(mFloatLayout, wmParams);
            //绑定触摸移动监听
            mFloatView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    // TODO Auto-generated method stub
                    wmParams.x = (int) event.getRawX() - mFloatLayout.getWidth() / 2;
                    //25为状态栏高度
                    wmParams.y = (int) event.getRawY() - mFloatLayout.getHeight() / 2 - 40;
                    mWindowManager.updateViewLayout(mFloatLayout, wmParams);
                    return false;
            //悬浮框设置点击监听
            mFloatView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    Toast.makeText(FloatWindowTest.this, "我是悬浮框", Toast.LENGTH_SHORT).show();
    

    layout_item.xml(悬浮框 )

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/float_id" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_margin="10dp" android:padding="10dp" android:background="@drawable/shape_background_dark" android:gravity="center" android:text="你好,再见!" android:textColor="#ffffff" android:textSize="20sp" /> </LinearLayout>

    原作者分别以Activity与Servier进行测试

    import android.app.Activity;
    import android.graphics.PixelFormat;
    import android.os.Bundle;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.WindowManager;
    import android.widget.Button;
    import android.widget.LinearLayout;
    import android.widget.TextView;
    import android.widget.Toast;
    import activity.main.qixin.com.xinqingofqian.R;
    public class FloatWindowTest extends Activity implements View.OnClickListener {
         * Called when the activity is first created.
        WindowManager mWindowManager;
        WindowManager.LayoutParams wmParams;
        LinearLayout mFloatLayout;
        TextView mFloatView;
        private Button start_Btn;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.test_window_float);
            //初始化控件
            initView();
        private void createFloatView() {
            //获取LayoutParams对象
            wmParams = new WindowManager.LayoutParams();
            //获取的是LocalWindowManager对象
            mWindowManager = this.getWindowManager();
            //设置window type
            wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            //设置图片格式,效果为背景透明
            wmParams.format = PixelFormat.RGBA_8888;
            //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
            wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            //调整悬浮窗显示的停靠位置为左侧置顶
            wmParams.gravity = Gravity.LEFT | Gravity.TOP;
            // 以屏幕左上角为原点,设置x、y初始值,相对于gravity
            wmParams.x = 0;
            wmParams.y = 0;
            //设置悬浮窗口长宽数据
            wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
             /*// 设置悬浮窗口长宽数据
            wmParams.width = 200;
            wmParams.height = 80;*/
            LayoutInflater inflater = this.getLayoutInflater();
            //获取浮动窗口视图所在布局
            mFloatLayout = (LinearLayout) inflater.inflate(R.layout.layout_item, null);
            //浮动窗口按钮
            mFloatView = mFloatLayout.findViewById(R.id.float_id);
            //添加mFloatLayout
            mWindowManager.addView(mFloatLayout, wmParams);
            //绑定触摸移动监听
            mFloatView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    // TODO Auto-generated method stub
                    wmParams.x = (int) event.getRawX() - mFloatLayout.getWidth() / 2;
                    //25为状态栏高度
                    wmParams.y = (int) event.getRawY() - mFloatLayout.getHeight() / 2 - 40;
                    mWindowManager.updateViewLayout(mFloatLayout, wmParams);
                    return false;
            //悬浮框设置点击监听
            mFloatView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    Toast.makeText(FloatWindowTest.this, "我是悬浮框", Toast.LENGTH_SHORT).show();
        private void initView() {
            start_Btn = (Button) findViewById(R.id.start_id);
            start_Btn.setOnClickListener(this);
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.start_id:
                    if (null == mWindowManager) {
                        createFloatView();
                    break;
        @Override
        public void onDestroy() {
            // TODO Auto-generated method stub
            super.onDestroy();
            if (mFloatLayout != null) {
                //移除悬浮窗口
                mWindowManager.removeView(mFloatLayout);
        Shanghai_MrLiu
            Android研发工程师
          
    粉丝