@@ -3,13 +3,17 @@ package com.didi.dimina.ui.view
33import android.content.res.ColorStateList
44import android.graphics.Color
55import android.graphics.Typeface
6+ import android.graphics.drawable.Drawable
67import android.graphics.drawable.GradientDrawable
78import android.media.MediaPlayer
89import android.net.Uri
910import android.os.Handler
1011import android.os.Looper
12+ import android.os.SystemClock
1113import android.text.TextUtils
1214import android.view.Gravity
15+ import android.view.InputDevice
16+ import android.view.MotionEvent
1317import android.view.View
1418import android.webkit.WebView
1519import android.widget.FrameLayout
@@ -30,11 +34,12 @@ import kotlinx.coroutines.SupervisorJob
3034import kotlinx.coroutines.cancel
3135import kotlinx.coroutines.launch
3236import kotlinx.coroutines.withContext
37+ import org.json.JSONArray
3338import org.json.JSONObject
3439import kotlin.math.roundToInt
3540
3641/* *
37- * Hosts native components as siblings of WebView and keeps their bounds aligned
42+ * Hosts native components behind the WebView and keeps their bounds aligned
3843 * with DOM placeholders reported from the render layer.
3944 */
4045class NativeComponentHost (
@@ -43,7 +48,10 @@ class NativeComponentHost(
4348 private val overlay : FrameLayout ,
4449) {
4550 private val components = mutableMapOf<String , NativeComponent >()
51+ private val touchDownTimes = mutableMapOf<String , Long >()
52+ private val originalWebViewBackground: Drawable ? = webView.background
4653 private val imageScope = CoroutineScope (SupervisorJob () + Dispatchers .Main .immediate)
54+ private var webViewTransparent = false
4755
4856 init {
4957 webView.setOnScrollChangeListener { _, _, _, _, _ ->
@@ -75,16 +83,140 @@ class NativeComponentHost(
7583 return true
7684 }
7785
86+ fun dispatchTouchFromWeb (params : JSONObject ): Boolean {
87+ val targetId = params.optString(" targetId" )
88+ if (targetId.isEmpty()) {
89+ return false
90+ }
91+ activity.runOnUiThread {
92+ dispatchNativeTouch(params)
93+ }
94+ return true
95+ }
96+
7897 fun clear () {
7998 activity.runOnUiThread {
8099 webView.setOnScrollChangeListener(null )
81100 components.values.forEach { it.release() }
82101 components.clear()
102+ touchDownTimes.clear()
103+ restoreWebViewBackground()
83104 imageScope.cancel()
84105 overlay.removeAllViews()
85106 }
86107 }
87108
109+ private fun dispatchNativeTouch (params : JSONObject ) {
110+ val targetId = params.optString(" targetId" )
111+ val component = components[targetId] ? : return
112+ val targetView = component.view
113+ if (targetView.visibility != View .VISIBLE ) {
114+ return
115+ }
116+
117+ val actionName = params.optString(" action" )
118+ val now = SystemClock .uptimeMillis()
119+ if (actionName == TOUCH_ACTION_DOWN ) {
120+ touchDownTimes[targetId] = now
121+ }
122+ val downTime = touchDownTimes[targetId] ? : now.also {
123+ touchDownTimes[targetId] = it
124+ }
125+
126+ val event = createMotionEvent(params, targetView, downTime, now) ? : return
127+ try {
128+ targetView.dispatchTouchEvent(event)
129+ } finally {
130+ event.recycle()
131+ }
132+
133+ if (actionName == TOUCH_ACTION_UP || actionName == TOUCH_ACTION_CANCEL ) {
134+ touchDownTimes.remove(targetId)
135+ }
136+ }
137+
138+ private fun createMotionEvent (
139+ params : JSONObject ,
140+ targetView : View ,
141+ downTime : Long ,
142+ eventTime : Long ,
143+ ): MotionEvent ? {
144+ val pointers = params.optJSONArray(" pointers" ) ? : return null
145+ if (pointers.length() == 0 ) {
146+ return null
147+ }
148+
149+ val actionPointerId = params.optInt(" actionPointerId" , - 1 )
150+ val actionPointerIndex = findPointerIndex(pointers, actionPointerId)
151+ val action = when (params.optString(" action" )) {
152+ TOUCH_ACTION_DOWN -> MotionEvent .ACTION_DOWN
153+ TOUCH_ACTION_POINTER_DOWN -> MotionEvent .ACTION_POINTER_DOWN or
154+ (actionPointerIndex.coerceAtLeast(0 ) shl MotionEvent .ACTION_POINTER_INDEX_SHIFT )
155+ TOUCH_ACTION_MOVE -> MotionEvent .ACTION_MOVE
156+ TOUCH_ACTION_POINTER_UP -> MotionEvent .ACTION_POINTER_UP or
157+ (actionPointerIndex.coerceAtLeast(0 ) shl MotionEvent .ACTION_POINTER_INDEX_SHIFT )
158+ TOUCH_ACTION_UP -> MotionEvent .ACTION_UP
159+ TOUCH_ACTION_CANCEL -> MotionEvent .ACTION_CANCEL
160+ else -> return null
161+ }
162+
163+ val viewportWidth = params.optDouble(" viewportWidth" , 0.0 )
164+ val viewportHeight = params.optDouble(" viewportHeight" , 0.0 )
165+ val scaleX = if (viewportWidth > 0.0 && webView.width > 0 ) {
166+ webView.width / viewportWidth
167+ } else {
168+ 1.0
169+ }
170+ val scaleY = if (viewportHeight > 0.0 && webView.height > 0 ) {
171+ webView.height / viewportHeight
172+ } else {
173+ scaleX
174+ }
175+
176+ val pointerProperties = Array (pointers.length()) { index ->
177+ val pointer = pointers.getJSONObject(index)
178+ MotionEvent .PointerProperties ().apply {
179+ id = pointer.optInt(" id" , index)
180+ toolType = MotionEvent .TOOL_TYPE_FINGER
181+ }
182+ }
183+ val pointerCoords = Array (pointers.length()) { index ->
184+ val pointer = pointers.getJSONObject(index)
185+ MotionEvent .PointerCoords ().apply {
186+ x = (pointer.optDouble(" clientX" ) * scaleX - targetView.left).toFloat()
187+ y = (pointer.optDouble(" clientY" ) * scaleY - targetView.top).toFloat()
188+ pressure = 1f
189+ size = 1f
190+ }
191+ }
192+
193+ return MotionEvent .obtain(
194+ downTime,
195+ eventTime,
196+ action,
197+ pointers.length(),
198+ pointerProperties,
199+ pointerCoords,
200+ 0 ,
201+ 0 ,
202+ 1f ,
203+ 1f ,
204+ 0 ,
205+ 0 ,
206+ InputDevice .SOURCE_TOUCHSCREEN ,
207+ 0 ,
208+ )
209+ }
210+
211+ private fun findPointerIndex (pointers : JSONArray , pointerId : Int ): Int {
212+ for (index in 0 until pointers.length()) {
213+ if (pointers.getJSONObject(index).optInt(" id" , - 1 ) == pointerId) {
214+ return index
215+ }
216+ }
217+ return 0
218+ }
219+
88220 private fun mountComponent (type : String , id : String , params : JSONObject ) {
89221 val existing = components[id]
90222 if (existing != null && existing.type != type) {
@@ -94,17 +226,47 @@ class NativeComponentHost(
94226 createComponent(type, id).also { overlay.addView(it.view) }
95227 }
96228 component.update(params)
229+ updateWebViewBackgroundForNativeComponents()
97230 }
98231
99232 private fun updateComponent (type : String , id : String , params : JSONObject ) {
100- components[id]?.update(params) ? : mountComponent(type, id, params)
233+ components[id]?.let { component ->
234+ component.update(params)
235+ updateWebViewBackgroundForNativeComponents()
236+ } ? : mountComponent(type, id, params)
101237 }
102238
103239 private fun unmountComponent (id : String ) {
104240 components.remove(id)?.let { component ->
241+ touchDownTimes.remove(id)
105242 component.release()
106243 overlay.removeView(component.view)
244+ updateWebViewBackgroundForNativeComponents()
245+ }
246+ }
247+
248+ private fun updateWebViewBackgroundForNativeComponents () {
249+ val hasVisibleNativeComponent = components.values.any { component ->
250+ component.view.visibility == View .VISIBLE
251+ }
252+ if (hasVisibleNativeComponent && ! webViewTransparent) {
253+ webView.setBackgroundColor(Color .TRANSPARENT )
254+ webViewTransparent = true
255+ } else if (! hasVisibleNativeComponent && webViewTransparent) {
256+ restoreWebViewBackground()
257+ }
258+ }
259+
260+ private fun restoreWebViewBackground () {
261+ if (! webViewTransparent) {
262+ return
263+ }
264+ if (originalWebViewBackground != null ) {
265+ webView.background = originalWebViewBackground
266+ } else {
267+ webView.setBackgroundColor(Color .WHITE )
107268 }
269+ webViewTransparent = false
108270 }
109271
110272 private fun createComponent (type : String , id : String ): NativeComponent {
@@ -748,6 +910,12 @@ class NativeComponentHost(
748910 private const val COVER_IMAGE_TYPE = " native/cover-image"
749911 private const val TIME_UPDATE_INTERVAL_MS = 250L
750912 private const val FIRST_FRAME_SEEK_MS = 1
913+ private const val TOUCH_ACTION_DOWN = " down"
914+ private const val TOUCH_ACTION_POINTER_DOWN = " pointerDown"
915+ private const val TOUCH_ACTION_MOVE = " move"
916+ private const val TOUCH_ACTION_POINTER_UP = " pointerUp"
917+ private const val TOUCH_ACTION_UP = " up"
918+ private const val TOUCH_ACTION_CANCEL = " cancel"
751919 private val SUPPORTED_TYPES = setOf (VIDEO_TYPE , COVER_VIEW_TYPE , COVER_IMAGE_TYPE )
752920 }
753921
0 commit comments