Skip to content

Commit 62903bc

Browse files
qflenmeta-codesync[bot]
authored andcommitted
Fix skewX/skewY transforms on Android Q+ (#27649) (#56724)
Summary: Fixes #27649. On Android, `skewX` / `skewY` transforms are silently dropped during view-prop application: the matrix math layer correctly extracts the shear into `MatrixDecompositionContext.skew[]`, but `BaseViewManager.setTransformProperty` reads only `translation`, `rotationDegrees`, `scale`, and `perspective` from the decomposition context and never consumes the `skew[]` field. Views with `skew*` end up rendered as rotated-and-scaled rectangles instead of true parallelograms. This PR adds a single dispatch in `BaseViewManager.setTransformProperty`: when the transform array contains `skewX` / `skewY` and is otherwise 2D-affine, build a `Matrix` directly from the operations and apply it via `View.setAnimationMatrix` on Android Q+. All other transform shapes (`rotateX`, `rotateY`, `perspective`, raw 4x4 `matrix`, `translateZ`) continue to flow through the existing decompose-and-set-View-props code unchanged. ### Root cause `packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java` (the pre-fix range `~573-635`) iterates the decomposed `translation`, `rotationDegrees`, `scale`, and `perspective` fields onto the View. The `skew[]` field on `MatrixDecompositionContext`, computed correctly by `MatrixMathHelper.decomposeMatrix`, is never read. Android `View` exposes property setters for translation, rotation around pivot, scale, and camera distance, but no `setSkewX` / `setSkewY`, so there has historically been no application path for the residual shear. quantizor's trace in #27649 (comment) identified the exact site. ### Fix A new internal Kotlin helper `SkewMatrixHelper` (in `packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/SkewMatrixHelper.kt`) exposes two `JvmStatic` functions: - `isAffine2DTransformWithSkew(transforms)`, linear scan; returns true iff the array contains at least one `skewX` / `skewY` AND no operation that can't be represented by a 2D Skia `Matrix` (rejects `matrix`, `perspective`, `rotateX`, `rotateY`, `translate` with a non-zero Z component, and the raw 16-element matrix shorthand used by Fabric LayoutAnimations). - `buildAffine2DMatrix(transforms, viewWidthDip, viewHeightDip, transformOrigin)`, walks the array left-to-right and applies each operation to a `Matrix` via `preRotate` / `preScale` / `preSkew` / `preTranslate` around the resolved pivot. Composition is pre-multiplication so the rightmost array entry is applied first to the point, matching CSS / iOS conventions and `MatrixMathHelper.multiplyInto` in `TransformHelper.processTransform`. Reuses `TransformHelper.convertToRadians` / `TransformHelper.parseTranslateValue` so degree/radian/percent parsing stays in one place. `BaseViewManager.setTransformProperty` adds a guarded dispatch immediately after the `transforms == null` reset block: ```java if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && SkewMatrixHelper.isAffine2DTransformWithSkew(transforms)) { Matrix affine = SkewMatrixHelper.buildAffine2DMatrix( transforms, PixelUtil.toDIPFromPixel(view.getWidth()), PixelUtil.toDIPFromPixel(view.getHeight()), transformOrigin); view.setTranslationX(0); view.setTranslationY(0); view.setRotation(0); view.setRotationX(0); view.setRotationY(0); view.setScaleX(1); view.setScaleY(1); view.setCameraDistance(0); view.setAnimationMatrix(affine); view.setTag(R.id.skew_animation_matrix, affine); return; } ``` The `R.id.skew_animation_matrix` tag (declared in `ids.xml`) holds the affine `Matrix` itself. A small `clearSkewAnimationMatrixIfActive(view)` helper is invoked from the `transforms == null` branch and from the existing decompose-path tail, gating the `view.setAnimationMatrix(null)` call on the tag. Without this gate, every animated rotate / scale / translate frame on every View would fire `setAnimationMatrix(null)`, which unconditionally invalidates the RenderNode and would be a per-frame regression for non-skew animations. `View.getMatrix()` does not compose `mAnimationMatrix` into its return value, so the React-side hit-test traversal in `TouchTargetHelper.kt` would otherwise still see the original rectangular bounds. To close that gap, the `R.id.skew_animation_matrix` view tag stores the affine `Matrix` itself (rather than `Boolean.TRUE`), and `TouchTargetHelper.getChildPoint` checks the tag and uses it as the inverse-mapping matrix when present. Net effect: hit testing follows the rendered parallelogram on both platforms, matching iOS / `CATransform3D` behavior. Invalidation, layer caching, and accessibility-bounds reporting come for free from the existing `setAnimationMatrix` plumbing. ### Pre-Q Gated to API 29+ to mirror the existing `view.setAnimationMatrix(null)` cleanup at `BaseViewManager.java:118`. On API 24-28 (small and declining install share in 2026), skew continues to be silently dropped, matching today's behavior. AndroidX Transitions uses `setAnimationMatrix` via reflection on pre-Q; if that ever becomes a priority for skew on pre-Q, the same shim could be added here without touching the dispatch shape. ### Why not the prior attempts - **#28862 (`wcandillon`, May 2020), fixed a JS-side decomposition bug where `skew[0]` was zeroed by a duplicate orthogonalization. That fix landed via Phabricator (commit `797367c0890a38ec51cfaf7bd90b9cc7db0e97c7`) and is preserved in current `main`. It corrected `decomposeMatrix` but did not address the application layer; this PR is the missing application path. - **#38494 (`xxrlzzz`, Jul 2023, closed Apr 2024), closest in spirit to this fix. It built a Skia `Matrix` and applied via `setAnimationMatrix` for a broader class of 2D transforms, plus a reflection shim for pre-Q. javache's review raised two concerns: (1) the SDK doc framing of `setAnimationMatrix` as an animation API, and (2) "two divergent code paths" complexity. This PR addresses both: (1) AndroidX `androidx.transition.ViewUtilsApi21` invokes the same API for static transforms in production today, and the API was promoted to public in API 29; the precedent is established. (2) The dispatch is tightened to `isAffine2DTransformWithSkew`, so the new path runs only for transforms that are broken under the existing path; the rotate / scale / translate / rotateX / rotateY / perspective code stays bit-identical. The reflection shim is intentionally not adopted; pre-Q skew remains dropped. - **#42676 (`piaskowyk` + `bartlomiejbloniarz`, Jan 2024, closed Aug 2025), simulated skew via 3D rotation + non-uniform scale + perspective hack. The 2x2 sub-matrix matches a true skew, but the 4x4 differs in the third row/column, so composition with real `rotateX` / `rotateY` produces wrong results. The PR author acknowledged this in the description. Not the right shape. ## Changelog: [ANDROID] [FIXED] - skewX / skewY transforms now render correctly on Android Q+. Pull Request resolved: #56724 Test Plan: ### Unit tests - `./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests 'com.facebook.react.uimanager.SkewMatrixHelperTest*'`, 17 / 17 pass (predicate cases for `hasSkewTransform` and `isAffine2DTransform`; matrix-math cases for `buildAffine2DMatrix` covering pure skewX, scale-then-translate ordering, view-center pivot default, and `transformOrigin` overrides via Number values and "%" strings). - `yarn jest packages/react-native/Libraries/StyleSheet/__tests__/processTransform-test.js`, 16 / 16 + 19 / 19 snapshots, unchanged. - `./gradlew ktfmtCheck`, `yarn lint`, `yarn flow`, all clean. ### Manual verification (Android, rn_test AVD: Pixel 8 Pro, Android 16, arm64-v8a) RNTester -> APIs -> Transforms -> "Skew (#27649)" (the new permanent example added in this PR). | Before | After | | --- | --- | | <img src="https://raw.githubusercontent.com/qflen/react-native/f4585e841aba90b69c11474dbfdc3e9540b8f0ee/assets/27649/before_android_skew_scene.png" width="260"> | <img src="https://raw.githubusercontent.com/qflen/react-native/f4585e841aba90b69c11474dbfdc3e9540b8f0ee/assets/27649/after_android_skew_scene.png" width="260"> | | Boxes render as rotated rectangles. Decompose extracts ~20 deg of rotation plus non-uniform scale; the residual shear is dropped. | Boxes render as true parallelograms. Top and bottom edges horizontal, sides tilted by 20 deg. | Hit testing follows the rendered parallelogram. Without commit 2, the rendering fix would land alone: parallelograms render but `TouchTargetHelper.getChildPoint` would still inverse-map through `child.matrix` (which doesn't compose `mAnimationMatrix`) and clip to the original rectangle. Verified empirically by sweeping tap coordinates 1 px on either side of every parallelogram edge on the skewX box (rect bounds `[116, 549] [300, 732]` on the rn_test AVD): - `(100, 555)`: inside parallelogram top-left tip, outside the rect. With only commit 1: misses. With commit 2 added: registers as `skewX 20deg`. - `(330, 731)`: bottom-right tip. Same flip. - `(208, 640)`: parallelogram / rect center. Registers either way. - `(350, 640)`: outside the parallelogram at vertical-pivot y. Misses either way (correct). The new `Skew (https://github.com/facebook/react-native/issues/27649)` example also includes a `useNativeDriver: true` `Animated.timing` interpolating skewX from `0deg` to `20deg`. Native-driven animations re-emit the transform array per frame via `TransformAnimatedNode.collectViewUpdates -> setTransformProperty`, so the dispatch runs each frame and the skew animates smoothly. ### iOS iOS already handles skewX / skewY correctly via `CATransform3D` (Paper: `RCTConvert+Transform.m` sets `next.m21 = tanf(skew)` for skewX and `next.m12 = tanf(skew)` for skewY; Fabric: `RCTViewComponentView.mm` calls `resolveTransform` -> `RCTCATransform3DFromTransformMatrix` -> `layer.transform`). This PR does not touch that path; the AFTER Android rendering above matches the existing iOS rendering. ### Negative case Existing transform examples (Translate-Rotate-Scale, Perspective-Rotate-Animation, Rotate-Scale, Transform-using-a-string, Transform-Matrix-2D / 3D) render bit-identically to `origin/main`. The `hasSkewTransform` predicate filters them out of the new path, so they go through the unchanged decompose-and-set-View-props code. The new `setAnimationMatrix(null)` clearing call on the fallthrough path is gated by the `R.id.skew_animation_matrix` view tag, so it fires only on the skew -> non-skew transition; non-skew animations have no per-frame regression. Reviewed By: christophpurrer Differential Revision: D106497417 Pulled By: javache fbshipit-source-id: 68f378a34c389a92fc7f161a31c7134cf6d8ee36
1 parent 02c5ab7 commit 62903bc

7 files changed

Lines changed: 597 additions & 3 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.facebook.react.uimanager;
99

1010
import android.graphics.Color;
11+
import android.graphics.Matrix;
1112
import android.graphics.Paint;
1213
import android.os.Build;
1314
import android.text.TextUtils;
@@ -579,6 +580,31 @@ protected void setTransformProperty(
579580
view.setScaleX(1);
580581
view.setScaleY(1);
581582
view.setCameraDistance(0);
583+
clearSkewAnimationMatrixIfActive(view);
584+
return;
585+
}
586+
587+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
588+
&& SkewMatrixHelper.isAffine2DTransformWithSkew(transforms)) {
589+
Matrix affine =
590+
SkewMatrixHelper.buildAffine2DMatrix(
591+
transforms,
592+
PixelUtil.toDIPFromPixel(view.getWidth()),
593+
PixelUtil.toDIPFromPixel(view.getHeight()),
594+
transformOrigin);
595+
view.setTranslationX(0);
596+
view.setTranslationY(0);
597+
view.setRotation(0);
598+
view.setRotationX(0);
599+
view.setRotationY(0);
600+
view.setScaleX(1);
601+
view.setScaleY(1);
602+
view.setCameraDistance(0);
603+
view.setAnimationMatrix(affine);
604+
// Tag value is the matrix itself so TouchTargetHelper can use it for hit testing -- View's
605+
// own getMatrix() does not compose mAnimationMatrix, so without this fallback the React
606+
// hit-test path would still see the original rectangular bounds.
607+
view.setTag(R.id.skew_animation_matrix, affine);
582608
return;
583609
}
584610

@@ -628,6 +654,21 @@ protected void setTransformProperty(
628654
scale * scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER);
629655
view.setCameraDistance(normalizedCameraDistance);
630656
}
657+
658+
clearSkewAnimationMatrixIfActive(view);
659+
}
660+
661+
// setAnimationMatrix is called only on the transition out of a skew matrix; calling it
662+
// unconditionally would invalidate the View's RenderNode every frame for any non-skew animation.
663+
private static <T extends View> void clearSkewAnimationMatrixIfActive(@NonNull T view) {
664+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
665+
return;
666+
}
667+
if (view.getTag(R.id.skew_animation_matrix) == null) {
668+
return;
669+
}
670+
view.setAnimationMatrix(null);
671+
view.setTag(R.id.skew_animation_matrix, null);
631672
}
632673

633674
/**
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.uimanager
9+
10+
import android.graphics.Matrix
11+
import com.facebook.react.bridge.ReadableArray
12+
import com.facebook.react.bridge.ReadableMap
13+
import com.facebook.react.bridge.ReadableType
14+
15+
/**
16+
* Builds a 2D-affine [Matrix] from a `transform` array for the subset of operations Android `View`
17+
* cannot represent through its individual property setters (specifically `skewX` / `skewY`). Used
18+
* by [BaseViewManager.setTransformProperty] to apply such transforms via `View.setAnimationMatrix`
19+
* on Android Q+.
20+
*/
21+
internal object SkewMatrixHelper {
22+
/**
23+
* Returns true if [transforms] contains only operations representable by a Skia [Matrix] in 2D:
24+
* `rotate` / `rotateZ`, `scale`, `scaleX` / `scaleY`, `translate` / `translateX` / `translateY`
25+
* with zero Z, `skewX`, `skewY`. Returns false for `matrix`, `perspective`, `rotateX`, `rotateY`,
26+
* a `translate` with a non-zero Z component, and the raw 16-element matrix shorthand used by
27+
* Fabric LayoutAnimations.
28+
*/
29+
@JvmStatic
30+
fun isAffine2DTransformWithSkew(transforms: ReadableArray): Boolean {
31+
if (isRawMatrixShorthand(transforms)) {
32+
return false
33+
}
34+
35+
var hasSkewTransform = false
36+
for (i in 0 until transforms.size()) {
37+
if (transforms.getType(i) != ReadableType.Map) continue
38+
val map = transforms.getMap(i) ?: continue
39+
val type = map.keySetIterator().nextKey()
40+
when (type) {
41+
"matrix",
42+
"perspective",
43+
"rotateX",
44+
"rotateY" -> return false
45+
"translate" -> {
46+
val value = map.getArray(type)
47+
if (
48+
value != null &&
49+
value.size() > 2 &&
50+
value.getType(2) == ReadableType.Number &&
51+
value.getDouble(2) != 0.0
52+
) {
53+
return false
54+
}
55+
}
56+
"skewX",
57+
"skewY" -> hasSkewTransform = true
58+
}
59+
}
60+
return hasSkewTransform
61+
}
62+
63+
/**
64+
* Builds a [Matrix] in pixel coordinates by walking [transforms] left-to-right and applying each
65+
* operation via `Matrix.preX` around the resolved pivot. Pivot is the view center; if
66+
* [transformOrigin] is set, it overrides per-axis (Number values are DIP, "P%" strings are P/100
67+
* of the view dimension; Z is ignored).
68+
*
69+
* Composition is pre-multiplication: when the resulting matrix is applied to a point, the
70+
* rightmost (last) array entry is applied first. Matches CSS / iOS conventions and the
71+
* left-to-right-iteration / right-multiply contract of [MatrixMathHelper.multiplyInto] used by
72+
* [TransformHelper.processTransform].
73+
*/
74+
@JvmStatic
75+
fun buildAffine2DMatrix(
76+
transforms: ReadableArray,
77+
viewWidthDip: Float,
78+
viewHeightDip: Float,
79+
transformOrigin: ReadableArray?,
80+
): Matrix {
81+
val pivotXPx: Float
82+
val pivotYPx: Float
83+
if (transformOrigin == null) {
84+
pivotXPx = PixelUtil.toPixelFromDIP(viewWidthDip / 2f)
85+
pivotYPx = PixelUtil.toPixelFromDIP(viewHeightDip / 2f)
86+
} else {
87+
pivotXPx =
88+
PixelUtil.toPixelFromDIP(
89+
resolveOriginAxis(transformOrigin, 0, viewWidthDip, viewWidthDip / 2f)
90+
)
91+
pivotYPx =
92+
PixelUtil.toPixelFromDIP(
93+
resolveOriginAxis(transformOrigin, 1, viewHeightDip, viewHeightDip / 2f)
94+
)
95+
}
96+
97+
val matrix = Matrix()
98+
for (i in 0 until transforms.size()) {
99+
if (transforms.getType(i) != ReadableType.Map) continue
100+
val map = transforms.getMap(i) ?: continue
101+
val type = map.keySetIterator().nextKey()
102+
when (type) {
103+
"rotate",
104+
"rotateZ" ->
105+
matrix.preRotate(
106+
Math.toDegrees(TransformHelper.convertToRadians(map, type)).toFloat(),
107+
pivotXPx,
108+
pivotYPx,
109+
)
110+
"scale" -> {
111+
val s = map.getDouble(type).toFloat()
112+
matrix.preScale(s, s, pivotXPx, pivotYPx)
113+
}
114+
"scaleX" -> matrix.preScale(map.getDouble(type).toFloat(), 1f, pivotXPx, pivotYPx)
115+
"scaleY" -> matrix.preScale(1f, map.getDouble(type).toFloat(), pivotXPx, pivotYPx)
116+
"skewX" ->
117+
matrix.preSkew(
118+
Math.tan(TransformHelper.convertToRadians(map, type)).toFloat(),
119+
0f,
120+
pivotXPx,
121+
pivotYPx,
122+
)
123+
"skewY" ->
124+
matrix.preSkew(
125+
0f,
126+
Math.tan(TransformHelper.convertToRadians(map, type)).toFloat(),
127+
pivotXPx,
128+
pivotYPx,
129+
)
130+
"translate" -> {
131+
val value = map.getArray(type)
132+
if (value != null && value.size() >= 1) {
133+
val tx = parseTranslateValue(value, 0, viewWidthDip)
134+
val ty = if (value.size() > 1) parseTranslateValue(value, 1, viewHeightDip) else 0.0
135+
matrix.preTranslate(
136+
PixelUtil.toPixelFromDIP(tx.toFloat()),
137+
PixelUtil.toPixelFromDIP(ty.toFloat()),
138+
)
139+
}
140+
}
141+
"translateX" ->
142+
matrix.preTranslate(
143+
PixelUtil.toPixelFromDIP(parseScalarTranslate(map, type, viewWidthDip).toFloat()),
144+
0f,
145+
)
146+
"translateY" ->
147+
matrix.preTranslate(
148+
0f,
149+
PixelUtil.toPixelFromDIP(parseScalarTranslate(map, type, viewHeightDip).toFloat()),
150+
)
151+
}
152+
}
153+
return matrix
154+
}
155+
156+
private fun isRawMatrixShorthand(transforms: ReadableArray): Boolean =
157+
transforms.size() == 16 && transforms.getType(0) == ReadableType.Number
158+
159+
private fun resolveOriginAxis(
160+
origin: ReadableArray,
161+
axis: Int,
162+
dimensionDip: Float,
163+
defaultDip: Float,
164+
): Float {
165+
if (origin.size() <= axis) return defaultDip
166+
return when (origin.getType(axis)) {
167+
ReadableType.Number -> origin.getDouble(axis).toFloat()
168+
ReadableType.String -> {
169+
val part = origin.getString(axis) ?: return defaultDip
170+
if (!part.endsWith("%")) return defaultDip
171+
try {
172+
(part.dropLast(1).toDouble() * dimensionDip / 100.0).toFloat()
173+
} catch (e: NumberFormatException) {
174+
defaultDip
175+
}
176+
}
177+
else -> defaultDip
178+
}
179+
}
180+
181+
private fun parseTranslateValue(value: ReadableArray, index: Int, dimensionDip: Float): Double {
182+
if (value.getType(index) != ReadableType.String) {
183+
return value.getDouble(index)
184+
}
185+
val s = value.getString(index) ?: return 0.0
186+
return TransformHelper.parseTranslateValue(s, dimensionDip.toDouble())
187+
}
188+
189+
private fun parseScalarTranslate(map: ReadableMap, key: String, dimensionDip: Float): Double {
190+
if (map.getType(key) != ReadableType.String) {
191+
return map.getDouble(key)
192+
}
193+
val s = map.getString(key) ?: return 0.0
194+
return TransformHelper.parseTranslateValue(s, dimensionDip.toDouble())
195+
}
196+
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.graphics.PointF
1313
import android.view.View
1414
import android.view.ViewGroup
1515
import com.facebook.common.logging.FLog
16+
import com.facebook.react.R
1617
import com.facebook.react.bridge.UiThreadUtil
1718
import com.facebook.react.common.ReactConstants
1819
import com.facebook.react.touch.ReactHitSlopView
@@ -298,7 +299,11 @@ public object TouchTargetHelper {
298299
): Boolean {
299300
var localX = x + parent.scrollX - child.left
300301
var localY = y + parent.scrollY - child.top
301-
val matrix = child.matrix
302+
// BaseViewManager applies skewX / skewY through View.setAnimationMatrix, which is composed
303+
// for drawing but not exposed via View.getMatrix(); fall back to the stashed matrix so hit
304+
// testing follows the rendered parallelogram and not the original rectangle.
305+
val skewMatrix = child.getTag(R.id.skew_animation_matrix) as? Matrix
306+
val matrix = skewMatrix ?: child.matrix
302307
if (!matrix.isIdentity) {
303308
val inverseMatrix = inverseMatrix
304309
if (!matrix.invert(inverseMatrix)) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public object TransformHelper {
2525
override fun initialValue(): DoubleArray = DoubleArray(16)
2626
}
2727

28-
private fun convertToRadians(transformMap: ReadableMap, key: String): Double {
28+
internal fun convertToRadians(transformMap: ReadableMap, key: String): Double {
2929
var value: Double
3030
var inRadians = true
3131
if (transformMap.getType(key) == ReadableType.String) {
@@ -189,7 +189,7 @@ public object TransformHelper {
189189
}
190190
}
191191

192-
private fun parseTranslateValue(stringValue: String, dimension: Double): Double {
192+
internal fun parseTranslateValue(stringValue: String, dimension: Double): Double {
193193
return try {
194194
if (stringValue.endsWith("%")) {
195195
val percentage = stringValue.dropLast(1).toDouble()

packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@
6666
<!-- tag is used to invalidate transform style in view manager -->
6767
<item type="id" name="invalidate_transform"/>
6868

69+
<!-- tag stores the active 2D-affine skew Matrix applied to the View via setAnimationMatrix,
70+
so non-skew updates can clear it only on the transition out and TouchTargetHelper can
71+
reuse it for hit testing -->
72+
<item type="id" name="skew_animation_matrix"/>
73+
6974
<!-- tag is used to store if we should render the view to a hardware texture -->
7075
<item type="id" name="use_hardware_layer"/>
7176

0 commit comments

Comments
 (0)