1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package androidx.compose.ui.platform
17 
18 import android.annotation.SuppressLint
19 import android.content.Context
20 import android.graphics.Canvas
21 import android.graphics.Rect
22 import android.view.MotionEvent
23 import android.view.View
24 import android.view.View.MeasureSpec.EXACTLY
25 import android.view.View.MeasureSpec.getMode
26 import android.view.ViewGroup
27 import androidx.compose.ui.internal.requirePrecondition
28 import androidx.compose.ui.node.LayoutNode
29 import androidx.compose.ui.viewinterop.AndroidViewHolder
30 
31 /**
32  * Used by [AndroidComposeView] to handle the Android [View]s attached to its hierarchy. The
33  * [AndroidComposeView] has one direct [AndroidViewsHandler], which is responsible of intercepting
34  * [requestLayout]s, [onMeasure]s, [invalidate]s, etc. sent from or towards children.
35  */
36 internal class AndroidViewsHandler(context: Context) : ViewGroup(context) {
37     init {
38         clipChildren = false
39     }
40 
41     val holderToLayoutNode = hashMapOf<AndroidViewHolder, LayoutNode>()
42     val layoutNodeToHolder = hashMapOf<LayoutNode, AndroidViewHolder>()
43 
onMeasurenull44     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
45         // Layout will be handled by component nodes. However, we act like proper measurement
46         // here in case ViewRootImpl did forceLayout().
47         requirePrecondition(getMode(widthMeasureSpec) == EXACTLY) {
48             "widthMeasureSpec should be EXACTLY"
49         }
50         requirePrecondition(getMode(heightMeasureSpec) == EXACTLY) {
51             "heightMeasureSpec should be EXACTLY"
52         }
53         setMeasuredDimension(
54             MeasureSpec.getSize(widthMeasureSpec),
55             MeasureSpec.getSize(heightMeasureSpec)
56         )
57         // Remeasure children, such that, if ViewRootImpl did forceLayout(), the holders
58         // will be set PFLAG_LAYOUT_REQUIRED and they will be relaid out during the next layout.
59         // This will ensure that the need relayout flags will be cleared correctly.
60         holderToLayoutNode.keys.forEach { it.remeasure() }
61     }
62 
onLayoutnull63     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
64         // Layout was already handled by component nodes, but replace here because
65         // the View system has forced relayout on children. This method will only be called
66         // when forceLayout is called on the Views hierarchy.
67         holderToLayoutNode.keys.forEach { it.layout(it.left, it.top, it.right, it.bottom) }
68     }
69 
70     // No call to super to avoid invalidating the AndroidComposeView and the handler, and rely on
71     // component nodes logic. The layer invalidation will have been already done by the holder.
72     @SuppressLint("MissingSuperCall")
onDescendantInvalidatednull73     override fun onDescendantInvalidated(child: View, target: View) {}
74 
75     @Suppress("OVERRIDE_DEPRECATION") // b/407491706
invalidateChildInParentnull76     override fun invalidateChildInParent(location: IntArray?, dirty: Rect?) = null
77 
78     fun drawView(view: AndroidViewHolder, canvas: Canvas) {
79         view.draw(canvas)
80     }
81 
82     // Touch events forwarding will be handled by component nodes.
dispatchTouchEventnull83     override fun dispatchTouchEvent(ev: MotionEvent?) = true
84 
85     // No call to super to avoid invalidating the AndroidComposeView and rely on
86     // component nodes logic.
87     @SuppressLint("MissingSuperCall")
88     override fun requestLayout() {
89         // Hack to cleanup the dirty layout flag on ourselves, such that this method continues
90         // to be called for further children requestLayout().
91         cleanupLayoutState(this)
92         // requestLayout() was called by a child, so we have to request remeasurement for
93         // their corresponding layout node.
94         for (i in 0 until childCount) {
95             val child = getChildAt(i)
96             val node = holderToLayoutNode[child]
97             if (child.isLayoutRequested && node != null) {
98                 node.requestRemeasure()
99             }
100         }
101     }
102 
shouldDelayChildPressedStatenull103     override fun shouldDelayChildPressedState(): Boolean = false
104 
105     // We don't want the AndroidComposeView drawing the holder and its children. All draw
106     // calls should come through AndroidViewHolder or ViewLayer.
107     override fun dispatchDraw(canvas: Canvas) {}
108 }
109