compose-shimmer
A library which offers a shimmering effect for Android's Jetpack Compose.
Setup
The library is available on mavenCentral()
.
dependencies {
implementation("com.valentinilk.shimmer:compose-shimmer:1.2.0")
}
Kotlin Multiplatform
Currently supported KMP targets are:
- Android
- iOS
- JVM (Desktop)
- JS (Browser)
Quick Start
A simple shimmer()
modifier is provided, which can be applied like any
other modifier in
Compose as well.
As usual, the order of the modifiers matters. Every visual defined after the shimmer()
modifier will be affected by the animation. This includes child views and other modifiers:
``` kotlin hl_lines="4 5 11" Box( modifier = Modifier .size(128.dp) .background(Color.Blue) .shimmer(), contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(64.dp) .background(Color.Red) ) }
<img src="https://user-images.githubusercontent.com/1201850/131474508-c572076c-d707-4ba1-9e84-729c1dc06f3f.gif" width="128" height="128">
``` kotlin hl_lines="4 5 11"
Box(
modifier = Modifier
.size(128.dp)
.shimmer()
.background(Color.Blue),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.size(64.dp)
.background(Color.Red)
)
}
Theming
The library includes a ShimmerTheme
which can be provided as
a local composition.
A good practice would be to integrate the theming into
your customized MaterialTheme.
There is no need to wrap every single shimmer
into a CompositionLocalProvider
.
val yourShimmerTheme = defaultShimmerTheme.copy(...)
CompositionLocalProvider(
LocalShimmerTheme provides yourShimmerTheme
) {
[...]
}
The theme can also be passed as a parameter by using the rememberShimmer(...)
function, which is
explained further down below.
The theme itself offers a few simple configurations like the shimmer's rotation
or width
.
Additionally a few unabstracted objects like an AnimationSpec
or BlendMode
are exposed. While
this violates the principales
of information hiding, it allows for some great
customizations.
For further information have a look at documentation in data class itself and have a look at
the ThemingSamples
in the sample app.
Advanced Usage
The default shimmer()
modifier creates a shimmering animation, which will traverse over the view
in a certain time. That means that the animation will have a different velocity, depending on the
size of the view.
If you apply the modifier to multiple views, each of a different size, then each shimmer will have its own velocity. This effect can be seen in the following gif:
That might not always be the desired effect, that's why the library offers a way to set the boundaries for the animation:
val shimmerInstance = rememberShimmer(shimmerBounds = ShimmerBounds.XXX)
Box(modifier = Modifier.shimmer(shimmerInstance))
ShimmerBounds.View (default)
The view's height and width will be used as the boundaries for the animation. This option was used to create the gifs shown above and should be sufficient for most use cases.
ShimmerBounds.Window
This option uses the window's height, with and coordinate system for the calculations. It will create a shimmer that travels over the whole window instead of only the views. But only views that have the shimmer modifier attached will be affected.
Be aware that this option might look odd on scrollable content, because the shimmer will be positioned relative to the window. So the shimmer will not be moved together with the content.
Column {
val shimmerInstance = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
Text("Shimmering Text", modifier = Modifier.shimmer(shimmerInstance))
Text("Non-shimmering Text")
Text("Shimmering Text", modifier = Modifier.shimmer(shimmerInstance))
}
ShimmerBounds.Custom
The downsides of the Window
option is why the ShimmerBounds.Custom
option exists.
By using this option, the shimmer and its content will not be drawn until the bounds are set
manually by using the updateBounds
method on the Shimmer
.
This can be used to attach the shimmer to a scrollable list for example. Or simply use the default
ShimmerBounds.View
option.
val shimmerInstance = rememberShimmer(ShimmerBounds.Custom)
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.onGloballyPositioned { layoutCoordinates ->
// Util function included in the library
val position = layoutCoordinates.unclippedBoundsInWindow()
shimmerInstance.updateBounds(position)
},
) {
Text("Shimmering Text", modifier = Modifier.shimmer(shimmerInstance))
Text("Non-shimmering Text")
Text("Shimmering Text", modifier = Modifier.shimmer(shimmerInstance))
}
Updating the bounds will not trigger a recomposition.
Custom Modifier
It is also possible to create custom modifiers, if the default one is not convenient enough.
One could, for example, create a modifier that takes in the animation duration as a parameter and
creates the theming on the go. The whole code can be found in the CustomModifierSample.kt
file in
the sample app.
fun Modifier.shimmer(
duration: Int
): Modifier = composed {
val shimmer = rememberShimmer(
shimmerBounds = ShimmerBounds.View,
theme = createCustomTheme(duration),
)
shimmer(customShimmer = shimmer)
}
Sample Apps
Sample apps for different platforms are included in the project. To use them, clone the repository into a folder locally.
Android
To run the Android app, simply open the project in Android Studio. Select the app
configuration and run it.
iOS
The iOS app can be build and launched by using XCode. Open the iosApp
folder with XCode as a project. Adapt the
signing in XCode to match your needs and launch the app on an emulator or iOS device.
Desktop
The desktop app can be launched by using the demo.desktop
configuration in Android Studio. Or run
./gradlew :shared:run
in the terminal.
Browser
To run the sample in the browser, simply use the demo.browser
configuration in Android Studio. Or run
./gradlew :shared:jsBrowserDevelopmentRun
in the terminal.
License
Copyright 2023 Valentin Ilk
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.