paint-brush
Design Your Own Shimmering UI in 25 Lines of Code with Composeby@leonidivankin
648 reads
648 reads

Design Your Own Shimmering UI in 25 Lines of Code with Compose

by Leonid IvankinJuly 17th, 2022
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The full algorithm for adding compose to a project can be found here: eYou can copy the full code of the ShimmeringActivity. The color can be changed to white, as it should be in your project. The width, height, cornerRadius and radius of the circle, as in any other compose view, is necessary to make the animation for all elements work synchronously. The value of translateAnim will change over time.

Company Mentioned

Mention Thumbnail
featured image - Design Your Own Shimmering UI in 25 Lines of Code with Compose
Leonid Ivankin HackerNoon profile picture


Shimmering is present in almost all applications that have networking. When the task of making it on compose came up, given that the latter simplifies the work with the view, I did not find a solution. All options were reduced to downloading fairly heavy libraries or writing a lot of code. However, I managed to find a solution, which allows me to make a shimmering on compose in a few lines of code.

Observations and Assumptions

  • my solution won't cover 100% of the cases of shimmering, but it's easy to refine;
  • my solution will be built around the Brush.linearGradient() method. There are probably more options, but this one seemed the easiest to me;
  • Don't forget to include compose if it's a new project, like buildFeatures {compose true}, composeOptions and others. The full algorithm for adding compose to a project can be found here: https://developer.android.com/jetpack/compose/setup.

Description

I am first attaching the full code of the ShimmeringActivity. You can copy it into your project and run it: https://gist.github.com/45ccb35b9ae80fbddcd2ffc2c0678c44

As a result, you should have the following animation:



Don't be intimidated by the orange and blue shimmering colors. I did this on purpose so that they would be easily distinguishable. The color can be changed to white, as it should be in your project.


Let's go through the code. Let's start with an easy one:

ShimmeringCompose(
   modifier = Modifier
       .width(300.dp)
       .height(150.dp),
   cornerRadius = 16.dp
)

width, height, cornerRadius - the width, height and radius of the circle, as in any other compose view.


val screenWidth = LocalConfiguration.current.screenWidthDp.dp

screenWidth - the found screen width. This is necessary to make the animation for all elements work synchronously. Otherwise, if there are several rectangles with different widths on the same screen, the shimmering for them will be out of sync, because the animation will run different distances at the same time.


val translateAnim by rememberInfiniteTransition().animateFloat(
   initialValue = 0f,
   targetValue = screenWidth.px,
   animationSpec = infiniteRepeatable(
       animation = tween(durationMillis = 1500, easing = FastOutSlowInEasing)
   )
)

rememberInfiniteTransition().animateFloat(), infiniteRepeatable() - methods for creating infinite animation.


initialValue, targetValue - start and end value of the animation.

tween() - method for configuring the animation.

durationMillis - animation time.

easing - interpolation of the animation.

The value of translateAnim will change over time from initialValue to targetValue. The translateAnim change logs:


D: ShimmeringCompose: 1.2176096
D: ShimmeringCompose: 2.7215502
D: ShimmeringCompose: 7.4594655
D: ShimmeringCompose: 11.377052
D: ShimmeringCompose: 21.533459
D: ShimmeringCompose: 27.730642
D: ShimmeringCompose: 34.646053
D: ShimmeringCompose: 53.458187
D: ShimmeringCompose: 64.20549
D: ShimmeringCompose: 91.909676
D: ShimmeringCompose: 107.303856


val shimmerColorShades = listOf(Color(0xFFF37F19), Color(0xFF007CFF), Color(0xFFF37F19))


With this list the colors are set: blue in the center and orange on the edges.

val brush = Brush.linearGradient(
   colors = shimmerColorShades,
   start = Offset(translateAnim, translateAnim),
   end = Offset(translateAnim + 70.dp.px, translateAnim + 35.dp.px)
)

Brush.linearGradient() is a method for creating a gradient.

In colors we assign the sheet of colors created above.

Offset() is a shell class for assigning x and y coordinates

The values 70 and 35 are chosen depending on the shimmering design in Figma.

Box(modifier = modifier.background(brush = brush, shape = RoundedCornerShape(cornerRadius)))

Create a Box object and assign to it in background() the values of brush and RoundedCornerShape to create rounded corners.


You will also need a utility method to translate dp to px:

val Dp.px: Float
    @Composable
    get() = with(LocalDensity.current) { [email protected]() }

As a result, we get animation, as in the gif-picture at the beginning of the article.

A few words about Brush.linearGradient()

To understand how Brush.linearGradient() works, change the code in the ShimmeringActivity to the following:

val brush = Brush.linearGradient(
   colors = shimmerColorShades,
   start = Offset(0f, 0f),
   end = Offset(0f, 100f)
)

You will end up with the following picture:

If you set x_start = x_end, the shimmer bar will be parallel to the x-axis. The thickness will be y_end - y_start, in this case 100f. Now insert the following code:

val brush = Brush.linearGradient(
   colors = shimmerColorShades,
   start = Offset(0f, 0f),
   end = Offset(100f, 0f)
)

You will get this picture:

If you set the y_start = y_end coordinates, the shimmer bar will be parallel to the y-axis. The thickness will be x_end - x_start, in this case 100f.

Thus, by manipulating the start and end coordinates, you can achieve the desired result.

Conclusion

As you can see, creating shimmering took about 25 lines of code, not including the ShimmeringActivity code and the utility method for translating to px. From my point of view, pulling in an additional library to create the shimmering makes no sense. Yes, my example isn't exactly clean from a code point of view: some variables aren't moved here and critical situations aren't handled. But it can be easily modified to the needs of your project.