如圖:

實現思路
通過重寫控件的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
