├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── shakenbeer │ │ └── animations │ │ ├── MainActivity.kt │ │ ├── airplane │ │ ├── Flight.kt │ │ └── FlightActivity.kt │ │ ├── biathlon │ │ ├── BiathlonActivity.kt │ │ └── BiathlonTarget.kt │ │ ├── checkboxloader │ │ └── CheckboxLoaderActivity.kt │ │ ├── composeowls │ │ ├── Babalex.kt │ │ ├── BabalexActivity.kt │ │ └── Model.kt │ │ ├── progressring │ │ ├── ProgressRing.kt │ │ └── ProgressRingActivity.kt │ │ ├── strikethru │ │ └── StrikethruActivity.kt │ │ └── theme │ │ ├── Color.kt │ │ ├── Theme.kt │ │ └── Type.kt │ └── res │ ├── animator │ └── checkbox_loader_rotation.xml │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xhdpi │ └── airplane_white_48dp.png │ ├── drawable-xxhdpi │ └── airplane_white_48dp.png │ ├── drawable-xxxhdpi │ ├── airplane_white_48dp.png │ ├── owl.png │ ├── owl_asleep.png │ ├── owl_drunk.png │ ├── owl_flower.png │ ├── owl_glasses.png │ ├── owl_tablet.png │ └── owl_witch.png │ ├── drawable │ ├── checkbox_loader.xml │ ├── ic_launcher_background.xml │ └── progress_gradient.xml │ ├── font │ └── allerta_stencil_regular.ttf │ ├── layout │ ├── activity_biathlon.xml │ ├── activity_checkbox_loader.xml │ ├── activity_flight.xml │ ├── activity_main.xml │ └── activity_progress_ring.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── integers.xml │ ├── strings.xml │ ├── styles.xml │ └── themes.xml ├── biathlon_target.gif ├── build.gradle ├── checkbox_loader.gif ├── flight.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── owls.gif ├── progress_ring.gif ├── settings.gradle └── strikethru.gif /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _So, I've decided to improve my animations._ 2 | 3 | ### Compose Strikethrough Icon 4 | 5 | ![target](https://github.com/Shakenbeer/Animations/blob/master/strikethru.gif) 6 | 7 | It's basically a copy-paste from a great article of Mark Allison: [Compose: Strikethru Animation](https://blog.stylingandroid.com/compose-strikethru-animation/) 8 | 9 | **Pro-tips:** 10 | 11 | If you want to draw on a Canvas as in good old days - ```DrawScope``` is you friend. Also, an animation progress should be a lambda, not a state -- more details on this in the article. 12 | 13 | ### Compose Pager 14 | 15 | ![target](https://github.com/Shakenbeer/Animations/blob/master/owls.gif) 16 | 17 | **Pro-tips:** 18 | 19 | I've implemented such behavior few years ago with RecyclerView. Compose code take much less space. But it's really hard to find out, how to do it with compose. 20 | [Pager from accompanist](https://google.github.io/accompanist/pager/) is used. And there is [calculateCurrentOffsetForPage](https://google.github.io/accompanist/api/pager/pager/com.google.accompanist.pager/calculate-current-offset-for-page.html), that speaks for itself. 21 | 22 | ### Biathlon target 23 | 24 | ![target](https://github.com/Shakenbeer/Animations/blob/master/biathlon_target.gif) 25 | 26 | **Pro-tips:** 27 | 28 | Use [AnimatorSet](https://developer.android.com/reference/android/animation/AnimatorSet) to chain [animations](https://developer.android.com/reference/android/animation/Animator) 29 | 30 | ```kotlin 31 | private val animations = mutableListOf() 32 | private val animatorSet = AnimatorSet() 33 | 34 | private fun startAnimation() { 35 | val list = animations as List? 36 | animatorSet.playSequentially(list) 37 | animatorSet.start() 38 | } 39 | ``` 40 | 41 | _And here is the second one, could be loading indicator for flights booking app. Try it, in real life it looks smooth, somehow converting to GIF makes it uglier._ 42 | 43 | ### Flight 44 | 45 | ![target](https://github.com/Shakenbeer/Animations/blob/master/flight.gif) 46 | 47 | **Pro-tips:** 48 | 49 | To tint bitmap but keep transparent background - use [PorterDuffColorFilter](https://developer.android.com/reference/android/graphics/PorterDuffColorFilter) 50 | 51 | In the same time, if you don't want to see anything (line in this case) behind transparent background - use [PorterDuffXfermode](https://developer.android.com/reference/android/graphics/PorterDuffXfermode) 52 | 53 | And, don't forget to set software layer type to your view, in other case PorterDuffXfermode will make background black. 54 | 55 | ```kotlin 56 | private val planeBitmap: Bitmap 57 | private val planePaint = Paint() 58 | //... 59 | init { 60 | planeBitmap = BitmapFactory.decodeResource(resources, R.drawable.airplane_white_48dp) 61 | planePaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) 62 | planePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC) 63 | setLayerType(LAYER_TYPE_SOFTWARE, null) 64 | } 65 | ``` 66 | 67 | Read more about [PorterDuff.Mode](https://developer.android.com/reference/android/graphics/PorterDuff.Mode) 68 | 69 | 70 | _I've found next animation on [uplabs.com](https://www.uplabs.com/posts/checkbox-loader), [Roy Tan](https://www.uplabs.com/royrt88) implemented it with CSS and JS, I've decided to try animated vector drawable._ 71 | 72 | ### Checkbox loader 73 | 74 | ![target](https://github.com/Shakenbeer/Animations/blob/master/checkbox_loader.gif) 75 | 76 | **Further reading:** 77 | 78 | Whole work could be done in a single xml file, you need kotlin (java) only to start animation. Read more: [Vector drawables overview](https://developer.android.com/guide/topics/graphics/vector-drawable-resources), [AnimatedVectorDrawable](https://developer.android.com/reference/android/graphics/drawable/AnimatedVectorDrawable) 79 | 80 | This example was inspired by [Nick Butcher](https://medium.com/@crafty)'s series on vector assets in Android. 81 | 82 | ### Progress Ring 83 | 84 | ![target](https://github.com/Shakenbeer/Animations/blob/master/progress_ring.gif) 85 | 86 | **Pro-tips:** 87 | 88 | When you are using PorterDuff, everything, what is already on a canvas, is considered as destination. To separate progress background and use only progress itself as destination (e.g. as mask for a gradient), one could use `Canvas.saveLayer()` and `canvas.restore` 89 | 90 | Glow effect is reachable simply with `Paint.setShadowLayer()` 91 | 92 | **Further reading:** 93 | 94 | This example is compilation of [A glowing progress ring with rounded ends for Android](https://medium.com/glose-team/a-glowing-progress-ring-with-rounded-ends-for-android-865eb0161cc1) and [this answer on SO](https://stackoverflow.com/questions/36639660/android-circular-progress-bar-with-rounded-corners/53830379#53830379) 95 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 33 9 | defaultConfig { 10 | applicationId "com.shakenbeer.animations" 11 | minSdkVersion 21 12 | targetSdkVersion 33 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 16 | vectorDrawables { 17 | useSupportLibrary true 18 | } 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | 34 | buildFeatures { 35 | compose true 36 | } 37 | 38 | composeOptions { 39 | kotlinCompilerExtensionVersion '1.3.1' 40 | } 41 | namespace 'com.shakenbeer.animations' 42 | } 43 | 44 | dependencies { 45 | implementation fileTree(dir: 'libs', include: ['*.jar']) 46 | implementation 'androidx.appcompat:appcompat:1.5.1' 47 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 48 | implementation "androidx.core:core-ktx:1.9.0" 49 | implementation 'com.google.android.material:material:1.6.1' 50 | implementation "androidx.compose.ui:ui:$compose_version" 51 | implementation "androidx.compose.material:material:$compose_version" 52 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 53 | implementation "androidx.compose.ui:ui-util:$compose_version" 54 | implementation "com.google.accompanist:accompanist-pager:0.14.0" 55 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' 56 | implementation 'androidx.activity:activity-compose:1.7.0-alpha01' 57 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 58 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 59 | } 60 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 29 | 32 | 35 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.shakenbeer.animations.airplane.FlightActivity 7 | import com.shakenbeer.animations.biathlon.BiathlonActivity 8 | import com.shakenbeer.animations.checkboxloader.CheckboxLoaderActivity 9 | import com.shakenbeer.animations.composeowls.BabalexActivity 10 | import com.shakenbeer.animations.progressring.ProgressRingActivity 11 | import com.shakenbeer.animations.strikethru.StrikethruActivity 12 | import kotlinx.android.synthetic.main.activity_main.* 13 | 14 | class MainActivity : AppCompatActivity() { 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_main) 19 | biathlonTextView.setOnClickListener { 20 | startActivity(Intent(this@MainActivity, BiathlonActivity::class.java)) 21 | } 22 | flightTextView.setOnClickListener { 23 | startActivity(Intent(this@MainActivity, FlightActivity::class.java)) 24 | } 25 | checkboxLoaderTextView.setOnClickListener { 26 | startActivity(Intent(this@MainActivity, CheckboxLoaderActivity::class.java)) 27 | } 28 | progressRingTextView.setOnClickListener { 29 | startActivity(Intent(this@MainActivity, ProgressRingActivity::class.java)) 30 | } 31 | composePagerTextView.setOnClickListener { 32 | startActivity(Intent(this@MainActivity, BabalexActivity::class.java)) 33 | } 34 | composeStrikethruTextView.setOnClickListener { 35 | startActivity(Intent(this@MainActivity, StrikethruActivity::class.java)) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/airplane/Flight.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.airplane 2 | 3 | import android.animation.ValueAnimator 4 | import android.animation.ValueAnimator.INFINITE 5 | import android.animation.ValueAnimator.RESTART 6 | import android.content.Context 7 | import android.content.res.Resources 8 | import android.graphics.* 9 | import android.util.AttributeSet 10 | import android.view.View 11 | import android.view.animation.AccelerateDecelerateInterpolator 12 | import com.shakenbeer.animations.R 13 | import kotlin.math.max 14 | 15 | class Flight @JvmOverloads constructor( 16 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 17 | ) : View(context, attrs, defStyleAttr) { 18 | 19 | private val h = HEIGHT_DP.toPx 20 | private val color: Int 21 | private val flightDuration: Int 22 | private val linePaint = Paint() 23 | private val planeBitmap: Bitmap 24 | private val planePaint = Paint() 25 | private var planeX: Float 26 | private var anim: ValueAnimator? = null 27 | private val planeInterpolator = AccelerateDecelerateInterpolator() 28 | 29 | init { 30 | val a = context.theme.obtainStyledAttributes(attrs, R.styleable.Flight, 0, 0) 31 | try { 32 | color = a.getColor(R.styleable.Flight_color, Color.BLACK) 33 | flightDuration = a.getInt(R.styleable.Flight_duration, DEFAULT_DURATION_MILLIS).let { 34 | when { 35 | it < MIN_DURATION_MILLIS -> MIN_DURATION_MILLIS 36 | it > MAX_DURATION_MILLIS -> MAX_DURATION_MILLIS 37 | else -> it 38 | } 39 | } 40 | } finally { 41 | a.recycle() 42 | } 43 | linePaint.color = color 44 | linePaint.strokeWidth = 2.toPx.toFloat() 45 | 46 | planeBitmap = BitmapFactory.decodeResource(resources, R.drawable.airplane_white_48dp) 47 | planePaint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) 48 | planePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC) 49 | setLayerType(LAYER_TYPE_SOFTWARE, null) 50 | planeX = -planeBitmap.width.toFloat() 51 | 52 | } 53 | 54 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 55 | setMeasuredDimension(max(widthMeasureSpec, MIN_WIDTH_DP.toPx), h) 56 | } 57 | 58 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 59 | super.onLayout(changed, left, top, right, bottom) 60 | anim = ValueAnimator.ofFloat(-planeBitmap.width.toFloat(), 61 | (width + planeBitmap.width).toFloat()).apply { 62 | duration = flightDuration.toLong() 63 | interpolator = planeInterpolator 64 | repeatMode = RESTART 65 | repeatCount = INFINITE 66 | addUpdateListener { animation -> 67 | planeX = animation.animatedValue as Float 68 | invalidate() 69 | } 70 | start() 71 | } 72 | } 73 | 74 | override fun onDraw(canvas: Canvas) { 75 | canvas.drawLine(0f, h.toFloat() / 2, width.toFloat(), h.toFloat() / 2, linePaint) 76 | canvas.drawBitmap(planeBitmap, planeX, 0f, planePaint) 77 | } 78 | 79 | override fun onDetachedFromWindow() { 80 | super.onDetachedFromWindow() 81 | anim?.cancel() 82 | planeBitmap.recycle() 83 | } 84 | 85 | companion object { 86 | private const val HEIGHT_DP = 48 87 | private const val MIN_WIDTH_DP = 48 88 | private const val MIN_DURATION_MILLIS = 300 89 | private const val DEFAULT_DURATION_MILLIS = 2000 90 | private const val MAX_DURATION_MILLIS = 4000 91 | 92 | private val Int.toPx: Int 93 | get() = (this * Resources.getSystem().displayMetrics.density).toInt() 94 | } 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/airplane/FlightActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.airplane 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | 6 | import com.shakenbeer.animations.R 7 | 8 | class FlightActivity : AppCompatActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | setContentView(R.layout.activity_flight) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/biathlon/BiathlonActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.biathlon 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | 6 | import com.shakenbeer.animations.R 7 | 8 | class BiathlonActivity : AppCompatActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | setContentView(R.layout.activity_biathlon) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/biathlon/BiathlonTarget.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.biathlon 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorListenerAdapter 5 | import android.animation.AnimatorSet 6 | import android.animation.ValueAnimator 7 | import android.annotation.SuppressLint 8 | import android.content.Context 9 | import android.content.res.Resources 10 | import android.graphics.Canvas 11 | import android.graphics.Color 12 | import android.graphics.Paint 13 | import android.graphics.RectF 14 | import android.util.AttributeSet 15 | import android.view.View 16 | import android.view.animation.LinearInterpolator 17 | 18 | @SuppressLint("Recycle") 19 | class BiathlonTarget @JvmOverloads constructor( 20 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 21 | ) : View(context, attrs, defStyleAttr) { 22 | 23 | private val coef = 0.15 24 | private val closingMillis = 500L 25 | private val openingMillis = 1000L 26 | private val delayMillis = 200L 27 | 28 | private val w: Int = (WIDTH_MM * coef).toInt().toPx 29 | private val h: Int = (HEIGHT_MM * coef).toInt().toPx 30 | private val ds: Int = (DIAMETER_SMALL_MM * coef).toInt().toPx 31 | private val d: Int = (DIAMETER_MM * coef).toInt().toPx 32 | private val gap: Int = (GAP_MM * coef).toInt().toPx 33 | 34 | private val targetRect = RectF(0f, 0f, w.toFloat(), h.toFloat()) 35 | private val targetPaint = Paint() 36 | 37 | private val centerY = (h / 2).toFloat() 38 | private val centers by lazy { 39 | (1..5).map { Pair(((gap + d / 2) * it + (d / 2) * (it - 1)).toFloat(), centerY) }.toTypedArray() 40 | } 41 | private val ringPaint = Paint(Paint.ANTI_ALIAS_FLAG) 42 | private val diskPaint = Paint(Paint.ANTI_ALIAS_FLAG) 43 | private val smallDiskPaint = Paint(Paint.ANTI_ALIAS_FLAG) 44 | 45 | private val lidCenterY = h.toFloat() + 1.toPx 46 | 47 | private val lidCenters by lazy { 48 | (1..5).map { Pair(((gap + d / 2) * it + (d / 2) * (it - 1)).toFloat(), lidCenterY) }.toTypedArray() 49 | } 50 | private val lidPaint = Paint(Paint.ANTI_ALIAS_FLAG) 51 | 52 | private var isAnimating = false 53 | 54 | private val animations = mutableListOf() 55 | private val animatorSet = AnimatorSet() 56 | 57 | init { 58 | targetPaint.style = Paint.Style.FILL 59 | targetPaint.color = Color.WHITE 60 | 61 | ringPaint.style = Paint.Style.STROKE 62 | ringPaint.color = Color.BLACK 63 | ringPaint.strokeWidth = 1.toPx.toFloat() 64 | 65 | diskPaint.style = Paint.Style.FILL 66 | diskPaint.color = 0xFF666666.toInt() 67 | 68 | smallDiskPaint.style = Paint.Style.FILL 69 | smallDiskPaint.color = Color.BLACK 70 | 71 | lidPaint.style = Paint.Style.FILL 72 | lidPaint.color = Color.WHITE 73 | 74 | for (i in 0..5) { 75 | animations.add(run { 76 | if (i < 5) { 77 | val anim = ValueAnimator.ofFloat(lidCenterY, centerY) 78 | anim.duration = closingMillis 79 | anim.addUpdateListener { 80 | lidCenters[i] = lidCenters[i].copy(second = it.animatedValue as Float) 81 | invalidate() 82 | } 83 | anim 84 | } else { 85 | val anim = ValueAnimator.ofFloat(centerY, lidCenterY) 86 | anim.startDelay = delayMillis 87 | anim.duration = openingMillis 88 | anim.interpolator = LinearInterpolator() 89 | anim.addUpdateListener { 90 | for (j in lidCenters.indices) { 91 | lidCenters[j] = lidCenters[j].copy(second = it.animatedValue as Float) 92 | } 93 | invalidate() 94 | } 95 | anim 96 | } 97 | }) 98 | } 99 | 100 | animatorSet.startDelay = delayMillis 101 | animatorSet.addListener(object : AnimatorListenerAdapter() { 102 | override fun onAnimationEnd(animation: Animator) { 103 | if (isAnimating) { 104 | animatorSet.start() 105 | } 106 | } 107 | }) 108 | } 109 | 110 | 111 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 112 | setMeasuredDimension(w, h) 113 | } 114 | 115 | override fun onDraw(canvas: Canvas) { 116 | canvas.drawRect(targetRect, targetPaint) 117 | centers.forEach { 118 | canvas.drawCircle(it.first, it.second, d.toFloat() / 2, ringPaint) 119 | canvas.drawCircle(it.first, it.second, d.toFloat() / 2, diskPaint) 120 | canvas.drawCircle(it.first, it.second, ds.toFloat() / 2, smallDiskPaint) 121 | } 122 | if (isAnimating) { 123 | lidCenters.forEach { canvas.drawCircle(it.first, it.second, d.toFloat() / 2, lidPaint) } 124 | } 125 | } 126 | 127 | override fun onAttachedToWindow() { 128 | super.onAttachedToWindow() 129 | startAnimation() 130 | } 131 | 132 | override fun onDetachedFromWindow() { 133 | super.onDetachedFromWindow() 134 | stopAnimation() 135 | } 136 | 137 | private fun startAnimation() { 138 | isAnimating = true 139 | for (i in lidCenters.indices) { 140 | lidCenters[i] = Pair(((gap + d / 2) * (i + 1) + (d / 2) * i).toFloat(), lidCenterY) 141 | } 142 | val list = animations as List? 143 | animatorSet.playSequentially(list) 144 | animatorSet.start() 145 | } 146 | 147 | private fun stopAnimation() { 148 | isAnimating = false 149 | animatorSet.end() 150 | } 151 | 152 | companion object { 153 | private const val WIDTH_MM = 1175 154 | private const val HEIGHT_MM = 310 155 | private const val DIAMETER_SMALL_MM = 45 156 | private const val DIAMETER_MM = 115 157 | private const val GAP_MM = 100 158 | 159 | val Int.toPx: Int 160 | get() = (this * Resources.getSystem().displayMetrics.density).toInt() 161 | } 162 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/checkboxloader/CheckboxLoaderActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.checkboxloader 2 | 3 | import android.graphics.drawable.AnimatedVectorDrawable 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.text.util.Linkify 7 | import com.shakenbeer.animations.R 8 | import kotlinx.android.synthetic.main.activity_checkbox_loader.* 9 | 10 | class CheckboxLoaderActivity : AppCompatActivity() { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_checkbox_loader) 15 | Linkify.addLinks(textView, Linkify.WEB_URLS) 16 | } 17 | 18 | override fun onResume() { 19 | super.onResume() 20 | val vDrawable = loaderImageView.drawable 21 | if (vDrawable is AnimatedVectorDrawable) { 22 | vDrawable.start() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/composeowls/Babalex.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.composeowls 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.graphics.graphicsLayer 8 | import androidx.compose.ui.res.painterResource 9 | import androidx.compose.ui.unit.dp 10 | import androidx.compose.ui.util.lerp 11 | import com.google.accompanist.pager.ExperimentalPagerApi 12 | import com.google.accompanist.pager.HorizontalPager 13 | import com.google.accompanist.pager.calculateCurrentOffsetForPage 14 | import com.google.accompanist.pager.rememberPagerState 15 | import kotlin.math.absoluteValue 16 | import kotlin.math.sign 17 | 18 | @ExperimentalPagerApi 19 | @Composable 20 | fun Babalex(size: Int) { 21 | val pagerState = rememberPagerState( 22 | pageCount = owls.size, 23 | initialOffscreenLimit = 2 24 | ) 25 | 26 | HorizontalPager( 27 | state = pagerState, 28 | itemSpacing = (size / 12).dp, 29 | modifier = Modifier 30 | .fillMaxSize() 31 | .height(size.dp) 32 | ) { page: Int -> 33 | 34 | val owlImage = owls[page].image 35 | 36 | Box( 37 | modifier = Modifier 38 | .graphicsLayer { 39 | // Calculate the absolute offset for the current page from the 40 | // scroll position. We use the absolute value which allows us to mirror 41 | // any effects for both directions 42 | val signedPageOffset = calculateCurrentOffsetForPage(page) 43 | val pageOffset = signedPageOffset.absoluteValue 44 | 45 | // We animate the scaleX + scaleY, between 35% and 100% 46 | lerp( 47 | start = 0.35f, 48 | stop = 1f, 49 | fraction = 1f - pageOffset.coerceIn(0f, 1f) 50 | ).also { scale -> 51 | scaleX = scale 52 | scaleY = scale 53 | } 54 | 55 | translationX = signedPageOffset.sign * (size / 2).dp.toPx() * (1 - scaleX) 56 | translationY = (size / 2).dp.toPx() * (1 - scaleY) 57 | } 58 | .fillMaxWidth(0.6f) 59 | .aspectRatio(1f) 60 | ) { 61 | Image( 62 | painter = painterResource(id = owlImage), 63 | contentDescription = null, 64 | modifier = Modifier.matchParentSize() 65 | ) 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/composeowls/BabalexActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.composeowls 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Surface 9 | import com.google.accompanist.pager.ExperimentalPagerApi 10 | import com.shakenbeer.animations.theme.AnimationsTheme 11 | 12 | class BabalexActivity : ComponentActivity() { 13 | @ExperimentalPagerApi 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContent { 17 | AnimationsTheme { 18 | // A surface container using the 'background' color from the theme 19 | Surface(color = MaterialTheme.colors.background) { 20 | Box { 21 | Babalex(200) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/composeowls/Model.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.composeowls 2 | 3 | import com.shakenbeer.animations.R 4 | 5 | interface Bird { 6 | val image: Int 7 | val name: String 8 | } 9 | 10 | class Owl(override val image: Int, override val name: String): Bird 11 | 12 | val owls = listOf(Owl(R.drawable.owl,"Pretty Owl"), 13 | Owl(R.drawable.owl_asleep, "Sleeping Owl"), 14 | Owl(R.drawable.owl_drunk, "Drunk Owl"), 15 | Owl(R.drawable.owl_flower, "Spring Owl"), 16 | Owl(R.drawable.owl_glasses, "Myopia Owl"), 17 | Owl(R.drawable.owl_tablet, "Internet Owl"), 18 | Owl(R.drawable.owl_witch, "Witch Owl")) -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/progressring/ProgressRing.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.progressring 2 | 3 | import android.content.Context 4 | import android.content.res.Resources 5 | import android.graphics.Bitmap 6 | import android.graphics.Canvas 7 | import android.graphics.Color 8 | import android.graphics.Paint 9 | import android.graphics.PorterDuff 10 | import android.graphics.PorterDuffXfermode 11 | import android.graphics.RectF 12 | import android.graphics.drawable.BitmapDrawable 13 | import android.graphics.drawable.Drawable 14 | import android.util.AttributeSet 15 | import android.view.View 16 | import androidx.annotation.ColorInt 17 | import androidx.annotation.FloatRange 18 | import androidx.core.content.res.use 19 | import com.shakenbeer.animations.R 20 | import kotlin.math.min 21 | 22 | /** 23 | * https://stackoverflow.com/a/53830379/276074 24 | */ 25 | @Suppress("MemberVisibilityCanBePrivate") 26 | class ProgressRing @JvmOverloads constructor( 27 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 28 | ) : View(context, attrs, defStyleAttr) { 29 | 30 | private val progressPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 31 | style = Paint.Style.STROKE 32 | } 33 | private val backgroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 34 | style = Paint.Style.STROKE 35 | } 36 | private val bitmapPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 37 | xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) 38 | } 39 | 40 | private val progressRect = RectF() 41 | private val fullRect = RectF() 42 | private val startAngle = -90f 43 | private val maxAngle = 360f 44 | private val minProgress = 0f 45 | private var currentProgress = 0f 46 | private val maxProgress = 100f 47 | private val minWidth = 1f.toPx 48 | private val maxWidth = 32f.toPx 49 | 50 | private var inset: Float = 0f 51 | @ColorInt 52 | var glowColor: Int = Color.WHITE 53 | 54 | private val glowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 55 | style = Paint.Style.STROKE 56 | } 57 | 58 | private var diameter = 0f 59 | private var angle = 0f 60 | 61 | private var progressBitmap: Bitmap? = null 62 | 63 | init { 64 | context.theme.obtainStyledAttributes(attrs, R.styleable.ProgressRing, 0, 0).use { 65 | setProgress(it.getFloat(R.styleable.ProgressRing_progress, 0f)) 66 | setProgressColor(it.getColor(R.styleable.ProgressRing_progressColor, Color.MAGENTA)) 67 | setProgressBackgroundColor(it.getColor(R.styleable.ProgressRing_progressBackgroundColor, Color.LTGRAY)) 68 | setProgressWidth(it.getDimension(R.styleable.ProgressRing_progressWidth, 16f.toPx)) 69 | setRounded(it.getBoolean(R.styleable.ProgressRing_rounded, false)) 70 | val progressDrawable = it.getDrawable(R.styleable.ProgressRing_progressDrawable) 71 | progressBitmap = progressDrawable?.toBitmap() 72 | setLayerType(LAYER_TYPE_SOFTWARE, null) 73 | glowColor = it.getColor(R.styleable.ProgressRing_glowColor, Color.WHITE) 74 | } 75 | } 76 | 77 | override fun onDraw(canvas: Canvas) { 78 | if (currentProgress >= maxProgress) { 79 | val f = (currentProgress - maxProgress) / (maxProgress * 0.25f) 80 | glowPaint.setShadowLayer( 81 | inset * f.coerceAtMost(1f), 82 | 0f, 83 | 0f, 84 | glowColor 85 | ) 86 | canvas.drawArc(progressRect, 0f, angle, false, glowPaint) 87 | } 88 | 89 | canvas.drawArc(progressRect, startAngle, maxAngle, false, backgroundPaint) 90 | canvas.saveLayer(null, null) 91 | 92 | canvas.drawArc(progressRect, startAngle, angle, false, progressPaint) 93 | progressBitmap?.let { 94 | canvas.drawBitmap(it, null, fullRect, bitmapPaint) 95 | } 96 | 97 | canvas.restore() 98 | } 99 | 100 | override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { 101 | diameter = min(width, height).toFloat() 102 | updateRect() 103 | } 104 | 105 | private fun updateRect() { 106 | val strokeWidth = backgroundPaint.strokeWidth 107 | progressRect.set(2 * strokeWidth, 2 * strokeWidth, diameter - 2 * strokeWidth, diameter - 2 * strokeWidth) 108 | fullRect.set(0f, 0f, diameter, diameter) 109 | } 110 | 111 | private fun calculateAngle(progress: Float) = maxAngle / maxProgress * progress 112 | 113 | fun setProgress(@FloatRange(from = 0.0, to = 100.0) progress: Float) { 114 | currentProgress = progress 115 | angle = calculateAngle(progress.coerceIn(minProgress, maxProgress)) 116 | invalidate() 117 | } 118 | 119 | fun setProgressColor(@ColorInt color: Int) { 120 | progressPaint.color = color 121 | invalidate() 122 | } 123 | 124 | fun setProgressBackgroundColor(@ColorInt color: Int) { 125 | backgroundPaint.color = color 126 | invalidate() 127 | } 128 | 129 | fun setProgressWidth(width: Float) { 130 | width.coerceIn(minWidth, maxWidth) 131 | progressPaint.strokeWidth = width 132 | backgroundPaint.strokeWidth = width 133 | glowPaint.strokeWidth = width 134 | inset = width 135 | updateRect() 136 | invalidate() 137 | } 138 | 139 | fun setRounded(rounded: Boolean) { 140 | progressPaint.strokeCap = if (rounded) Paint.Cap.ROUND else Paint.Cap.BUTT 141 | invalidate() 142 | } 143 | 144 | private val Float.toPx: Float 145 | get() = this * Resources.getSystem().displayMetrics.density 146 | 147 | private val Int.toPx: Int 148 | get() = (this * Resources.getSystem().displayMetrics.density).toInt() 149 | 150 | private fun Drawable.toBitmap(): Bitmap { 151 | if (this is BitmapDrawable) { 152 | return this.bitmap 153 | } 154 | 155 | val bitmap = Bitmap.createBitmap(this.intrinsicWidth.coerceAtLeast(32.toPx), this.intrinsicHeight.coerceAtLeast(32.toPx), Bitmap.Config.ARGB_8888) 156 | val canvas = Canvas(bitmap) 157 | this.setBounds(0, 0, canvas.width, canvas.height) 158 | this.draw(canvas) 159 | 160 | return bitmap 161 | } 162 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/progressring/ProgressRingActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.progressring 2 | 3 | import android.animation.ValueAnimator 4 | import android.animation.ValueAnimator.INFINITE 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.os.Bundle 7 | import android.view.animation.AccelerateDecelerateInterpolator 8 | import com.shakenbeer.animations.R 9 | import kotlinx.android.synthetic.main.activity_progress_ring.* 10 | 11 | class ProgressRingActivity : AppCompatActivity() { 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_progress_ring) 15 | } 16 | 17 | override fun onResume() { 18 | super.onResume() 19 | with(progressRing) { 20 | setProgress(0f) 21 | ValueAnimator.ofFloat(0f, 200f).apply { 22 | duration = 2000 23 | interpolator = AccelerateDecelerateInterpolator() 24 | addUpdateListener { 25 | setProgress(it.animatedValue as Float) 26 | invalidate() 27 | } 28 | repeatCount = INFINITE 29 | start() 30 | } 31 | } 32 | 33 | with(progressRing2) { 34 | setProgress(0f) 35 | ValueAnimator.ofFloat(75f, 150f).apply { 36 | duration = 1000 37 | interpolator = AccelerateDecelerateInterpolator() 38 | addUpdateListener { 39 | setProgress(it.animatedValue as Float) 40 | invalidate() 41 | } 42 | repeatCount = INFINITE 43 | start() 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/strikethru/StrikethruActivity.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.strikethru 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import androidx.activity.compose.setContent 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.compose.animation.core.animateFloat 8 | import androidx.compose.animation.core.updateTransition 9 | import androidx.compose.foundation.clickable 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.layout.size 14 | import androidx.compose.foundation.text.ClickableText 15 | import androidx.compose.material.Icon 16 | import androidx.compose.material.MaterialTheme 17 | import androidx.compose.material.Surface 18 | import androidx.compose.material.icons.Icons 19 | import androidx.compose.material.icons.filled.ShoppingCart 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.getValue 22 | import androidx.compose.runtime.mutableStateOf 23 | import androidx.compose.runtime.remember 24 | import androidx.compose.runtime.setValue 25 | import androidx.compose.ui.Alignment.Companion.BottomCenter 26 | import androidx.compose.ui.Alignment.Companion.Center 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.draw.drawWithContent 29 | import androidx.compose.ui.geometry.Offset 30 | import androidx.compose.ui.geometry.center 31 | import androidx.compose.ui.graphics.BlendMode 32 | import androidx.compose.ui.graphics.Color 33 | import androidx.compose.ui.graphics.drawscope.DrawScope 34 | import androidx.compose.ui.graphics.drawscope.rotate 35 | import androidx.compose.ui.graphics.graphicsLayer 36 | import androidx.compose.ui.graphics.vector.ImageVector 37 | import androidx.compose.ui.platform.LocalUriHandler 38 | import androidx.compose.ui.text.SpanStyle 39 | import androidx.compose.ui.text.buildAnnotatedString 40 | import androidx.compose.ui.text.style.TextDecoration 41 | import androidx.compose.ui.tooling.preview.Preview 42 | import androidx.compose.ui.unit.Dp 43 | import androidx.compose.ui.unit.dp 44 | import com.shakenbeer.animations.theme.AnimationsTheme 45 | import com.shakenbeer.animations.theme.allertaFontFamily 46 | 47 | class StrikethruActivity : AppCompatActivity() { 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | super.onCreate(savedInstanceState) 50 | setContent { 51 | AnimationsTheme { 52 | Surface( 53 | modifier = Modifier.fillMaxSize(), 54 | color = MaterialTheme.colors.background 55 | ) { 56 | Box( 57 | modifier = Modifier.fillMaxSize(), 58 | contentAlignment = Center 59 | ) { 60 | StrikethruIcon() 61 | val annotatedString = buildAnnotatedString { 62 | val text = "Styling Android blog post" 63 | append(text) 64 | addStyle( 65 | style = SpanStyle( 66 | color = MaterialTheme.colors.secondary, 67 | textDecoration = TextDecoration.Underline, 68 | fontFamily = allertaFontFamily 69 | ), 70 | start = 0, 71 | end = text.length 72 | ) 73 | addStringAnnotation( 74 | tag = "URL", 75 | annotation = "https://blog.stylingandroid.com/compose-strikethru-animation/", 76 | start = 0, 77 | end = text.length 78 | ) 79 | } 80 | val uriHandler = LocalUriHandler.current 81 | ClickableText( 82 | modifier = Modifier.align(BottomCenter).padding(16.dp), 83 | text = annotatedString, 84 | onClick = { 85 | annotatedString.getStringAnnotations("URL", it, it) 86 | .firstOrNull()?.let { annotation -> 87 | uriHandler.openUri(annotation.item) 88 | } 89 | }) 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | @Composable 98 | fun StrikethruIcon( 99 | modifier: Modifier = Modifier, 100 | imageVector: ImageVector = Icons.Filled.ShoppingCart 101 | ) { 102 | Surface(color = MaterialTheme.colors.background) { 103 | var enabled by remember { mutableStateOf(false) } 104 | val transition = updateTransition(enabled, label = "Transition") 105 | val progress by transition.animateFloat(label = "Progress") { state -> 106 | if (state) 1f else 0f 107 | } 108 | val overlay = StrikethruOverlay( 109 | color = MaterialTheme.colors.primary, 110 | widthDp = 4.dp, 111 | getProgress = { progress } 112 | ) 113 | Icon( 114 | modifier = modifier 115 | .clickable { enabled = !enabled } 116 | .padding(8.dp) 117 | .animatedOverlay(overlay) 118 | .padding(12.dp) 119 | .size(52.dp), 120 | imageVector = imageVector, 121 | tint = MaterialTheme.colors.primary, 122 | contentDescription = null 123 | ) 124 | } 125 | } 126 | 127 | @Preview 128 | @Composable 129 | fun StrikethruIconPreview() { 130 | AnimationsTheme { 131 | Surface( 132 | modifier = Modifier.fillMaxSize(), 133 | color = MaterialTheme.colors.background 134 | ) { 135 | Box { 136 | StrikethruIcon() 137 | } 138 | } 139 | } 140 | } 141 | 142 | interface AnimatedOverlay { 143 | fun drawOverlay(drawScope: DrawScope) 144 | } 145 | 146 | class StrikethruOverlay( 147 | private val color: Color = Color.Black, 148 | private var widthDp: Dp = 4.dp, 149 | private val getProgress: () -> Float 150 | ) : AnimatedOverlay { 151 | 152 | @Suppress("MagicNumber") 153 | override fun drawOverlay(drawScope: DrawScope) { 154 | 155 | with(drawScope) { 156 | Log.d("NEU_DEBUG", "drawOverlay: ${size.toDpSize()}") 157 | val width = density.run { widthDp.toPx() } 158 | val halfWidth = width / 2f 159 | val progressHeight = size.height * getProgress() 160 | rotate(-45f) { 161 | drawLine( 162 | color = color, 163 | start = Offset(size.center.x + halfWidth, 0f), 164 | end = Offset(size.center.x + halfWidth, progressHeight), 165 | strokeWidth = width, 166 | blendMode = BlendMode.Clear 167 | ) 168 | drawLine( 169 | color = color, 170 | start = Offset(size.center.x - halfWidth, 0f), 171 | end = Offset(size.center.x - halfWidth, progressHeight), 172 | strokeWidth = width 173 | ) 174 | } 175 | } 176 | } 177 | } 178 | 179 | @Suppress("MagicNumber") 180 | fun Modifier.animatedOverlay(animatedOverlay: AnimatedOverlay) = this.then( 181 | Modifier 182 | .graphicsLayer { 183 | // This is required to render to an offscreen buffer 184 | // The Clear blend mode will not work without it 185 | alpha = 0.99f 186 | } 187 | .drawWithContent { 188 | drawContent() 189 | animatedOverlay.drawOverlay(this) 190 | } 191 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Blue = Color(0xFF3F51B5) 6 | val DarkBlue = Color(0xFF303F9F) 7 | val Pink = Color(0xFFFF4081) -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.theme 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.material.lightColors 5 | import androidx.compose.runtime.Composable 6 | 7 | private val LightColorPalette = lightColors( 8 | primary = Blue, 9 | primaryVariant = DarkBlue, 10 | secondary = Pink, 11 | ) 12 | 13 | @Composable 14 | fun AnimationsTheme(content: @Composable () -> Unit) { 15 | val colors = LightColorPalette 16 | 17 | MaterialTheme( 18 | colors = colors, 19 | typography = Typography, 20 | content = content 21 | ) 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shakenbeer/animations/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.shakenbeer.animations.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.Font 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.unit.sp 9 | import com.shakenbeer.animations.R 10 | 11 | 12 | val allertaFontFamily = FontFamily( 13 | Font(R.font.allerta_stencil_regular) 14 | ) 15 | 16 | val Typography = Typography( 17 | defaultFontFamily = allertaFontFamily, 18 | body1 = TextStyle( 19 | fontWeight = FontWeight.Normal, 20 | fontSize = 16.sp 21 | ), 22 | ) -------------------------------------------------------------------------------- /app/src/main/res/animator/checkbox_loader_rotation.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/airplane_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xhdpi/airplane_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/airplane_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxhdpi/airplane_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/airplane_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxxhdpi/airplane_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/owl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxxhdpi/owl.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/owl_asleep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxxhdpi/owl_asleep.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/owl_drunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxxhdpi/owl_drunk.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/owl_flower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxxhdpi/owl_flower.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/owl_glasses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxxhdpi/owl_glasses.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/owl_tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxxhdpi/owl_tablet.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/owl_witch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/drawable-xxxhdpi/owl_witch.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/checkbox_loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 25 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 45 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 64 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | 81 | 82 | 83 | 84 | 87 | 88 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/progress_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/allerta_stencil_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/font/allerta_stencil_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_biathlon.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_checkbox_loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_flight.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 32 | 33 | 44 | 45 | 56 | 57 | 68 | 69 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_progress_ring.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shakenbeer/Animations/0f1a4762cef325760d722e4758c8012bbebe07f2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 700 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Animations 3 | 1. Biathlon Target Animation 4 | 2. Flight Animation 5 | 3. Checkbox Loader Animation 6 | 4. Progress Ring 7 | 5. Compose Pager 8 | 6. Compose Strikethru Icon 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |