最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

如何自己實(shí)現(xiàn)Android View Touch事件分發(fā)流程

 更新時(shí)間:2021年03月26日 09:59:21   作者:CAZ  
這篇文章主要介紹了如何自己實(shí)現(xiàn)Android View Touch事件分發(fā)流程,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下

Android Touch事件分發(fā)是Android UI中的重要內(nèi)容,Touch事件從驅(qū)動(dòng)層向上,經(jīng)過(guò)InputManagerService,WindowManagerService,ViewRootImpl,Window,到達(dá)DecorView,經(jīng)View樹(shù)分發(fā),最終被消費(fèi)。

本文嘗試通過(guò)對(duì)其中View部分的事件分發(fā),也是與日常開(kāi)發(fā)聯(lián)系最緊密的部分,進(jìn)行重寫(xiě)。說(shuō)是重寫(xiě),其實(shí)是對(duì)Android該部分源碼進(jìn)行大幅精簡(jiǎn)而不失要點(diǎn),且能夠獨(dú)立運(yùn)行,以一窺其全貌,而不陷入到源碼繁雜的細(xì)節(jié)中。

以下類均為自定義類,而非Android同名原生類。

MotionEvent

class MotionEvent {
 companion object {
  const val ACTION_DOWN = 0
  const val ACTION_MOVE = 1
  const val ACTION_UP = 2
  const val ACTION_CANCEL = 3
 }
 var x = 0
 var y = 0
 var action = 0
 override fun toString(): String {
  return "MotionEvent(x=$x, y=$y, action=$action)"
 }
}

首先定義MotionEvent,這里將觸摸事件action減少為最常用的4種,同時(shí)只支持單指操作,因此action取值僅支持4個(gè)常量。并且為了簡(jiǎn)化后續(xù)的位置計(jì)算,x和y表示的是絕對(duì)坐標(biāo)(相當(dāng)于getRawX()與getRawY()),而非相對(duì)坐標(biāo)。

View

open class View {
 var left = 0
 var right = 0
 var top = 0
 var bottom = 0//1

 var enable = true
 var clickable = false
 var onTouch: ((View, MotionEvent) -> Boolean)? = null
 var onClick: ((View) -> Unit)? = null//3
  set(value) {
   field = value
   clickable = true
  }

 private var downed = false

 open fun layout(l: Int, t: Int, r: Int, b: Int) {
  left = l
  top = t
  right = r
  bottom = b
 }//2

 open fun onTouchEvent(ev: MotionEvent): Boolean {
  var handled: Boolean
  if (enable && clickable) {
   when (ev.action) {
    MotionEvent.ACTION_DOWN -> {
     downed = true
    }
    MotionEvent.ACTION_UP -> {
     if (downed && ev.inView(this)) {//7
      downed = false
      onClick?.invoke(this)
     }
    }
    MotionEvent.ACTION_MOVE -> {
     if (!ev.inView(this)) {//7
      downed = false
     }
    }
    MotionEvent.ACTION_CANCEL -> {
     downed = false
    }
   }
   handled = true
  } else {
   handled = false
  }
  return handled
 }//5

 open fun dispatchTouchEvent(ev: MotionEvent): Boolean {
  var result = false
  if (onTouch != null && enable) {
   result = onTouch!!.invoke(this, ev)
  }
  if (!result && onTouchEvent(ev)) {
   result = true
  }
  return result
 }//4
}
fun MotionEvent.inView(v: View) = v.left <= x && x <= v.right && v.top <= y && y <= v.bottom//6

接下來(lái)定義View。(1)定義了View的位置,這里同樣表示絕對(duì)坐標(biāo),而不是相對(duì)于父View的位置。(2)同時(shí)使用layout方法傳遞位置,因?yàn)槲覀兊闹攸c(diǎn)是View的事件分發(fā)而不是其布局與繪制,因此只定義了layout。(3)觸摸回調(diào)這里直接使用函數(shù)類型定義,(4)dispatchTouchEvent先處理了onTouch回調(diào),如果未回調(diào),則調(diào)用onTouchEvent,可見(jiàn)二者的優(yōu)先級(jí)。(5)onTouchEvent則主要處理了onClick回調(diào),雖然真實(shí)源碼中對(duì)點(diǎn)擊的判斷更為復(fù)雜,但實(shí)際效果與此處是一致的,(6)使用擴(kuò)展函數(shù)來(lái)確定事件是否發(fā)生在View內(nèi)部,(7)兩處調(diào)用配合downed標(biāo)記確保ACTION_MOVE與ACTION_UP發(fā)生在View內(nèi)才被識(shí)別為點(diǎn)擊。至于長(zhǎng)按等其他手勢(shì)的監(jiān)聽(tīng),因?yàn)檩^為繁瑣,這里就不再實(shí)現(xiàn)。

ViewGroup

open class ViewGroup(private vararg val children: View) : View() {//1
 private var mFirstTouchTarget: View? = null

 open fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
  return false
 }//2

 override fun dispatchTouchEvent(ev: MotionEvent): Boolean {//3
  val intercepted: Boolean
  var handled = false

  if (ev.action == MotionEvent.ACTION_DOWN) {
   mFirstTouchTarget = null
  }//4
  if (ev.action == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
   intercepted = onInterceptTouchEvent(ev)//5
  } else {
   intercepted = true//6
  }

  val canceled = ev.action == MotionEvent.ACTION_CANCEL
  var alreadyDispatchedToNewTouchTarget = false
  if (!intercepted) {
   if (ev.action == MotionEvent.ACTION_DOWN) {//7
    for (child in children.reversed()) {//8
     if (ev.inView(child)) {//9
      if (dispatchTransformedTouchEvent(ev, false, child)) {//10
       mFirstTouchTarget = child
       alreadyDispatchedToNewTouchTarget = true//12
      }
      break
     }
    }
   }
  }

  if (mFirstTouchTarget == null) {
   handled = dispatchTransformedTouchEvent(ev, canceled, null)//17
  } else {
   if (alreadyDispatchedToNewTouchTarget) {//13
    handled = true
   } else {
    val cancelChild = canceled || intercepted//14
    if (dispatchTransformedTouchEvent(ev, cancelChild, mFirstTouchTarget)) {
     handled = true
    }
    if (cancelChild) {
     mFirstTouchTarget = null//16
    }
   }
  }

  if (canceled || ev.action == MotionEvent.ACTION_UP) {
   mFirstTouchTarget = null
  }//4
  return handled
 }

 private fun dispatchTransformedTouchEvent(ev: MotionEvent, cancel: Boolean, child: View?): Boolean {
  if (cancel) {
   ev.action = MotionEvent.ACTION_CANCEL//15
  }
  val oldAction = ev.action
  val handled = if (child == null) {
   super.dispatchTouchEvent(ev)//18
  } else {
   child.dispatchTouchEvent(ev)//11
  }
  ev.action = oldAction
  return handled
 }
}

最后來(lái)實(shí)現(xiàn)ViewGroup:(1)子View這里通過(guò)構(gòu)造函數(shù)傳入, 而不再提供addView等方法,(2)onInterceptTouchEvent簡(jiǎn)單返回false,主要通過(guò)子類繼承來(lái)修改返回,(3)dispatchTouchEvent是整個(gè)實(shí)現(xiàn)中最主要的邏輯,來(lái)詳細(xì)解釋,這里的實(shí)現(xiàn)只包含對(duì)單指Touch事件的處理,并且不包含requestDisallowInterceptTouchEvent的情況。

(4)源碼中開(kāi)頭和結(jié)尾處有清理字段與標(biāo)記的方法,用于在一個(gè)事件序列(由ACTION_DOWN開(kāi)始,經(jīng)過(guò)若干ACTION_MOVE等,最終以ACTION_UP結(jié)束,即整個(gè)觸摸過(guò)程)開(kāi)頭和結(jié)束時(shí)清理舊數(shù)據(jù),這里簡(jiǎn)化為了將我們類中的唯一字段mFirstTouchTarget(表示整個(gè)事件序列的目標(biāo)視圖,在源碼中,此變量類型為TouchTarget,實(shí)現(xiàn)為一個(gè)View的鏈表節(jié)點(diǎn),以此來(lái)支持多指觸摸,這里簡(jiǎn)化為View)置空。

接下來(lái)將該方法分為幾部分來(lái)介紹:

事件攔截

(5)表示在一個(gè)事件序列的開(kāi)始或者已經(jīng)找到了目標(biāo)視圖的情況下,才需要調(diào)用onInterceptTouchEvent判斷本ViewGroup是否攔截事件。(6)表示如果ACTION_DOWN沒(méi)有視圖消費(fèi),則之后的事件將被攔截,且攔截的View是View樹(shù)中的頂層View,即Android中的DecorView。

尋找目標(biāo)視圖,分發(fā)ACTION_DOWN

(7)當(dāng)ACTION_DOWN事件未被攔截,(8)則反向遍歷子View數(shù)組,(9)尋找ACTION_DOWN事件落在其中的View,(10)并將ACTION_DOWN事件傳遞給該子View,這一步調(diào)用了dispatchTransformedTouchEvent,該方法將源碼中的方法簡(jiǎn)化為了三參數(shù),方法名中的Transformed表示,會(huì)將Touch事件進(jìn)行坐標(biāo)系的變換,而這里為了簡(jiǎn)化使用的坐標(biāo)是絕對(duì)的,因此不需要變換。此時(shí)會(huì)調(diào)用dispatchTransformedTouchEvent中(11)處向子View分發(fā)ACTION_DOWN,child即mFirstTouchTarget。

分發(fā)除ACTION_DOWN外的其他事件

(12)對(duì)于ACTION_DOWN事件,會(huì)將alreadyDispatchedToNewTouchTarget置位,(13)此時(shí)會(huì)會(huì)進(jìn)入if塊,而非ACTION_DOWN事件會(huì)進(jìn)入else塊。(14)當(dāng)該事件是ACTION_CANCEL或者事件被攔截,則在調(diào)用dispatchTransformedTouchEvent的(15)處后,將事件修改為ACTION_CANCEL,然后調(diào)用(11),將ACTION_CANCEL分發(fā)給子View,(16)同時(shí)將mFirstTouchTarget置空。當(dāng)事件序列中的下個(gè)事件到來(lái)時(shí),會(huì)進(jìn)入(17)處,即最終調(diào)用(18),調(diào)用上節(jié)中View的事件處理,即ViewGroup消費(fèi)該事件,消費(fèi)該事件的ViewGroup即攔截了非ACTION_DOWN事件并向子View分發(fā)ACTION_CANCEL的ViewGroup。

使用

至此,實(shí)現(xiàn)了MotionEvent,View,與ViewGroup,來(lái)進(jìn)行一下驗(yàn)證。

定義三個(gè)子類:

class VG1(vararg children: View) : ViewGroup(*children)
class VG2(vararg children: View) : ViewGroup(*children)
class V : View() {
 override fun onTouchEvent(ev: MotionEvent): Boolean {
  println("V onTouchEvent $ev")
  return super.onTouchEvent(ev)
 }

 override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
  println("V dispatchTouchEvent $ev")
  return super.dispatchTouchEvent(ev)
 }
}

定義一個(gè)事件發(fā)生方法,由該方法來(lái)模擬Touch事件的軌跡與action:

fun produceEvents(startX: Int, startY: Int, endX: Int, endY: Int, stepNum: Int): List<MotionEvent> {
 val list = arrayListOf<MotionEvent>()
 val stepX = (endX - startX) / stepNum
 val stepY = (endY - startY) / stepNum
 for (i in 0..stepNum) {
  when (i) {
   0 -> {
    list.add(MotionEvent().apply {
     action = MotionEvent.ACTION_DOWN
     x = startX
     y = startY
    })
   }
   stepNum -> {
    list.add(MotionEvent().apply {
     action = MotionEvent.ACTION_UP
     x = endX
     y = endY
    })
   }
   else -> {
    list.add(MotionEvent().apply {
     action = MotionEvent.ACTION_MOVE
     x = stepX * i + startX
     y = stepY * i + startY
    })
   }
  }
 }
 return list
}

接下來(lái)就可以驗(yàn)證了,在Android中事件由驅(qū)動(dòng)層一步步傳遞至View樹(shù)的頂端,這里我們定義一個(gè)三層的布局page,(1)直接將事件序列遍歷調(diào)用頂層ViewGroup的dispatchTouchEvent來(lái)開(kāi)啟事件分發(fā)。

fun main() {
 val page = VG1(
  VG2(
   V().apply { layout(0, 0, 100, 100); onClick = { println("Click in V") } }//2
  ).apply { layout(0, 0, 200, 200) }
 ).apply { layout(0, 0, 300, 300) }//3

 val events = produceEvents(50, 50, 90, 90, 5)
 events.forEach {
  page.dispatchTouchEvent(it)//1
 }
}

程序可以正常執(zhí)行,打印如下:

V dispatchTouchEvent MotionEvent(x=50, y=50, action=0)
V onTouchEvent MotionEvent(x=50, y=50, action=0)
V dispatchTouchEvent MotionEvent(x=58, y=58, action=1)
V onTouchEvent MotionEvent(x=58, y=58, action=1)
V dispatchTouchEvent MotionEvent(x=66, y=66, action=1)
V onTouchEvent MotionEvent(x=66, y=66, action=1)
V dispatchTouchEvent MotionEvent(x=74, y=74, action=1)
V onTouchEvent MotionEvent(x=74, y=74, action=1)
V dispatchTouchEvent MotionEvent(x=82, y=82, action=1)
V onTouchEvent MotionEvent(x=82, y=82, action=1)
V dispatchTouchEvent MotionEvent(x=90, y=90, action=2)
V onTouchEvent MotionEvent(x=90, y=90, action=2)
Click in V

因?yàn)槲覀冊(cè)冢?)增加了點(diǎn)擊事件,以上表示了一次點(diǎn)擊的事件分發(fā)。也可以重寫(xiě)修改page布局(3)來(lái)查看其它情景下的事件分發(fā)流程,或者重寫(xiě)VG1,VG2的方法,增加打印并查看。

總結(jié)

通過(guò)對(duì)Android 源碼的整理,用約150行代碼就能實(shí)現(xiàn)了一個(gè)簡(jiǎn)化版的Android Touch View事件分發(fā),雖然為了代碼結(jié)構(gòu)的簡(jiǎn)潔舍棄了部分功能,但整個(gè)流程與Android Touch View事件分發(fā)是一致的,能夠更方便理解這套機(jī)制。

以上就是如何自己實(shí)現(xiàn)Android View Touch事件分發(fā)流程的詳細(xì)內(nèi)容,更多關(guān)于實(shí)現(xiàn)Android View Touch事件分發(fā)流程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • android 全屏去掉底部虛擬導(dǎo)航欄的方法

    android 全屏去掉底部虛擬導(dǎo)航欄的方法

    今天小編就為大家分享一篇android 全屏去掉底部虛擬導(dǎo)航欄的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • Android CameraX打開(kāi)攝像頭預(yù)覽教程

    Android CameraX打開(kāi)攝像頭預(yù)覽教程

    大家好,本篇文章主要講的是Android CameraX打開(kāi)攝像頭預(yù)覽教程,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下
    2021-12-12
  • so加載Linker跟NameSpace機(jī)制詳解

    so加載Linker跟NameSpace機(jī)制詳解

    這篇文章主要為大家介紹了so加載Linker跟NameSpace機(jī)制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Android實(shí)現(xiàn)用戶登錄記住密碼功能

    Android實(shí)現(xiàn)用戶登錄記住密碼功能

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)用戶登錄記住密碼功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Android自定義View實(shí)現(xiàn)角度選擇器

    Android自定義View實(shí)現(xiàn)角度選擇器

    前幾天在Google Photos查看照片,用了一下它的圖片剪裁功能,于是我馬上就被其界面和操作吸引。后來(lái)想模仿做一個(gè)和Google Photos裁圖頁(yè)面幾乎一模一樣的角度選擇器,本文比較基礎(chǔ),在閱讀本文前只需要掌握最基礎(chǔ)的自定義View知識(shí)和Android事件知識(shí)。下面來(lái)一起學(xué)習(xí)下吧。
    2016-11-11
  • Android的HTTP多線程下載示例代碼

    Android的HTTP多線程下載示例代碼

    本篇文章主要介紹了Android的HTTP多線程下載示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-03-03
  • 基于Android實(shí)現(xiàn)計(jì)算器計(jì)算功能

    基于Android實(shí)現(xiàn)計(jì)算器計(jì)算功能

    這篇文章主要介紹了基于Android實(shí)現(xiàn)計(jì)算器計(jì)算功能,文章通過(guò)代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-06-06
  • Android設(shè)置默認(rèn)鎖屏壁紙接口的方法

    Android設(shè)置默認(rèn)鎖屏壁紙接口的方法

    這篇文章主要介紹了Android默認(rèn)鎖屏壁紙接口的設(shè)置方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Android清空編輯框內(nèi)容功能的實(shí)現(xiàn)實(shí)例代碼

    Android清空編輯框內(nèi)容功能的實(shí)現(xiàn)實(shí)例代碼

    本篇文章主要介紹了Android清空編輯框數(shù)據(jù)功能的實(shí)現(xiàn)實(shí)例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。
    2017-03-03
  • 解決Android SearchView不顯示搜索icon的問(wèn)題

    解決Android SearchView不顯示搜索icon的問(wèn)題

    這篇文章主要介紹了解決Android SearchView不顯示搜索icon問(wèn)題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-05-05

最新評(píng)論

永善县| 汉阴县| 来安县| 雅江县| 乐平市| 略阳县| 汉沽区| 伊金霍洛旗| 清远市| 吉林省| 黎平县| 禄丰县| 平昌县| 黑水县| 加查县| 南宁市| 夏河县| 靖江市| 库伦旗| 甘肃省| 雅江县| 岱山县| 福海县| 泸西县| 石楼县| 南华县| 吉林省| 岑巩县| 阿鲁科尔沁旗| 招远市| 建阳市| 南丰县| 耒阳市| 开封市| 定边县| 凤阳县| 昭平县| 抚州市| 哈巴河县| 赣州市| 乐清市|