Composables to display Images, or Composables as before and after composables to display differences or animate progress between 2 layouts or Composables with overlay and customization options and progress observe properties for animating before-after progress
video.mp4
To get a Git project into your build:
Step 1: Add the JitPack repository to your build file Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2: Add the dependency
dependencies {
implementation 'com.github.SmartToolFactory:Compose-BeforeAfter:<version>'
}
where <version>
is one available in
Image that takes two `ImageBitmaps as parameter and displays them based on specified order. By default Labels and Overlay is provided and overload function that accept other Composables as overlay to customize
@Composable
fun BeforeAfterImage(
modifier: Modifier = Modifier,
beforeImage: ImageBitmap,
afterImage: ImageBitmap,
enableProgressWithTouch: Boolean = true,
enableZoom: Boolean = true,
contentOrder: ContentOrder = ContentOrder.BeforeAfter,
overlayStyle: OverlayStyle = OverlayStyle(),
beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) },
afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) },
contentScale: ContentScale = ContentScale.Fit,
alignment: Alignment = Alignment.Center,
contentDescription: String? = null,
) {
}
and
@Composable
fun BeforeAfterImage(
modifier: Modifier = Modifier,
beforeImage: ImageBitmap,
afterImage: ImageBitmap,
enableProgressWithTouch: Boolean = true,
enableZoom: Boolean = true,
contentOrder: ContentOrder = ContentOrder.BeforeAfter,
@FloatRange(from = 0.0, to = 100.0) progress: Float = 50f,
onProgressChange: ((progress: Float) -> Unit)? = null,
overlayStyle: OverlayStyle = OverlayStyle(),
beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) },
afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) },
contentScale: ContentScale = ContentScale.Fit,
alignment: Alignment = Alignment.Center,
contentDescription: String? = null,
)
overloads has default DefaultOverlay
@Composable
internal fun DefaultOverlay(
width: Dp,
height: Dp,
position: Offset,
overlayStyle: OverlayStyle
)
@Composable
fun BeforeAfterImage(
modifier: Modifier = Modifier,
beforeImage: ImageBitmap,
afterImage: ImageBitmap,
enableProgressWithTouch: Boolean = true,
enableZoom: Boolean = true,
contentOrder: ContentOrder = ContentOrder.BeforeAfter,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
contentDescription: String? = null,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) },
afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) },
overlay: @Composable BeforeAfterImageScope.() -> Unit
)
and
@Composable
fun BeforeAfterImage(
modifier: Modifier = Modifier,
beforeImage: ImageBitmap,
afterImage: ImageBitmap,
enableProgressWithTouch: Boolean = true,
enableZoom: Boolean = true,
contentOrder: ContentOrder = ContentOrder.BeforeAfter,
@FloatRange(from = 0.0, to = 100.0) progress: Float = 50f,
onProgressChange: ((progress: Float) -> Unit)? = null,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
contentDescription: String? = null,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) },
afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) },
overlay: @Composable BeforeAfterImageScope.() -> Unit
)
has overlay
parameters to add another Composable to draw overlay
BeforeAfterImage(
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.fillMaxWidth()
.aspectRatio(4 / 3f),
beforeImage = imageBefore,
afterImage = imageAfter,
contentScale = contentScale
)
or
BeforeAfterImage(
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.border(3.dp, Color(0xffE91E63), RoundedCornerShape(10.dp))
.fillMaxWidth()
.aspectRatio(4 / 3f),
beforeImage = imageBefore3,
afterImage = imageAfter3,
progress = progress,
onProgressChange = {},
contentScale = contentScale,
)
- beforeImage image that show initial progress
- afterImage image that show final progress
- enableProgressWithTouch flag to enable drag and change progress with touch
- enableZoom when enabled images are zoomable and pannable
- contentOrder order of images to be drawn
- alignment determines where image will be aligned inside
BoxWithConstraints
- contentScale how image should be scaled inside Canvas to match parent dimensions.
ContentScale.Fit
for instance maintains src ratio and scales image to fit inside the parent.- alpha Opacity to be applied to
beforeImage
from 0.0f to 1.0f representing fully transparent to fully opaque respectively - colorFilter ColorFilter to apply to the
beforeImage
when drawn into the destination - filterQuality Sampling algorithm applied to the
beforeImage
when it is scaled and drawn into the destination. The default isFilterQuality.Low
which scales using a bilinear sampling algorithm - overlay is a Composable that can be matched at exact position where
beforeImage
is drawn. This is useful for drawing thumbs, cropping or another layout that should match position with the image that is scaled is drawn
Layout can draw any Composable, image, or video as before and after layout and can be zoomed and panned when [enableZoom] is true and returns a callback to animate before/after progress
@Composable
fun BeforeAfterLayout(
modifier: Modifier = Modifier,
enableProgressWithTouch: Boolean = true,
enableZoom: Boolean = true,
contentOrder: ContentOrder = ContentOrder.BeforeAfter,
overlayStyle: OverlayStyle = OverlayStyle(),
beforeContent: @Composable () -> Unit,
afterContent: @Composable () -> Unit,
beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) },
afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) },
)
and
@Composable
fun BeforeAfterLayout(
modifier: Modifier = Modifier,
enableProgressWithTouch: Boolean = true,
enableZoom: Boolean = true,
contentOrder: ContentOrder = ContentOrder.BeforeAfter,
@FloatRange(from = 0.0, to = 100.0) progress: Float = 50f,
onProgressChange: ((progress: Float) -> Unit)? = null,
overlayStyle: OverlayStyle = OverlayStyle(),
beforeContent: @Composable () -> Unit,
afterContent: @Composable () -> Unit,
beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) },
afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) },
)
BeforeAfterLayout(
modifier = Modifier
.shadow(1.dp, RoundedCornerShape(10.dp))
.fillMaxWidth()
.aspectRatio(4 / 3f),
beforeContent = {
DemoImage(imageBitmap = imageBefore)
},
afterContent = {
DemoImage(imageBitmap = imageAfter)
},
overlayStyle = OverlayStyle(
dividerColor = Color(0xffF44336),
dividerWidth = 2.dp,
thumbShape = CutCornerShape(8.dp),
thumbBackgroundColor = Color.Red,
thumbTintColor = Color.White
)
)
BeforeAfterLayout(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
progress = progress,
beforeContent = {
BeforeComposable(progress)
},
afterContent = {
AfterComposable(progress)
},
enableProgressWithTouch = false,
enableZoom = false,
beforeLabel = null,
afterLabel = null,
overlay = null
)
val transition: InfiniteTransition = rememberInfiniteTransition()
// Infinite progress animation
val progress by transition.animateFloat(
initialValue = 0f,
targetValue = 100f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 4000,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse
)
)
BeforeAfterLayout(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
progress = progress,
beforeContent = {
BeforeComposable(progress)
},
afterContent = {
AfterComposable(progress)
},
enableProgressWithTouch = false,
enableZoom = false,
beforeLabel = null,
afterLabel = null,
overlay = null
)
Warning
Note there is a bug with Exoplayer2
If you have a fix please open a PR or answer this question Both are appreciated greatly
BeforeAfterLayout(
modifier = Modifier
.fillMaxSize()
.aspectRatio(4 / 3f),
beforeContent = {
MyPlayer(
modifier = Modifier
.border(3.dp, Color.Red),
uri = "asset:///floodplain_dirty.mp4"
)
},
afterContent = {
MyPlayer(
modifier = Modifier
.border(3.dp, Color.Yellow),
uri = "asset:///floodplain_clean.mp4"
)
},
enableZoom = false
)