Status Update
Comments
cl...@google.com <cl...@google.com>
gu...@gmail.com <gu...@gmail.com> #2
First of all thanks for this detailed issue.
This issue had been investigated thoroughly when it was first reported internally. The surprising detail in this report is that the issue is not reproducible before 1.7
. I will look into this.
The main problem with POBox is the fact that it is deprecated. Since 2021 Sony has been shipping new Xperia devices with Gboard pre-installed. Although we are aware that there is still a considerable amount of users still using POBox, the described behavior is caused by POBox's noncompliant behavior with InputConnection
and InputMethodManager
documentation. However, this is understandable since TextView
implementation was also not respecting the behavior that is expected from Editors.
Ultimately we have decided to enforce the documented behavior with specifically regards to when editors should call InputMethodManager.updateSelection
. Also, although unconfirmed, there were traces of possible custom code being included in Sony OEM images that changed how InputMethodManager was notified from TextView. If POBox also depended on something like this, it would be impossible for Compose code to replicate the same unknown behavior.
il...@gmail.com <il...@gmail.com> #3
Or is that option not available?
Even if the root cause is POBox, from the perspective of the app's customers, it looks like an app bug, so this issue is a blocker against updating Jetpack Compose.
me...@gmail.com <me...@gmail.com> #4
Just to be sure, it is dangerous to replace Compose TextField with Android View EditText as a workaround for this issue.
Compose 1.7 has a bug that causes ANR when the focus is on EditText.
Another View-related bug in Compose 1.7 is that an Android View is focused by calling FocusManager.clearFocus().
Perhaps there is a lack of testing of Compose 1.7 in combination with Android View. There is also a possibility that there are other fatal bugs related to View.
In other words, the only options for apps targeting the Japanese market that require POBox support are to continue using Compose 1.6 or to use EditText in combination with various workarounds.
co...@gmail.com <co...@gmail.com> #5
Project: platform/frameworks/support
Branch: androidx-main
Author: Halil Ozercan <
Link:
Fix POBox keyboard issue
Expand for full commit details
Fix POBox keyboard issue
Fix: 373743376
Fix: 329209241
Test: NullableInputConnectionWrapperTest
Change-Id: I94e0e598274fb88b255f977f9fbd50dfbbb1ecb1
Files:
- M
compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/NullableInputConnectionWrapperTest.kt
- M
compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/NullableInputConnectionWrapper.android.kt
Hash: 57f58c4b80d5d8470b2aca325dfdcd55f235231e
Date: Thu Oct 24 01:25:20 2024
ja...@gmail.com <ja...@gmail.com> #6
Many thanks again for this report. Especially for giving us a huge clue in terms of what could be going wrong. The fix is now merged and I will ask for a cherry-pick into a stable release.
sa...@rapido.bike <sa...@rapido.bike> #7
Do you have any concrete plan to cherry-pick the fix into current stable version (1.7.x)? We are currently waiting it.
ro...@gmail.com <ro...@gmail.com> #8
Yes, this fix is planned to be included in a future 1.7.x
release.
za...@gmail.com <za...@gmail.com> #9
Thanks for the fix. Sorry to follow up on this. is it possible for you to share specific release version/date for the stable version? We are waiting on this to decide on our direction.
ma...@gmail.com <ma...@gmail.com> #10
ba...@gmail.com <ba...@gmail.com> #11
pr...@gmail.com <pr...@gmail.com> #12
ma...@gmail.com <ma...@gmail.com> #13
ri...@gmail.com <ri...@gmail.com> #14
+1+1
ep...@gmail.com <ep...@gmail.com> #15
ra...@gmail.com <ra...@gmail.com> #16
+1
xy...@gmail.com <xy...@gmail.com> #17
ho...@gmail.com <ho...@gmail.com> #18
br...@gmail.com <br...@gmail.com> #19
am...@gmail.com <am...@gmail.com> #20
to...@gmail.com <to...@gmail.com> #21
+1
lo...@gmail.com <lo...@gmail.com> #22
Hey folks, please stop adding +1 comments and use the dedicated star button at the top of the page instead.
That'll save some noise and avoid all the "stargazers" from getting unactionable emails.
Thank you.
ta...@mercury.com <ta...@mercury.com> #23
Here's a terrible, terrible hack to get the blur RenderEffect
for a Compose canvas.
The hack is terrible but it appears to work. You have been warned.
so...@gmail.com <so...@gmail.com> #24
de...@gmail.com <de...@gmail.com> #25
ya...@gmail.com <ya...@gmail.com> #26
ma...@gmail.com <ma...@gmail.com> #27
Hey folks, please stop adding +1 comments and use the dedicated star button at the top of the page instead.
That'll save some noise and avoid all the "stargazers" from getting unactionable emails.
Thank you.
[Deleted User] <[Deleted User]> #29
ap...@google.com <ap...@google.com> #31
Branch: androidx-main
commit 87b47565c30d75faf827b482a2dbad95c3016ca7
Author: Nader Jawad <njawad@google.com>
Date: Tue Aug 17 19:56:23 2021
Introduce BlurredEdgeTreatment API
Relnote: "Added BlurredEdgeTreatment API
to simplify blur use cases into more
commonly used combinations of clip flags
and TileModes. Most use cases involve
either letting blurred content render
outside the original content bounds
and blurring regions outside these bounds
with transparent black, or clipping content
to content bounds sampling the closest edge
for blur kernels that extend beyond content
bounds."
Bug: 166927547
Test: Added BlurTest
Change-Id: I6b4b7966920855374275ae7ea950b310fa28efd0
M compose/ui/ui/api/current.txt
M compose/ui/ui/api/public_plus_experimental_current.txt
M compose/ui/ui/api/restricted_current.txt
M compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/BlurSample.kt
A compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/BlurTest.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/Blur.kt
ap...@google.com <ap...@google.com> #32
Branch: androidx-main
commit a04a6e81fdeba710a3790d30e113d203dd3ff390
Author: Nader Jawad <njawad@google.com>
Date: Tue Aug 03 17:37:24 2021
More RenderEffect support
Relnote: "Added support for RenderEffect
in compose desktop.
Introduced OffsetEffect as well as
the blur modifier as a simple way to
introduce blur visual effects to a portion
of the composition hierarchy."
Fixed order of operations issue on compose
desktop that prevented clipping from working
properly with saveLayer.
Bug: 166927547
Test: Added tests to RenderEffectTest/GraphicsLayerTest/
AndroidRenderEffectTest
Change-Id: I0f6aa293db2bf34f5ed2aa7499a97332dacf73fc
M compose/ui/ui-graphics/api/current.txt
M compose/ui/ui-graphics/api/public_plus_experimental_current.txt
M compose/ui/ui-graphics/api/restricted_current.txt
M compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidRenderEffectTest.kt
M compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidRenderEffect.android.kt
M compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/RenderEffect.kt
M compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopTileMode.desktop.kt
A compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/RenderEffect.desktop.kt
M compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/RenderEffectTest.kt
M compose/ui/ui/api/current.txt
M compose/ui/ui/api/public_plus_experimental_current.txt
M compose/ui/ui/api/restricted_current.txt
A compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/BlurSample.kt
A compose/ui/ui/samples/src/main/res/drawable/circus.jpg
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerModifierTest.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
A compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/Blur.kt
M compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.desktop.kt
M compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
ap...@google.com <ap...@google.com> #33
Branch: androidx-main
commit af8e89a54fb3173251a43251bd8a0bda2a126c66
Author: Nader Jawad <njawad@google.com>
Date: Fri Jul 30 18:46:49 2021
Add RenderEffect API
Relnote: "Introduced RenderEffect API
that can be optionally configured on
a Modifier.graphicsLayer to alter
the contents of the layer itself. This
can be used to blur contents of a composable
and child composables within a composition
hierarchy."
Bug: 166927547
Test: Added tests to RenderEffectTest/GraphicsLayerTest/
AndroidRenderEffectTest
Change-Id: I47c4d5ecc801f35e632d2062e03c756f564a2db5
M compose/ui/ui-graphics/api/current.txt
M compose/ui/ui-graphics/api/public_plus_experimental_current.txt
M compose/ui/ui-graphics/api/restricted_current.txt
A compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidRenderEffectTest.kt
A compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidRenderEffect.android.kt
A compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/RenderEffect.kt
A compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/RenderEffectTest.kt
M compose/ui/ui/api/current.ignore
M compose/ui/ui/api/current.txt
M compose/ui/ui/api/public_plus_experimental_current.txt
M compose/ui/ui/api/restricted_current.ignore
M compose/ui/ui/api/restricted_current.txt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/DeviceRenderNode.android.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeApi23.android.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeApi29.android.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerScope.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
M compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.desktop.kt
M compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
ap...@google.com <ap...@google.com> #34
Branch: androidx-main
commit 1d626519ace91a88f1a08d02b24d3741dea10b95
Author: Nader Jawad <njawad@google.com>
Date: Thu Jul 29 15:23:06 2021
Introduce TileMode.Decal
Relnote: "Add TileMode.Decal support
which is useful in defining edge
behavior for blur based RenderEffects."
Bug: 166927547
Test: Added tests to TileModeTest and
AndroidTileModeTest
Change-Id: I7e8ed0c4eb2490ef3cd0032b5952d0962f489354
M compose/ui/ui-graphics/api/current.txt
M compose/ui/ui-graphics/api/public_plus_experimental_current.txt
M compose/ui/ui-graphics/api/restricted_current.txt
A compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidTileModeTest.kt
M compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidTileMode.android.kt
M compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/TileMode.kt
M compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/TileModeTest.kt
el...@gmail.com <el...@gmail.com> #35
nj...@google.com <nj...@google.com>
cs...@google.com <cs...@google.com> #36
🎉🎉🎉
fa...@gmail.com <fa...@gmail.com> #37
when will it be ready for use? And short guideline would great. Thank you for the fast reaction!
da...@gmail.com <da...@gmail.com> #38
Nice, which version of compose will have this?
Is it android 12 only or backported? Thanks and well done!
nj...@google.com <nj...@google.com> #39
This API is supported on Android 12 only and is a no-op for older API levels.
Blur is supported as a Modifier API and common usage would look like the following:
Box(modifier = Modifier.size(100.dp).blur(30.dp)) {
// child composable
}
[Deleted User] <[Deleted User]> #40
se...@gmail.com <se...@gmail.com> #41
It's already been released with Compose 1.1.0-alpha03
ru...@gmail.com <ru...@gmail.com> #42
sh...@gmail.com <sh...@gmail.com> #43
is it supported in android < 12
si...@gmail.com <si...@gmail.com> #44
ri...@gmail.com <ri...@gmail.com> #45
I think this question should be reopened? It doesn't support versions below Android S, as a non-system ui framework, this is very disappointing.
ja...@gmail.com <ja...@gmail.com> #46
And Kotlin Multiplatform info should be great too
sh...@gmail.com <sh...@gmail.com> #47
The Blur Modifier in compose currently only supports the API 31, hence it is of no use. I tried to create a blur Modifier by first drawing the content on the bitmap canvus and the blur it and draw it on target canvus. but it seems compose does expose the draw(canvus) like in ViewWorld. Currently I looked the docs of Test ScreenShot, Magnifier but both of these use onGloballyPositioned to capture the piece of bitmap from LocalView. Hence it doesn't do what I intended it to do.
There is another solution that involves Creating a Blury View and then use composed to make a modifier (But I don't like this solution.). If anybody knows how to achieve it please do tell me.
mg...@gmail.com <mg...@gmail.com> #48
bl...@gmail.com <bl...@gmail.com> #49
cs...@google.com <cs...@google.com> #50
To
On the "glassmorphism" effect, could send a link to exactly what effect do you mean?
ga...@gmail.com <ga...@gmail.com> #51
+1
wo...@gmail.com <wo...@gmail.com> #52
mg...@gmail.com <mg...@gmail.com> #53
The best I can show you without putting to much effort on it, it is some images examples:
Here, the card receives an alpha color and the elements located behind it receive a level o blur.
As you move the front element, the back ones dynamically interact with it.
And the effect, as you can see, can be stacked.
ca...@usp.br <ca...@usp.br> #54
Glassmorphism would be great since it opens several UI possibilities (that right now seems only feasible for iOS). In Brazil, Android is predominant and there's a current trend among UXs to adopt glassmorphic like effects. The only thing holding us back is the implementation friction.
Plenty use cases here:
ra...@gmail.com <ra...@gmail.com> #55
We need it
cs...@google.com <cs...@google.com> #56
Regarding
mg...@gmail.com <mg...@gmail.com> #57
mg...@gmail.com <mg...@gmail.com> #58
Searching through the internet, you can find quite a lot of examples on how to implement background blur. All the examples, although different in execution, uses the same logic:
1 - You choose a root for you effect;
2 - You choose the view you want to apply the effect on;
3 - You observe the view relative position into the view tree;
4 - When the view is showing itself on the screen, fully or partially, you start the following computation:
a - Get the matrix dimension of the desired view;
b - Generates a bitmap from the dimensions of the view;
c - Scale down to a proper scale so the next steps don't consume too much resources;
d - With the scaled bitmap, apply algorithms of image treatment to achieve the blur effect;
e - With the blurred bitmap, paint it on a canvas based on the dimensions of the view;
f - finally, display the blurred view on the place of a normal view (with transparent on it).
As you can see, this process is kind of a cheat one. It can work well in some cases, but most of them are just terrible. The more your components move in screen, the harder it becomes to achieve performance and better looking effects.
Another downside of this method is its compatibility with the composable way of working. On old style android, each fragment / activity is its own view. But, that's not the case for compose: the whole thing is a single view. There are some ways of transforming Compose component -> Compose View -> Android View, creating some sort of old android view tree, but that's just nasty and prune to bad results.
After experimenting with all of these, I was looking into the Composable Canvas, having a lot fun with it by the way, and I realized the presence of a very interesting effect which can be applied to the drawing: blend mode.
Blend mode has some interesting visual effects on the drawing. It computes a mix of RGB channels so the back composable interacts with the children component mixing the visuals. It's almost the same principle used in the background blur, but instead of pixels effect, it's a color effect. The blend mode, funny enough, just breaks with the new Blur Effect from composable.
Finally, a last but interesting discover, is the
mg...@gmail.com <mg...@gmail.com> #60
[Deleted User] <[Deleted User]> #61
ba...@gmail.com <ba...@gmail.com> #62
Dear Android Team,
Current design i.e Modifier.blur(50.dp)
supports blur on the composable itself not underneath. But its use is too much limited. What most of the people here and elsewhere are
looking for overlay blur which is not supported.
I don't think it is a too hard to even comprehend kind of request. If you guys believe it is. Kindly give us a response of why it is that hard to implemented a overlay blur.
da...@gmail.com <da...@gmail.com> #63
Android 12 introduced support for RenderEffect
s including blurring, even though out-of-the-box it would blur the View
itself, and not any content underneath it — so it's not a backdrop blur (in CSS terms, it's like filter: blur()
and not backdrop-filter: blur()
).
However, internally, Views are backed by RenderNode
s, which ultimately apply RenderEffects. By leveraging RenderNodes, it's possible to implement backdrop blurring, and I've done that with Compose. Note that it's for Android 12 onwards.
For anyone interested, the implementation is at the end of the post.
Sample usage
It works in a producer–consumer manner, where the producer is any composable with Modifier.backdropContent
(which feeds the composable into the Backdrop
) and the consumer is any composable with Modifier.backdrop
(which takes content from the specified Backdrop
and draws it behind the composable, applying a RenderEffect
to it). The Backdrop
class thus serves to connect the producers and the consumers and it also stores the content being produced — the backdrop. You obtain an instance of Backdrop
using the rememberBackdrop
function.
To further simplify usage on the consumer part, I also implemented Modifier.blurredSurface
, which accepts a surfaceColor
, blurRadius
, saturation
, and a border stroke
, and sets up the backdrop with the appropriate RenderEffect
automatically. The following example should show a list (to be populated) and a blurred bar overlaid on top of the list.
- Note: I haven't tested the example, but the implementation works in my app.
@Composable
fun BackdropBlurDemo() {
val backdrop = rememberBackdrop()
val listState = rememberLazyListState()
val showDivider = listState.canScrollForward
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxWidth()
.requiredHeight(56.dp)
.blurredSurface(backdrop) {
BlurredSurfaceDefaults.parameters(
strokeAlpha = when {
showDivider -> BlurredSurfaceDefaults.DefaultStrokeAlpha
else -> 0f
},
strokeAlphaAnimationSpec = spring()
)
}
)
LazyColumn(
state = listState,
modifier = Modifier
.matchParentSize()
.backdropContent(backdrop)
) {
// Load some items
}
}
}
Implementation
Notes:
- Some
import
s may be missing or unordered, I joined several files to post the code here. - The implementation depends on the artifact
androidx.compose.ui:ui-util
.
import android.graphics.RenderNode
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.layout.*
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.packFloats
import androidx.compose.ui.util.unpackFloat1
import androidx.compose.ui.util.unpackFloat2
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.math.roundToInt
import kotlin.reflect.KProperty
@Immutable
data class BlurredSurfaceParameters(
val surfaceColor: Color,
val blurRadius: Dp,
val saturation: Float,
val stroke: DpStroke?
) {
@JvmField
internal val paint = Paint().also { paint ->
paint.colorFilter = ColorFilter.colorMatrix(
colorMatrix = when (val saturation = saturation) {
1f -> {
MakeOpaqueColorMatrix
}
else -> ColorMatrix().also { colorMatrix ->
colorMatrix.setToSaturation(saturation)
colorMatrix *= MakeOpaqueColorMatrix
}
}
)
}
}
@Immutable
object BlurredSurfaceDefaults {
const val DefaultSurfaceOpacity = 0.5f
@JvmStatic val DefaultBlurRadius = 24.dp
const val DefaultSaturation = 1.3f
const val DefaultStrokeAlpha = 0.7f
@Composable
fun parameters(
surfaceColor: Color = MaterialTheme.colorScheme.surface,
surfaceOpacity: Float = DefaultSurfaceOpacity,
blurRadius: Dp = DefaultBlurRadius,
saturation: Float = DefaultSaturation,
strokeAlpha: Float = DefaultStrokeAlpha,
strokeAlphaAnimationSpec: AnimationSpec<Float>? = null,
stroke: DpStroke? = DpStroke(
width = DividerDefaults.Thickness,
color = MaterialTheme.colorScheme.outlineVariant,
parameters = StrokeParameters(
alignment = -1f,
alpha = when (strokeAlphaAnimationSpec) {
null -> strokeAlpha
else -> animateFloatAsState(
targetValue = strokeAlpha,
animationSpec = strokeAlphaAnimationSpec,
visibilityThreshold = 1f / 255f,
label = "blurredSurface strokeAlpha"
).value
},
blendMode = BlendMode.Luminosity
)
)
) = BlurredSurfaceParameters(
surfaceColor = surfaceColor.copy(alpha = surfaceOpacity),
blurRadius = blurRadius,
saturation = saturation,
stroke = stroke
)
}
fun Modifier.blurredSurface(
backdrop: Backdrop,
parameters: @DisallowComposableCalls @Composable () -> BlurredSurfaceParameters = {
BlurredSurfaceDefaults.parameters()
}
) = composed {
val currentParameters by rememberUpdatedState(parameters())
val currentDensity by rememberUpdatedState(LocalDensity.current)
val background: DrawScope.() -> Unit = remember {
{ drawRect(currentParameters.surfaceColor.copy(alpha = 1f)) }
}
remember {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
backdrop(
backdrop = backdrop,
background = background,
paint = { currentParameters.paint },
renderEffect = {
with(currentDensity) {
val radiusPx = currentParameters.blurRadius.toPx()
BlurEffect(
radiusX = radiusPx,
radiusY = radiusPx,
edgeTreatment = TileMode.Decal
)
}
}
).drawBehind {
drawRect(currentParameters.surfaceColor)
}
} else {
drawBehind(background)
}
}
.stroke(currentParameters.stroke)
}
private val MakeOpaqueColorMatrix = ColorMatrix(
floatArrayOf(
1f, 0f, 0f, 0f, 0f,
0f, 1f, 0f, 0f, 0f,
0f, 0f, 1f, 0f, 0f,
0f, 0f, 0f, 0f, 255f
)
)
@Composable
fun rememberBackdrop(): Backdrop =
remember { Backdrop() }
@RequiresApi(Build.VERSION_CODES.S)
fun Modifier.backdropContent(
backdrop: Backdrop,
paint: () -> Paint? = Backdrop.DefaultPrePaint
): Modifier {
val paintState = derivedStateOf(structuralEqualityPolicy(), paint)
return composed {
remember(backdrop) {
backdrop.ContentNode(paintState = paintState)
}.modifier
}
}
@RequiresApi(Build.VERSION_CODES.S)
fun Modifier.backdrop(
backdrop: Backdrop,
inset: Dp = 0.dp,
background: (DrawScope.() -> Unit)? = null,
paint: () -> Paint? = Backdrop.DefaultPostPaint,
renderEffect: () -> RenderEffect
): Modifier {
val paintState = derivedStateOf(structuralEqualityPolicy(), paint)
val renderEffectState = derivedStateOf(structuralEqualityPolicy(), renderEffect)
return composed {
remember(backdrop) {
BackdropNode(
backdrop = backdrop,
paintState = paintState,
renderEffectState = renderEffectState,
initialInsetDp = inset.value
)
}
.also { it.background = background }
.also { it.insetDp = inset.value }
.modifier
}
}
class Backdrop internal constructor() {
@Immutable
internal companion object {
@JvmStatic internal val DefaultPrePaint: () -> Paint? = { null }
@JvmStatic internal val DefaultPostPaint: () -> Paint? = { null }
@JvmStatic private val DefaultPrePaintValue = NativePaint()
}
private var contentNodeHead: ContentNode? = null
private var contentNodeTail: ContentNode? = null
@OptIn(ExperimentalContracts::class)
internal inline fun forEachNode(
block: (ContentNode) -> Unit
) {
contract {
callsInPlace(block)
}
contentNodeHead?.forEach(block)
}
internal inner class ContentNode
/**
* Creates a [ContentNode] that is added to this [Backdrop] when remembered
* and removed when forgotten.
*
* Always create inside a [remember] block.
*/
@RequiresApi(Build.VERSION_CODES.Q)
constructor(
paintState: State<Paint?>
) :
RememberObserver {
@RequiresApi(Build.VERSION_CODES.Q)
override fun onRemembered() {
append()
}
override fun onForgotten() {
remove()
}
override fun onAbandoned() {
remove()
}
var renderNode: RenderNode? = null
private set
val paint by paintState
var position by mutableOffsetStateDelegateOf(Offset.Zero)
private set
/**
* The [Modifier] to be used in [backdropContent].
*/
@RequiresApi(Build.VERSION_CODES.Q)
@JvmField
val modifier = Modifier
.onPlaced { position = it.positionInRoot() }
.drawWithContent {
renderNode?.let { renderNode ->
renderNode.setUseCompositingLayer(
true,
paint?.asFrameworkPaint() ?: DefaultPrePaintValue
)
val (width, height) = size.round()
renderNode.setPosition(0, 0, width, height)
withNode(renderNode) { drawContent() }
}
}
private var previous: ContentNode? = null
private var next: ContentNode? = null
@OptIn(ExperimentalContracts::class)
inline fun forEach(
block: (ContentNode) -> Unit
) {
contract {
callsInPlace(block)
}
var node = this
while (true) {
block(node)
node = node.next ?: break
}
}
@RequiresApi(Build.VERSION_CODES.Q)
private fun append() {
if (renderNode != null)
return
renderNode = RenderNode("content")
this@Backdrop.contentNodeTail = apply {
when (val tail = this@Backdrop.contentNodeTail) {
null -> {
contentNodeHead = this
}
else -> {
tail.next = this
this.previous = tail
}
}
}
}
private fun remove() {
if (renderNode == null)
return
renderNode = null
val previous = this.previous
val next = this.next
previous?.next = next
next?.previous = previous
if (this@Backdrop.contentNodeHead === this)
this@Backdrop.contentNodeHead = next
if (this@Backdrop.contentNodeTail === this)
this@Backdrop.contentNodeTail = previous
}
}
}
@RequiresApi(Build.VERSION_CODES.S)
private class BackdropNode(
private val backdrop: Backdrop,
paintState: State<Paint?>,
renderEffectState: State<RenderEffect>,
initialBackground: (DrawScope.() -> Unit)? = null,
initialInsetDp: Float
) {
private var position by mutableOffsetStateDelegateOf(Offset.Zero)
private val backdropNode = RenderNode("backdrop")
val renderEffect by renderEffectState
val paint by paintState
var background by mutableStateOf(initialBackground)
var insetDp by mutableFloatStateOf(initialInsetDp)
/**
* The [Modifier] to be used in [backdrop].
*/
@JvmField
val modifier = Modifier
.onPlaced { position = it.positionInRoot() }
.drawBehind {
val backdropNode = backdropNode
val (width, height) = size.round()
val inset = insetDp.dp.roundToPx()
backdropNode.setRenderEffect(renderEffect.asAndroidRenderEffect())
backdropNode.setPosition(
inset,
inset,
width - inset,
height - inset
)
backdropNode.withRecording { blurCanvas ->
backdrop.forEachNode { contentNode ->
contentNode.renderNode?.let { renderNode ->
val (sx, sy) = contentNode.position
val (dx, dy) = this@BackdropNode.position
val tx = sx - dx - inset
val ty = sy - dy - inset
blurCanvas.translate(+tx, +ty)
blurCanvas.drawRenderNode(renderNode)
blurCanvas.translate(-tx, -ty)
}
}
}
with(drawContext.canvas.androidCanvas) {
val saveCount = saveLayer(
0f,
0f,
width.toFloat(),
height.toFloat(),
paint?.asFrameworkPaint()
)
background?.invoke(this@drawBehind)
drawRenderNode(backdropNode)
restoreToCount(saveCount)
}
}
}
@Stable
private inline fun Size.round(): IntSize =
IntSize(
width = width.roundToInt(),
height = height.roundToInt()
)
@OptIn(ExperimentalContracts::class)
@RequiresApi(Build.VERSION_CODES.Q)
private inline fun <R> RenderNode.withRecording(
block: (Canvas) -> R
): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return try {
block(beginRecording())
} finally {
endRecording()
}
}
@OptIn(ExperimentalContracts::class)
@RequiresApi(Build.VERSION_CODES.Q)
private inline fun <T : DrawScope, R> T.withNode(
renderNode: RenderNode,
draw: T.() -> R
): R {
contract {
callsInPlace(draw, InvocationKind.EXACTLY_ONCE)
}
val composeCanvas = drawContext.canvas
val nativeCanvas = composeCanvas.androidCanvas
val result: R
renderNode.withRecording { rnCanvas ->
composeCanvas.androidCanvas = rnCanvas
result = draw()
composeCanvas.androidCanvas = nativeCanvas
}
nativeCanvas.drawRenderNode(renderNode)
return result
}
private fun mutableOffsetStateDelegateOf(value: Offset) = OffsetStateDelegate(value = value)
@JvmInline
private value class OffsetStateDelegate private constructor(
private val state: MutableLongState
) {
constructor(value: Offset) : this(
state = mutableLongStateOf(value.toLongValue())
)
operator fun getValue(thisObj: Any?, property: KProperty<*>): Offset =
state.longValue.let { boundsValue ->
Offset(
x = unpackFloat1(boundsValue),
y = unpackFloat2(boundsValue)
)
}
operator fun setValue(
thisObj: Any?,
property: KProperty<*>,
value: Offset
) {
state.longValue = value.toLongValue()
}
@Immutable
private companion object {
@Suppress("NOTHING_TO_INLINE")
@Stable
private inline fun Offset.toLongValue() = packFloats(x, y)
}
}
License: No specific license, I'm offering this for any kind of use or modification.
sh...@gmail.com <sh...@gmail.com> #64
Hi, I’m the author of the Toolkit library, which contains a custom blur modifier that can blur the background behind any composable in real time. It works on any version of Android.
I wrote an article that shows how to use my library and how to create stunning glassmorphic designs for your Android apps. You can read it here: Blurring the Lines: How to Achieve a Glassmorphic Design with Jetpack Compose.
link:
I hope you find my library and article helpful. If you have any feedback or suggestions, please let me know.
Description
It was mentioned in a Dev YouTube video that Compose will be design system agnostic. In order to achieve that, it would make sense to natively support blurring as some design systems use it. Also, there are designers that don't necessary use MD while designing apps for Android,
and in that case if they use blur usually they either have to find an alternative
or the devs have a really hard time getting it to work consistently across all devices.
Ideally, blurring would be supported on all container layouts such as CardView, ConstraintLayout etc. (all that inherit from ViewGroup?). The API should be something like this:
Modifier.blur(radius = 16.dp, dynamic = true)
Dynamic = true/false would mean either realtime blurring in moving/scrolling layouts or one-time-blur on static content to minimize performance impact when dynamic blurring is not needed.
As for the current problems of blurring in Android - the only way to implement it is using RenderScript or an external library (from which most rely on RenderScript anyway).
When using it the API is usually complicated and after some testing on different devices it seems
as the performance impact depends on the chipset where Snapdragon devices have no perceivable lag and for ex. Kirin and Exynos devices drop frames to a point where it is not feasable to keep blur in UI.