1. 程式人生 > 其它 >Android中實現一個可拖動的懸浮按鈕,點選彈出選單的功能

Android中實現一個可拖動的懸浮按鈕,點選彈出選單的功能

如圖:

實現思路
通過重寫控制元件的onTouchEvent方法監聽觸控效果
通過View的setX()和setY()方法實現移動
使用屬性動畫實現邊緣吸附效果
手指按下
首先是處理手指按壓下的事件,這裡我們把拖拽識別符號設定為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 (Kotlin版)

package com.example.mychartdemo

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
//https://blog.csdn.net/qq_20451879/article/details/87876673
//效果可以
@SuppressLint("AppCompatCustomView")
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

    public var isDrag: Boolean = false
    public var isRight: Boolean = false
    public var isTop: Boolean = true
    public var isCanMove: Boolean = true

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val rawX = event.rawX.toInt()
        val rawY = event.rawY.toInt()
        Log.i("列印X軸、Y軸座標:","rawX:$rawX  ,rawY: $rawY")
         if (!isCanMove){//不可移動
             return super.onTouchEvent(event)
         }

        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 -> {
                isDrag = !(parentHeight <= 0 || parentWidth === 0)
                /*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()

                    isRight = true
                } else {
                    //靠左吸附
                    val oa = ObjectAnimator.ofFloat(this, "x", x, 0F)
                    oa.interpolator = DecelerateInterpolator()
                    oa.duration = 500
                    oa.start()

                    isRight = false
                }
                //判斷是否位於上半部分
                isTop = rawY <= parentHeight/2
                Log.i("列印是否位於頂部:",""+isTop)
            }
        }
        //如果是拖拽則消s耗事件,否則正常傳遞即可。
        return !isNotDrag() || super.onTouchEvent(event)
    }

    private fun isNotDrag(): Boolean {
        return !isDrag && (x == 0f || x == (parentWidth - width).toFloat())
    }
}

  主佈局檔案

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity3">
    <com.example.mychartdemo.DragFloatActionButton
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:id="@+id/img_btn"
        android:src="@mipmap/ic_launcher"></com.example.mychartdemo.DragFloatActionButton>


</LinearLayout>

 MainActivity

package com.example.mychartdemo

import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.PopupWindow
import android.widget.RadioGroup
import androidx.appcompat.app.AppCompatActivity
import com.example.mychartdemo.databinding.ActivityMain3Binding

class MainActivity : AppCompatActivity(),View.OnClickListener{

    private  var popupWindow: PopupWindow? = null

    private lateinit var binding: ActivityMain3Binding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMain3Binding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.imgBtn.setOnClickListener {
         
            if (popupWindow == null) {
                initPopWindow()
                //設定不可拖動
                binding.imgBtn.isCanMove = false
            } else if (popupWindow != null && popupWindow?.isShowing == true) {
                popupWindow?.dismiss()
                popupWindow = null
                //設定可拖動
                binding.imgBtn.isCanMove = true
                
            }

        }


    }

    private fun initPopWindow() {
        var view: View? = null
        if(binding.imgBtn.isRight){
           view = View.inflate(this, R.layout.popwindow_view, null)
        }else{
            view = View.inflate(this, R.layout.popwindow_left_view, null)
        }

        val popimg_1: ImageView = view.findViewById<ImageView>(R.id.popimg_1)
        popimg_1.setOnClickListener(this)
        val popimg_2: ImageView = view.findViewById<ImageView>(R.id.popimg_2)
        popimg_2.setOnClickListener(this)
        val popimg_3: ImageView = view.findViewById<ImageView>(R.id.popimg_3)
        popimg_3.setOnClickListener(this)
        popupWindow = PopupWindow(
            view,
            DensityUtil.dip2px(this, 160F),
            RadioGroup.LayoutParams.WRAP_CONTENT
        )
        popupWindow?.setTouchable(true) //設定可以點選
        popupWindow?.setOutsideTouchable(false) //點選外部關閉

        // popupWindow.setBackgroundDrawable(new ColorDrawable());//設定背景
        // popupWindow.setBackgroundDrawable(new ColorDrawable());//設定背景
        popupWindow?.setBackgroundDrawable(BitmapDrawable())
        if (binding.imgBtn.isTop){
            Log.i("列印是否位於頂部111:",""+binding.imgBtn.isTop)
            popupWindow?.showAsDropDown(binding.imgBtn)
        }else{
            if(binding.imgBtn.isRight){
                Log.i("列印是否位於頂部222:",""+binding.imgBtn.isTop)
                popupWindow?.showAsDropDown(binding.imgBtn)
            }else{
                Log.i("列印是否位於頂部3333:",""+binding.imgBtn.isTop)
                popupWindow?.showAsDropDown(binding.imgBtn,0,DensityUtil.dip2px(this,-200F))
            }


        }


    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.popimg_1 -> {
                if (popupWindow != null) {
                    popupWindow?.dismiss()
                    popupWindow = null
                    //設定可拖動
                    binding.imgBtn.isCanMove = true
                  Log.i("點選了","111")
                }else{
                    Log.i("點選了","2222")
                }

            }
            R.id.popimg_2 -> {
                if (popupWindow != null) {
                    popupWindow?.dismiss()
                    popupWindow = null
                    //設定可拖動
                    binding.imgBtn.isCanMove = true
                }
            }
            R.id.popimg_3 -> {
                if (popupWindow != null) {
                    popupWindow?.dismiss()
                    popupWindow = null
                    //設定可拖動
                    binding.imgBtn.isCanMove = true
                }
            }
        }
    }
}

popwindow_view 佈局檔案

  

<?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"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:text="滿刻度"
            android:gravity="center|right"
            android:paddingRight="10dp"></TextView>
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/popimg_1"></ImageView>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:text="資料明細"
            android:gravity="center|right"
            android:paddingRight="10dp"></TextView>
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/popimg_2"></ImageView>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:text="選擇日期"
            android:gravity="center|right"
            android:paddingRight="10dp"></TextView>
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/popimg_3"></ImageView>
    </LinearLayout>

</LinearLayout>

  popwindow_left_view 左邊佈局檔案

<?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"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/popimg_1"></ImageView>
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:text="滿刻度"
            android:gravity="center|left"
            android:paddingLeft="10dp"></TextView>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/popimg_2"></ImageView>
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:text="資料明細"
            android:gravity="center|left"
            android:paddingLeft="10dp"></TextView>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/popimg_3"></ImageView>
        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:text="選擇日期"
            android:gravity="center|left"
            android:paddingLeft="10dp"></TextView>
    </LinearLayout>

</LinearLayout>

  完成

參考於:https://blog.csdn.net/qq_20451879/article/details/87876673