Skip to content

Commit

Permalink
Merge pull request #394 from skydoves/kmp/landscapist-animation
Browse files Browse the repository at this point in the history
Support KMP for the landscapist-animation module
  • Loading branch information
skydoves authored Jan 3, 2024
2 parents f95cd26 + 34b550c commit e080a0f
Show file tree
Hide file tree
Showing 18 changed files with 383 additions and 29 deletions.
34 changes: 34 additions & 0 deletions landscapist-animation/api/android/landscapist-animation.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
public abstract interface class com/skydoves/landscapist/animation/circular/CircularRevealFinishListener {
public abstract fun onFinish ()V
}

public final class com/skydoves/landscapist/animation/circular/CircularRevealImageKt {
public static final field DefaultCircularRevealDuration I
public static final fun CircularRevealImage (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;Ljava/lang/String;FLandroidx/compose/ui/graphics/ColorFilter;Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;Landroidx/compose/runtime/Composer;II)V
}

public final class com/skydoves/landscapist/animation/circular/CircularRevealPlugin : com/skydoves/landscapist/plugins/ImagePlugin$PainterPlugin {
public static final field $stable I
public fun <init> ()V
public fun <init> (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;)V
public synthetic fun <init> (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
public final fun component2 ()Lcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;
public fun compose (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter;
public final fun copy (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;)Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;
public static synthetic fun copy$default (Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;ILjava/lang/Object;)Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;
public fun equals (Ljava/lang/Object;)Z
public final fun getDuration ()I
public final fun getOnFinishListener ()Lcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/skydoves/landscapist/animation/crossfade/CrossfadePlugin : com/skydoves/landscapist/plugins/ImagePlugin$PainterPlugin {
public static final field $stable I
public fun <init> ()V
public fun <init> (I)V
public synthetic fun <init> (IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun compose (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter;
}

34 changes: 34 additions & 0 deletions landscapist-animation/api/desktop/landscapist-animation.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
public abstract interface class com/skydoves/landscapist/animation/circular/CircularRevealFinishListener {
public abstract fun onFinish ()V
}

public final class com/skydoves/landscapist/animation/circular/CircularRevealImageKt {
public static final field DefaultCircularRevealDuration I
public static final fun CircularRevealImage (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;Ljava/lang/String;FLandroidx/compose/ui/graphics/ColorFilter;Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;Landroidx/compose/runtime/Composer;II)V
}

public final class com/skydoves/landscapist/animation/circular/CircularRevealPlugin : com/skydoves/landscapist/plugins/ImagePlugin$PainterPlugin {
public static final field $stable I
public fun <init> ()V
public fun <init> (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;)V
public synthetic fun <init> (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
public final fun component2 ()Lcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;
public fun compose (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter;
public final fun copy (ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;)Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;
public static synthetic fun copy$default (Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;ILcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;ILjava/lang/Object;)Lcom/skydoves/landscapist/animation/circular/CircularRevealPlugin;
public fun equals (Ljava/lang/Object;)Z
public final fun getDuration ()I
public final fun getOnFinishListener ()Lcom/skydoves/landscapist/animation/circular/CircularRevealFinishListener;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/skydoves/landscapist/animation/crossfade/CrossfadePlugin : com/skydoves/landscapist/plugins/ImagePlugin$PainterPlugin {
public static final field $stable I
public fun <init> ()V
public fun <init> (I)V
public synthetic fun <init> (IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun compose (Landroidx/compose/ui/graphics/ImageBitmap;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter;
}

52 changes: 37 additions & 15 deletions landscapist-animation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import com.github.skydoves.landscapist.Configuration

plugins {
id("landscapist.library.compose")
id("landscapist.library.compose.multiplatform")
id("landscapist.spotless")
}

Expand All @@ -35,6 +35,31 @@ mavenPublishing {
}
}

kotlin {
sourceSets {
all {
languageSettings.optIn("kotlin.RequiresOptIn")
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
languageSettings.optIn("com.skydoves.landscapist.InternalLandscapistApi")
}
val commonMain by getting {
dependencies {
api(project(":landscapist"))

implementation(compose.ui)
implementation(compose.runtime)
implementation(compose.foundation)
}
}

val androidMain by getting {
dependencies {
implementation(libs.androidx.core.ktx)
}
}
}
}

android {
namespace = "com.skydoves.landscapist.animation"
compileSdk = Configuration.compileSdk
Expand All @@ -52,17 +77,14 @@ baselineProfile {
}
}

dependencies {
api(project(":landscapist"))

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.foundation)

androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.compose.ui)
androidTestImplementation(libs.androidx.compose.ui.test)
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += listOf(
"-Xexplicit-api=strict",
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=com.skydoves.landscapist.InternalLandscapistApi",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import androidx.compose.ui.graphics.ImageShader
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.painter.Painter
Expand All @@ -42,12 +41,12 @@ import androidx.core.util.Pools
* @param imageBitmap an image bitmap for loading for the content.
* @param painter an image painter to draw an [ImageBitmap] into the provided canvas.
*/
internal class CircularRevealPainter(
private val imageBitmap: ImageBitmap,
private val painter: Painter,
internal actual class CircularRevealPainter actual constructor(
actual val imageBitmap: ImageBitmap,
actual val painter: Painter,
) : Painter() {

var radius by mutableStateOf(0f, policy = neverEqualPolicy())
actual var radius by mutableStateOf(0f, policy = neverEqualPolicy())

override fun DrawScope.onDraw() {
var dx = 0f
Expand All @@ -68,8 +67,8 @@ internal class CircularRevealPainter(
canvas.saveLayer(size.toRect(), paint)

val mDrawableRect = RectF(0f, 0f, size.width, size.height)
val bitmapWidth: Int = imageBitmap.asAndroidBitmap().width
val bitmapHeight: Int = imageBitmap.asAndroidBitmap().height
val bitmapWidth: Int = imageBitmap.width
val bitmapHeight: Int = imageBitmap.height

if (bitmapWidth * mDrawableRect.height() > mDrawableRect.width() * bitmapHeight) {
scale = mDrawableRect.height() / bitmapHeight.toFloat()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ import androidx.core.util.Pools
* @param imageBitmap an image bitmap for loading for the content.
* @param painter an image painter to draw an [ImageBitmap] into the provided canvas.
*/
internal class CrossfadePainter(
private val imageBitmap: ImageBitmap,
private val painter: Painter,
internal actual class CrossfadePainter actual constructor(
actual val imageBitmap: ImageBitmap,
actual val painter: Painter,
) : Painter() {

/** return the dimension size of the [painter]'s intrinsic width and height. */
override val intrinsicSize: Size get() = painter.intrinsicSize

/** color filter that will be applied to draw the [imageBitmap]. */
var transitionColorFilter by mutableStateOf<ColorFilter?>(null)
actual var transitionColorFilter by mutableStateOf<ColorFilter?>(null)

override fun DrawScope.onDraw() {
drawIntoCanvas { canvas ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:JvmName("CircularRevealImage")
@file:JvmMultifileClass

package com.skydoves.landscapist.animation.circular

import androidx.compose.foundation.Image
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Designed and developed by 2020-2023 skydoves (Jaewoong Eum)
*
* 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.
*/
package com.skydoves.landscapist.animation.circular

import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.Painter

/**
* CircularRevealPainter is a [Painter] which animates a clipping circle to reveal an image.
* Reveal animations provide users visual continuity when we show an image.
*
* @param imageBitmap an image bitmap for loading for the content.
* @param painter an image painter to draw an [ImageBitmap] into the provided canvas.
*/
internal expect class CircularRevealPainter(
imageBitmap: ImageBitmap,
painter: Painter,
) : Painter {
internal val imageBitmap: ImageBitmap
internal val painter: Painter
internal var radius: Float
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Designed and developed by 2020-2023 skydoves (Jaewoong Eum)
*
* 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.
*/
package com.skydoves.landscapist.animation.crossfade

import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.Painter

/**
* CrossfadePainter is a [Painter] that applies crossfade filter effect on the given [imageBitmap].
*
* @param imageBitmap an image bitmap for loading for the content.
* @param painter an image painter to draw an [ImageBitmap] into the provided canvas.
*/
internal expect class CrossfadePainter(
imageBitmap: ImageBitmap,
painter: Painter,
) : Painter {
internal val imageBitmap: ImageBitmap
internal val painter: Painter
internal var transitionColorFilter: ColorFilter?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Designed and developed by 2020-2023 skydoves (Jaewoong Eum)
*
* 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.
*/
package com.skydoves.landscapist.animation.circular

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.neverEqualPolicy
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.asSkiaBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.painter.Painter
import org.jetbrains.skia.Image
import org.jetbrains.skia.Matrix33

/**
* CircularRevealPainter is a [Painter] which animates a clipping circle to reveal an image.
* Reveal animations provide users visual continuity when we show an image.
*
* @param imageBitmap an image bitmap for loading for the content.
* @param painter an image painter to draw an [ImageBitmap] into the provided canvas.
*/
internal actual class CircularRevealPainter actual constructor(
actual val imageBitmap: ImageBitmap,
actual val painter: Painter,
) : Painter() {

actual var radius by mutableStateOf(0f, policy = neverEqualPolicy())

override fun DrawScope.onDraw() {
var dx = 0f
var dy = 0f
var scale: Float
val paint = Paint()

paint.asFrameworkPaint().apply {
isAntiAlias = true
isDither = true
}

drawIntoCanvas { canvas ->
// cache the paint in the internal stack.
canvas.saveLayer(size.toRect(), paint)

val mDrawableRect = Rect(offset = Offset.Zero, size = Size(size.width, size.height))
val bitmapWidth: Int = imageBitmap.width
val bitmapHeight: Int = imageBitmap.height

if (bitmapWidth * mDrawableRect.size.height > mDrawableRect.size.width * bitmapHeight) {
scale = mDrawableRect.size.height / bitmapHeight.toFloat()
dx = (mDrawableRect.size.width - bitmapWidth * scale) * 0.5f
} else {
scale = mDrawableRect.size.width / bitmapWidth.toFloat()
dy = (mDrawableRect.size.height - bitmapHeight * scale) * 0.5f
}

// resize the matrix to scale by sx and sy.
val m1 = Matrix33.makeScale(scale, scale)
val m2 = Matrix33.makeTranslate(
(dx + 0.5f) + mDrawableRect.left,
(dy + 0.5f) + mDrawableRect.top,
)
val shaderMatrix = m1.makeConcat(m2)
val shader = Image.makeFromBitmap(imageBitmap.asSkiaBitmap()).makeShader(
localMatrix = shaderMatrix,
)
val brush = ShaderBrush(shader)
// calculate radius and draw an image bitmap as a circle.
val calculatedRadius = size.width.coerceAtLeast(size.height) * radius
drawCircle(brush, calculatedRadius, Offset(size.width / 2, size.height / 2))
// restore canvas.
canvas.restore()
// resets the paint and release to the pool.
paint.asFrameworkPaint().reset()
}
}

/** return the dimension size of the [painter]'s intrinsic width and height. */
override val intrinsicSize: Size get() = painter.intrinsicSize
}
Loading

0 comments on commit e080a0f

Please sign in to comment.