Android事件沖突解決懸浮窗拖拽處理方案
需求場(chǎng)景
最近項(xiàng)目中要做一個(gè)音樂(lè)播放懸浮按鈕的功能,最終實(shí)現(xiàn)效果如下:

問(wèn)題暴露
懸浮窗布局文件就不放了,就是水平LinearLayout里面放幾個(gè)ImageView。
做的過(guò)程當(dāng)中遇到一個(gè)問(wèn)題,就是懸浮窗是可以任意拖拽的,懸浮窗里面的按鈕是可以點(diǎn)擊的,比如暫停,下一曲,關(guān)閉懸浮窗等。
按常規(guī)思路,先給整個(gè)懸浮窗setOnTouchListener(),然后再給你里面的按鈕setOnClickListener(),點(diǎn)擊運(yùn)行,結(jié)果發(fā)現(xiàn),點(diǎn)擊事件是可以響應(yīng),拖拽也沒(méi)問(wèn)題,但是當(dāng)手指放在ImageView上拖拽時(shí),onTouchListener事件無(wú)法響應(yīng)。
此時(shí)第一感覺(jué)就是setOnTouchListener()和setOnClickListener()沖突了,需要解決一下沖突。無(wú)奈自己對(duì)Android事件分發(fā)消費(fèi)機(jī)制一直都是一知半解的,一般都是出了問(wèn)題需要解決,第一時(shí)間先百度,沒(méi)有解決方案就只能去研究Android事件分發(fā)消費(fèi)機(jī)制了,但是研究完也都是懵懵懂懂的,今天就決定把這個(gè)難點(diǎn)徹底消化掉。
主要研究了這篇文章Android事件分發(fā)消費(fèi)機(jī)制,然后對(duì)照著寫(xiě)了個(gè)demo,一一去驗(yàn)證,加深了自己的理解,最后終于解決了我的問(wèn)題。
解決思路
先說(shuō)下解決思路,自定義LinearLayout,當(dāng)手指處于滑動(dòng)時(shí),直接攔截事件,交給自己的onTouchEvent處理即可,核心代碼如下:
/**
* @author:Jason
* @date:2021/8/24 19:49
* @description:可拖拽的LinearLayout,解決子View設(shè)置OnClickListener之后無(wú)法拖拽的問(wèn)題
*/
class DraggerbleLinearLayout : LinearLayout {
constructor(context: Context, attr: AttributeSet) : this(context, attr, 0)
constructor(context: Context, attr: AttributeSet, defStyleAttr: Int) : this(context, attr, defStyleAttr, 0)
constructor(context: Context, attr: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attr, defStyleAttr, defStyleRes)
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_MOVE -> {
return true
}
}
return super.onInterceptTouchEvent(ev)
}
}
很簡(jiǎn)單,就是在onInterceptTouchEvent()里面攔截move事件即可,這里你也可以重寫(xiě)onTouchEvent(),在里面實(shí)現(xiàn)拖拽功能,但是這樣就固定死了,所以我選擇在外面setOnTouchListener(),需要拖拽功能時(shí)才去實(shí)現(xiàn)
/**
* 創(chuàng)建懸浮窗
*/
@SuppressLint("ClickableViewAccessibility")
private fun showWindow() {
//獲取WindowManager
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
val outMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(outMetrics)
var layoutParam = WindowManager.LayoutParams().apply {
/**
* 設(shè)置type 這里進(jìn)行了兼容
*/
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
format = PixelFormat.RGBA_8888
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
//位置大小設(shè)置
width = WRAP_CONTENT
height = WRAP_CONTENT
gravity = Gravity.LEFT or Gravity.TOP
//設(shè)置劇中屏幕顯示
x = outMetrics.widthPixels / 2 - width / 2
y = outMetrics.heightPixels / 2 - height / 2
}
//在這里設(shè)置觸摸監(jiān)聽(tīng),以實(shí)現(xiàn)拖拽功能
floatRootView?.setOnTouchListener(ItemViewTouchListener(layoutParam, windowManager))
// 將懸浮窗控件添加到WindowManager
windowManager.addView(floatRootView, layoutParam)
isAdded = true
}
主要是這行代碼
//在這里設(shè)置觸摸監(jiān)聽(tīng),以實(shí)現(xiàn)拖拽功能 floatRootView?.setOnTouchListener(ItemViewTouchListener(layoutParam, windowManager))
ItemViewTouchListener.kt文件內(nèi)容
/**
* @author:Jason
* @date: 2021/8/23 19:27
* @description:
*/
class ItemViewTouchListener(val layoutParams: WindowManager.LayoutParams, val windowManager: WindowManager) : View.OnTouchListener {
private var lastX = 0.0f
private var lastY = 0.0f
override fun onTouch(view: View, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
//這里接收不到Down事件,不要在這里寫(xiě)邏輯
}
MotionEvent.ACTION_MOVE -> {
//重寫(xiě)LinearLayout的OnInterceptTouchEvent之后,這里的Down事件會(huì)接收不到,所以初始位置需要在Move事件里賦值
if (lastX == 0.0f || lastY == 0.0f) {
lastX = event.rawX
lastY = event.rawY
}
val nowX: Float = event.rawX
val nowY: Float = event.rawY
val movedX: Float = nowX - lastX
val movedY: Float = nowY - lastY
layoutParams.apply {
x += movedX.toInt()
y += movedY.toInt()
}
//更新懸浮球控件位置
windowManager?.updateViewLayout(view, layoutParams)
lastX = nowX
lastY = nowY
}
MotionEvent.ACTION_UP -> {
lastX = 0.0f
lastY = 0.0f
}
}
return true
}
}
這里有一點(diǎn)需要注意的是,重寫(xiě)了LinearLayout的onInterceptTouchEvent()后會(huì)導(dǎo)致setOnTouchListener()里面的ACTION_DOWN事件接收不到,所以不要在down事件里面寫(xiě)邏輯。然后onTouch一定要返回true,表示要消費(fèi)事件,否則當(dāng)拖拽非ImageView區(qū)域時(shí)會(huì)拖不動(dòng)。
好了,花了一整天,就解決了這個(gè)小問(wèn)題。
以上就是Android事件沖突解決懸浮窗拖拽處理方案的詳細(xì)內(nèi)容,更多關(guān)于Android事件沖突懸浮窗拖拽的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android仿百度谷歌搜索自動(dòng)提示框AutoCompleteTextView簡(jiǎn)單應(yīng)用示例
這篇文章主要介紹了Android仿百度谷歌搜索自動(dòng)提示框AutoCompleteTextView簡(jiǎn)單應(yīng)用,結(jié)合實(shí)例形式分析了AutoCompleteTextView Widget使用步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-10-10
Android編程實(shí)現(xiàn)隱藏狀態(tài)欄及測(cè)試Activity是否活動(dòng)的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)隱藏狀態(tài)欄及測(cè)試Activity是否活動(dòng)的方法,涉及Android界面布局設(shè)置及Activity狀態(tài)操作的相關(guān)技巧,需要的朋友可以參考下2016-10-10
Android中ExpandableListView使用示例詳解
這篇文章主要為大家詳細(xì)介紹了Android中ExpandableListView使用示例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Kotlin Extension Function擴(kuò)展函數(shù)詳細(xì)介紹
Kotlin支持使用新功能擴(kuò)展類(lèi)的能力,而無(wú)需通過(guò)類(lèi)實(shí)現(xiàn)繼承概念或使用設(shè)計(jì)模式,如裝飾器(Decorator)。這是通過(guò)稱(chēng)為擴(kuò)展功能(Extension Function)的特殊方式來(lái)完成的。因此,此功能可以有效地使代碼變得更清晰和易于閱讀,并且還可以減少代碼2023-02-02
Android網(wǎng)絡(luò)判斷知識(shí)小結(jié)
本文通過(guò)兩段實(shí)例代碼分別給大家介紹Android中判斷當(dāng)前網(wǎng)絡(luò)是否可用和Android 關(guān)于判斷應(yīng)用是否有網(wǎng)絡(luò)的相關(guān)知識(shí),對(duì)android網(wǎng)絡(luò)判斷相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2015-12-12
簡(jiǎn)單仿寫(xiě)Android控件SlidingMenu的實(shí)例代碼
下面小編就為大家分享一篇簡(jiǎn)單仿寫(xiě)Android控件SlidingMenu的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
android圖像繪制(七)ClipRect局部繪圖/切割原圖繪制總結(jié)
這幾天開(kāi)始學(xué)游戲地圖制作,今天小小的總結(jié)一下Canvas的clipRect()接口的使用,接下來(lái)介紹ClipRect局部繪圖/切割原圖繪制感興趣的朋友可以了解下2013-01-01

