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