A beautiful Jetpack Compose UI library for creating fluid motion effects and animations with organic ink bleed reveals.
InkFlow is a modern Android library built with Jetpack Compose that provides elegant and performant fluid motion effects for your Android applications. Designed with AGSL (Android Graphics Shading Language) support, InkFlow uses advanced procedural noise shaders on API 33+ (Android 13+) to create organic, non-uniform ink spread patterns while gracefully falling back to alpha fade on older devices.
- π¨ Organic Ink Bleed Effects - Procedural noise-based shaders create natural, fluid motion
- π Built with Jetpack Compose - Modern declarative UI framework
- β‘ Optimized Performance - Shader caching and efficient rendering
- π― Easy to Use - Simple modifier API, works out of the box
- π± Cross-Platform Compatible - API 24+ support with automatic fallbacks
- ποΈ Highly Customizable - Control speed, origin, noise, and edge properties
- π Adaptive Design - Automatic tablet optimization
Note: Requires JitPack and JDK 17+.
Add JitPack repository to your settings.gradle.kts (or settings.gradle):
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}Or if using settings.gradle (Groovy):
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}Then add the dependency to your build.gradle.kts:
dependencies {
implementation("com.github.felixny:InkFlow:1.0.0")
}Or in build.gradle (Groovy):
dependencies {
implementation 'com.github.felixny:InkFlow:1.0.0'
}Note: You can also use specific commit hashes or branch names:
implementation("com.github.felixny:InkFlow:main-SNAPSHOT")- Latest from main branchimplementation("com.github.felixny:InkFlow:abc1234")- Specific commit hash
Apply the inkReveal modifier to any composable to create an organic ink bleed reveal effect:
import com.felixny.inkflow.inkReveal
@Composable
fun MyCard() {
var progress by remember { mutableFloatStateOf(0f) }
Card(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.inkReveal(progress = progress)
) {
// Your content here
}
}Customize the ink effect using InkFlowConfig:
import com.felixny.inkflow.InkFlowConfig
val config = InkFlowConfig(
noiseScale = 3.0f, // Controls noise detail (higher = more detail)
distortionStrength = 0.15f, // How much noise distorts the spread (0.0 to 1.0)
edgeSoftness = 0.15f, // Softness of the ink edge (0.0 to 1.0)
centerX = 0.5f, // X origin (0.0 = left, 0.5 = center, 1.0 = right)
centerY = 0.5f, // Y origin (0.0 = top, 0.5 = center, 1.0 = bottom)
speedMultiplier = 1.0f // Speed of spread (1.0 = normal, >1.0 = faster)
)
Card(
modifier = Modifier.inkReveal(
progress = progress,
config = config
)
) {
// Content
}Animate the reveal effect using Animatable:
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import kotlinx.coroutines.launch
@Composable
fun AnimatedReveal() {
var progress by remember { mutableFloatStateOf(0f) }
val animatableProgress = remember { Animatable(0f) }
val coroutineScope = rememberCoroutineScope()
// Sync animatable with state
LaunchedEffect(animatableProgress.value) {
progress = animatableProgress.value
}
Column {
Card(
modifier = Modifier.inkReveal(progress = progress)
) {
// Content
}
Button(
onClick = {
coroutineScope.launch {
animatableProgress.animateTo(
targetValue = 1f,
animationSpec = tween(2000)
)
}
}
) {
Text("Reveal")
}
}
}Use convenience constants for common origin positions:
// Start from center (default)
val config = InkFlowConfig.CENTER
// Start from top center (like a dropdown)
val config = InkFlowConfig.TOP_CENTER
// Start from bottom center (like a bottom sheet)
val config = InkFlowConfig.BOTTOM_CENTER
// Start from corners
val config = InkFlowConfig.TOP_LEFT
val config = InkFlowConfig.BOTTOM_RIGHT
// Apply with custom speed
val config = InkFlowConfig.BOTTOM_CENTER.copy(speedMultiplier = 2.0f)Automatically adjust noise scale for tablets to prevent stretching:
import androidx.compose.ui.platform.LocalConfiguration
@Composable
fun AdaptiveReveal() {
val configuration = LocalConfiguration.current
val isTablet = configuration.screenWidthDp >= 600
val config = InkFlow.createOptimizedConfig(isTablet = isTablet)
Card(
modifier = Modifier.inkReveal(
progress = 0.5f,
config = config
)
) {
// Content
}
}InkFlow uses procedural noise algorithms implemented in AGSL (Android Graphics Shading Language) to create organic, non-uniform ink spread patterns. The implementation includes:
- A gradient noise function that produces smoother, more natural-looking patterns than Perlin noise
- Provides better computational performance and visual quality
- Used as the base for creating organic distortions
- Combines multiple octaves of Simplex noise at different frequencies
- Creates complex, natural-looking patterns with varying levels of detail
- 4 octaves are used for optimal balance between detail and performance
- Base distance calculation from the origin point
- Noise-based distortion applied to create organic, non-circular spread
- Configurable distortion strength allows fine-tuning of the effect
The shader works by:
- Calculating base distance from the configurable origin point
- Applying noise distortion to create organic variation
- Normalizing distance to create a 0-1 progress range
- Applying smooth falloff with configurable edge softness
- Masking alpha based on progress to reveal/hide content
This approach creates a fluid, organic ink spread that looks natural and avoids the mechanical appearance of simple geometric shapes.
- Shader Caching: RuntimeShader instances are cached using
remember()to avoid recompilation - Uniform Updates: Only uniform values are updated during recomposition, not the shader code
- Efficient Rendering: Uses Compose's graphicsLayer with RenderEffect for hardware acceleration
- Automatic Fallback: API < 33 uses simple alpha fade (no shader overhead)
Applies an ink bleed reveal effect to a composable.
Parameters:
progress: Float- Reveal progress from 0.0 to 1.0 (automatically clamped)config: InkFlowConfig- Optional configuration (defaults provided)
Returns: A modifier that applies the ink bleed effect
Configuration class for customizing the ink reveal effect.
Properties:
noiseScale: Float- Controls the scale of the noise pattern (default:3.0f)- Higher values = more detail, better for larger screens/tablets
- Lower values = smoother, better for smaller screens
distortionStrength: Float- How much noise distorts the ink spread (default:0.15f, range: 0.0-1.0)edgeSoftness: Float- Softness of the ink edge (default:0.15f, range: 0.0-1.0)centerX: Float- X coordinate of flow origin (default:0.5f, range: 0.0-1.0)centerY: Float- Y coordinate of flow origin (default:0.5f, range: 0.0-1.0)speedMultiplier: Float- Speed of ink spread (default:1.0f, must be > 0)
Predefined Positions:
InkFlowConfig.CENTER- Center (0.5, 0.5)InkFlowConfig.TOP_CENTER- Top center (0.5, 0.0)InkFlowConfig.BOTTOM_CENTER- Bottom center (0.5, 1.0)InkFlowConfig.LEFT_CENTER- Left center (0.0, 0.5)InkFlowConfig.RIGHT_CENTER- Right center (1.0, 0.5)InkFlowConfig.TOP_LEFT- Top left (0.0, 0.0)InkFlowConfig.TOP_RIGHT- Top right (1.0, 0.0)InkFlowConfig.BOTTOM_LEFT- Bottom left (0.0, 1.0)InkFlowConfig.BOTTOM_RIGHT- Bottom right (1.0, 1.0)
Main entry point for the library.
Properties:
VERSION_NAME: String- Library version nameVERSION_CODE: Int- Library version codeMIN_AGSL_API: Int- Minimum API level for AGSL support (33)defaultConfig: InkFlowConfig- Default configuration instance
Functions:
isAgslSupported(): Boolean- Checks if device supports AGSL shaderscreateOptimizedConfig(isTablet: Boolean): InkFlowConfig- Creates optimized config for device type
- API 33+ (Android 13+): Uses AGSL shaders for the full organic ink bleed effect
- API 24-32: Automatically falls back to a simple alpha fade effect
- The library handles API detection automatically - no additional code needed!
The repository includes a comprehensive sample app (:app module) with 8 different use cases:
- Profile Card - Reveal profile information with ink bleed
- Image Reveal - Animate image appearance with organic spread
- Text Animation - Sequential text reveal with staggered effects
- Button Press - Interactive button with ink effect
- Loading State - Show loading progress with ink reveal
- Card Stack - Reveal stacked cards sequentially
- Modal Dialog - Animate modal appearance
- Flow Control - Test speed and origin position settings
Run the sample app to see all examples in action!
Contributions are welcome and greatly appreciated! Here's how you can help:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and ensure code follows the project style
- Add tests if applicable
- Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
- Follow Kotlin coding conventions
- Use meaningful variable and function names
- Add KDoc documentation for public APIs
- Ensure all public functions are properly documented
- Write tests for new features
If you find a bug or have a feature request, please open an issue on GitHub with:
- Clear description of the problem/feature
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Android version and device information (if applicable)
- Keep PRs focused and small when possible
- Include tests for new functionality
- Update documentation as needed
- Ensure CI checks pass
Copyright 2024 Felix Ny
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.
Felix Ny

