├── .gitignore
├── LICENSE
├── README.md
├── _config.yml
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── karumi
│ │ └── androidanimations
│ │ ├── MainActivity.kt
│ │ ├── MainFragment.kt
│ │ ├── base
│ │ └── BaseFragment.kt
│ │ ├── common
│ │ ├── CircularReveal.kt
│ │ ├── CircularTransform.kt
│ │ ├── CircularView.kt
│ │ ├── ColorEvaluator.kt
│ │ ├── GlobalPositionTransition.kt
│ │ ├── PaintView.kt
│ │ ├── PathProperty.kt
│ │ ├── RadiusTransition.kt
│ │ └── TextResize.kt
│ │ ├── coordinatorlayout
│ │ ├── CoordinatorLayoutExample.kt
│ │ ├── CoordinatorLayoutExerciseFragment.kt
│ │ ├── CoordinatorLayoutFragment.kt
│ │ ├── CustomBehaviorFragment.kt
│ │ ├── DefaultBehaviorFragment.kt
│ │ └── ParallaxBehavior.kt
│ │ ├── drawablegraphicsanimation
│ │ ├── DrawableGraphicsAnimationFragment.kt
│ │ ├── DrawableGraphicsExerciseAnimation.kt
│ │ ├── DrawableGraphicsLottieAnimation.kt
│ │ └── DrawableGraphicsSimpleAnimation.kt
│ │ ├── extensions
│ │ ├── StringResourceResolver.kt
│ │ ├── animatePath.kt
│ │ ├── exhaustive.kt
│ │ ├── pixel.kt
│ │ └── transitions.kt
│ │ ├── layouttransition
│ │ ├── ExerciseLayoutTransition.kt
│ │ ├── LayoutTransitionFragment.kt
│ │ └── SimpleLayoutTransition.kt
│ │ ├── pathdrawing
│ │ └── PathDrawingFragment.kt
│ │ ├── propertyanimator
│ │ ├── PropertyAnimatorFragment.kt
│ │ ├── PropertyExerciseAnimator.kt
│ │ └── PropertySimpleAnimator.kt
│ │ ├── sharedelements
│ │ ├── SharedBackgroundPathTransition.kt
│ │ ├── SharedBackgroundPathTransitionFragment.kt
│ │ ├── SharedElementsExerciseTransition.kt
│ │ ├── SharedElementsExerciseTransitionFragment.kt
│ │ ├── SharedElementsFragment.kt
│ │ ├── SharedImageTransition.kt
│ │ └── SharedImageTransitionFragment.kt
│ │ └── viewanimation
│ │ ├── ViewAnimationFragment.kt
│ │ ├── ViewExerciseAnimation.kt
│ │ └── ViewSimpleAnimation.kt
│ └── res
│ ├── anim
│ └── alpha_animation.xml
│ ├── animator
│ └── value_animator.xml
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── circle_item.xml
│ ├── coordinator_exercise_item_background.xml
│ ├── frame00.xml
│ ├── frame01.xml
│ ├── frame02.xml
│ ├── frame03.xml
│ ├── frame04.xml
│ ├── frame05.xml
│ ├── frame06.xml
│ ├── frame07.xml
│ ├── frame08.xml
│ ├── frame09.xml
│ ├── frame10.xml
│ ├── frame11.xml
│ ├── frame12.xml
│ ├── frame13.xml
│ ├── frame14.xml
│ ├── frame15.xml
│ ├── frame16.xml
│ ├── frame17.xml
│ ├── frame18.xml
│ ├── frame19.xml
│ ├── frame20.xml
│ ├── frame21.xml
│ ├── frame22.xml
│ ├── frame23.xml
│ ├── frame24.xml
│ ├── frame25.xml
│ ├── frame26.xml
│ ├── frame27.xml
│ ├── frame28.xml
│ ├── frame29.xml
│ ├── frame30.xml
│ ├── ic_launcher_background.xml
│ ├── keyframe_animation.xml
│ ├── landscape.jpg
│ ├── parallax_item_background.xml
│ ├── property_animator_switch_background.xml
│ ├── property_animator_switch_ball_background.xml
│ ├── rounded_item.xml
│ ├── username_gradient.xml
│ ├── vector_landscape.jpg
│ ├── vectorized_animation.xml
│ └── view_section_background.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── fragment_animated_drawable_graphics.xml
│ ├── fragment_coordinator_layout.xml
│ ├── fragment_coordinator_layout_exercise.xml
│ ├── fragment_custom_behavior.xml
│ ├── fragment_default_behaviors.xml
│ ├── fragment_layout_transition.xml
│ ├── fragment_main.xml
│ ├── fragment_path_drawing.xml
│ ├── fragment_property_animator.xml
│ ├── fragment_shared_background_path_transition.xml
│ ├── fragment_shared_elements.xml
│ ├── fragment_shared_elements_exercise_transition.xml
│ ├── fragment_shared_image_transition.xml
│ ├── fragment_view_animation.xml
│ ├── view_coordinator_exercise.xml
│ ├── view_coordinator_layout_example.xml
│ ├── view_custom_behavior_item.xml
│ ├── view_default_behaviors_item.xml
│ ├── view_exercise_drawable_graphics_animation.xml
│ ├── view_exercise_layout_transition.xml
│ ├── view_exercise_property_animation.xml
│ ├── view_exercise_view_animation.xml
│ ├── view_layout_transition_end_state.xml
│ ├── view_layout_transition_exercise_end_state.xml
│ ├── view_layout_transition_start_state.xml
│ ├── view_lottie_drawable_graphics_animation.xml
│ ├── view_section.xml
│ ├── view_shared_background_path_transition.xml
│ ├── view_shared_elements_exercise_transition.xml
│ ├── view_shared_image_element_transition.xml
│ ├── view_simple_drawable_graphics_animation.xml
│ ├── view_simple_layout_transition.xml
│ ├── view_simple_property_animator.xml
│ └── view_simple_view_animation.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
│ ├── navigation
│ └── navigation_graph.xml
│ ├── raw
│ └── material_wave_loading.json
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ints.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | title: "AndroidAnimations"
2 | baseurl: /AndroidAnimations
3 | remote_theme: Karumi/KarumiJekyllTheme
4 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 | apply plugin: "kotlin-android"
3 | apply plugin: "kotlin-android-extensions"
4 | apply plugin: "androidx.navigation.safeargs.kotlin"
5 |
6 | android {
7 | compileSdkVersion 28
8 | defaultConfig {
9 | applicationId "com.karumi.androidanimations"
10 | minSdkVersion 19
11 | targetSdkVersion 28
12 | versionCode 1
13 | versionName "1.0"
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | vectorDrawables.useSupportLibrary = true
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | implementation fileTree(dir: "libs", include: ["*.jar"])
27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
28 | implementation "androidx.appcompat:appcompat:1.1.0-alpha04"
29 | implementation "androidx.constraintlayout:constraintlayout:1.1.3"
30 | implementation "androidx.recyclerview:recyclerview:1.0.0"
31 | implementation "androidx.navigation:navigation-fragment-ktx:2.0.0"
32 | implementation "androidx.navigation:navigation-ui-ktx:2.0.0"
33 | implementation 'com.afollestad:recyclical:0.5.2'
34 | implementation 'com.airbnb.android:lottie:3.0.0'
35 | implementation 'com.squareup.picasso:picasso:2.71828'
36 | testImplementation "junit:junit:4.12"
37 | androidTestImplementation "com.android.support.test:runner:1.0.2"
38 | androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.2"
39 | }
40 |
--------------------------------------------------------------------------------
/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 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.navigation.findNavController
6 | import androidx.navigation.ui.AppBarConfiguration
7 | import androidx.navigation.ui.setupWithNavController
8 | import kotlinx.android.synthetic.main.activity_main.*
9 |
10 | class MainActivity : AppCompatActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContentView(R.layout.activity_main)
14 | configureToolbar()
15 | }
16 |
17 | private fun configureToolbar() {
18 | val navController = findNavController(R.id.hostFragment)
19 | navController.addOnDestinationChangedListener { _, destination, _ ->
20 | collapsingToolbarLayout.isTitleEnabled = false
21 | collapsingToolbarLayout.title = destination.label
22 | }
23 | val appBarConfiguration = AppBarConfiguration(navController.graph)
24 | toolbar.setupWithNavController(navController, appBarConfiguration)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/MainFragment.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.TextView
8 | import androidx.navigation.fragment.findNavController
9 | import androidx.recyclerview.widget.LinearLayoutManager
10 | import com.afollestad.recyclical.ViewHolder
11 | import com.afollestad.recyclical.datasource.dataSourceOf
12 | import com.afollestad.recyclical.setup
13 | import com.afollestad.recyclical.withItem
14 | import com.karumi.androidanimations.base.BaseFragment
15 | import kotlinx.android.synthetic.main.fragment_main.*
16 |
17 | class MainFragment : BaseFragment() {
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater,
21 | container: ViewGroup?,
22 | savedInstanceState: Bundle?
23 | ): View? = inflater.inflate(R.layout.fragment_main, container, false)
24 |
25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
26 | super.onViewCreated(view, savedInstanceState)
27 | configureSections()
28 | }
29 |
30 | private fun configureSections() {
31 | val layoutManager = LinearLayoutManager(requireContext())
32 | val dataSource = dataSourceOf(*Section.values())
33 |
34 | allSections.setup {
35 | withLayoutManager(layoutManager)
36 | withDataSource(dataSource)
37 |
38 | withItem(R.layout.view_section) {
39 | onBind(::SectionViewHolder) { _, item ->
40 | name.text = when (item) {
41 | Section.PathDrawing -> R.string.section_path_drawing
42 | Section.ViewAnimation -> R.string.section_view_animation_framework
43 | Section.PropertyAnimation -> R.string.section_property_animation_framework
44 | Section.AnimatedVector -> R.string.section_animated_vector_drawable
45 | Section.LayoutTransition -> R.string.section_layout_transition
46 | Section.SharedElements -> R.string.section_shared_elements
47 | Section.CoordinatorLayout -> R.string.section_coordinator_layout
48 | }.resolve()
49 | }
50 |
51 | onClick { _, item ->
52 | val directions = when (item) {
53 | Section.PathDrawing -> MainFragmentDirections.actionMainFragmentToPathDrawingFragment()
54 | Section.ViewAnimation -> MainFragmentDirections.actionMainFragmentToViewAnimatorFragment()
55 | Section.PropertyAnimation -> MainFragmentDirections.actionMainFragmentToPropertyAnimationFragment()
56 | Section.AnimatedVector -> MainFragmentDirections.actionMainFragmentToDrawableGraphicsAnimationFragment()
57 | Section.LayoutTransition -> MainFragmentDirections.actionMainFragmentToLayoutTransitionFragment()
58 | Section.SharedElements -> MainFragmentDirections.actionMainFragmentToSharedElementsFragment()
59 | Section.CoordinatorLayout -> MainFragmentDirections.actionMainFragmentToCoordinatorLayoutFragment()
60 | }
61 | findNavController().navigate(directions)
62 | }
63 | }
64 | }
65 | }
66 |
67 | private enum class Section {
68 | PathDrawing, ViewAnimation, PropertyAnimation, AnimatedVector, LayoutTransition,
69 | SharedElements, CoordinatorLayout
70 | }
71 |
72 | private class SectionViewHolder(itemView: View) : ViewHolder(itemView) {
73 | val name: TextView = itemView.findViewById(R.id.sectionName)
74 | }
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/base/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.base
2 |
3 | import androidx.fragment.app.Fragment
4 | import com.karumi.androidanimations.extensions.StringResourceResolver
5 |
6 | open class BaseFragment : Fragment(), StringResourceResolver {
7 | fun Int.resolve(): String = resolve(requireContext())
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/common/CircularReveal.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.common
2 |
3 | import android.animation.Animator
4 | import android.annotation.TargetApi
5 | import android.graphics.Rect
6 | import android.os.Build
7 | import android.view.View
8 | import android.view.ViewAnimationUtils
9 | import android.view.ViewGroup
10 | import androidx.transition.Transition
11 | import androidx.transition.TransitionValues
12 |
13 |
14 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
15 | class CircularReveal : Transition() {
16 |
17 | override fun getTransitionProperties(): Array? = PROPS
18 |
19 | override fun captureStartValues(transitionValues: TransitionValues) {
20 | captureValues(transitionValues)
21 | }
22 |
23 | override fun captureEndValues(transitionValues: TransitionValues) {
24 | captureValues(transitionValues)
25 | }
26 |
27 | private fun captureValues(values: TransitionValues) {
28 | val view = values.view
29 | val bounds = Rect()
30 | bounds.left = view.left
31 | bounds.right = view.right
32 | bounds.top = view.top
33 | bounds.bottom = view.bottom
34 |
35 | values.values[BOUNDS] = bounds
36 | }
37 |
38 | override fun createAnimator(
39 | sceneRoot: ViewGroup,
40 | startValues: TransitionValues?,
41 | endValues: TransitionValues?
42 | ): Animator? {
43 | startValues ?: return null
44 | endValues ?: return null
45 |
46 | val startRect = startValues.values[BOUNDS] as Rect
47 | val endRect = endValues.values[BOUNDS] as Rect
48 |
49 | val view = endValues.view
50 |
51 | val circularTransition: Animator
52 | return if (isReveal(startRect, endRect)) {
53 | circularTransition = createReveal(view, startRect, endRect)
54 | circularTransition
55 | } else {
56 | layout(startRect, view)
57 | circularTransition = createConceal(view, startRect, endRect)
58 | circularTransition
59 | }
60 | }
61 |
62 | private fun layout(startRect: Rect, view: View) {
63 | view.layout(startRect.left, startRect.top, startRect.right, startRect.bottom)
64 | }
65 |
66 | private fun createReveal(view: View, from: Rect, to: Rect): Animator {
67 | val centerX = from.centerX()
68 | val centerY = from.centerY()
69 | val finalRadius = Math.hypot(to.width().toDouble(), to.height().toDouble()).toFloat()
70 |
71 | return ViewAnimationUtils.createCircularReveal(
72 | view, centerX - from.left, centerY - from.top,
73 | from.width() / 2f, finalRadius
74 | )
75 | }
76 |
77 | private fun createConceal(view: View, from: Rect, to: Rect): Animator {
78 | val centerX = to.centerX()
79 | val centerY = to.centerY()
80 | val initialRadius = Math.hypot(from.width().toDouble(), from.height().toDouble()).toFloat()
81 |
82 | return ViewAnimationUtils.createCircularReveal(
83 | view, centerX - to.left, centerY - to.top,
84 | initialRadius, to.width() / 2f
85 | )
86 | }
87 |
88 | private fun isReveal(startRect: Rect, endRect: Rect): Boolean {
89 | return startRect.width() < endRect.width()
90 | }
91 |
92 | companion object {
93 | private const val BOUNDS = "viewBounds"
94 |
95 | private val PROPS = arrayOf(BOUNDS)
96 | }
97 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/common/CircularTransform.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.common
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.BitmapShader
5 | import android.graphics.Canvas
6 | import android.graphics.Paint
7 | import android.graphics.Shader
8 | import com.squareup.picasso.Transformation
9 |
10 |
11 | class CircularTransform : Transformation {
12 | override fun transform(source: Bitmap): Bitmap {
13 | val size = Math.min(source.width, source.height)
14 |
15 | val x = (source.width - size) / 2
16 | val y = (source.height - size) / 2
17 |
18 | val squaredBitmap = Bitmap.createBitmap(source, x, y, size, size)
19 | if (squaredBitmap != source) {
20 | source.recycle()
21 | }
22 |
23 | val bitmap = Bitmap.createBitmap(size, size, source.config)
24 |
25 | val canvas = Canvas(bitmap)
26 | val paint = Paint()
27 | val shader = BitmapShader(
28 | squaredBitmap,
29 | Shader.TileMode.CLAMP, Shader.TileMode.CLAMP
30 | )
31 | paint.shader = shader
32 | paint.isAntiAlias = true
33 |
34 | val r = size / 2f
35 | canvas.drawCircle(r, r, r, paint)
36 |
37 | squaredBitmap.recycle()
38 | return bitmap
39 | }
40 |
41 | override fun key(): String {
42 | return "circular"
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/common/CircularView.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.common
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Path
6 | import android.graphics.PointF
7 | import android.util.AttributeSet
8 | import android.view.View
9 |
10 | class CircularView @JvmOverloads constructor(
11 | context: Context,
12 | attrs: AttributeSet? = null,
13 | defStyleAttr: Int = 0
14 | ) : View(context, attrs, defStyleAttr) {
15 |
16 | var radius: Float = 2000f
17 | set(value) {
18 | field = value
19 | invalidate()
20 | }
21 | var center: PointF = PointF(0f, 0f)
22 | set(value) {
23 | field = value
24 | invalidate()
25 | }
26 |
27 | var centerX: Float
28 | get() = center.x
29 | set(value) {
30 | center = PointF(value, center.y)
31 | }
32 |
33 | var centerY: Float
34 | get() = center.y
35 | set(value) {
36 | center = PointF(center.x, value)
37 | }
38 |
39 | override fun draw(canvas: Canvas?) {
40 | val path = Path().apply {
41 | addCircle(center.x, center.y, radius, Path.Direction.CW)
42 | }
43 |
44 | path.let { canvas?.clipPath(it) }
45 | super.draw(canvas)
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/common/ColorEvaluator.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.common
2 |
3 | import android.animation.TypeEvaluator
4 | import android.animation.ValueAnimator
5 |
6 |
7 | /*
8 | See https://gist.github.com/aritraroy/f5aac68cf42e83270d71f9bf58fac19c
9 | */
10 | class ColorEvaluator : TypeEvaluator {
11 |
12 | override fun evaluate(fraction: Float, startValue: Int, endValue: Int): Int {
13 | val startA = (startValue shr 24 and 0xff) / 255.0f
14 | var startR = (startValue shr 16 and 0xff) / 255.0f
15 | var startG = (startValue shr 8 and 0xff) / 255.0f
16 | var startB = (startValue and 0xff) / 255.0f
17 |
18 | val endA = (endValue shr 24 and 0xff) / 255.0f
19 | var endR = (endValue shr 16 and 0xff) / 255.0f
20 | var endG = (endValue shr 8 and 0xff) / 255.0f
21 | var endB = (endValue and 0xff) / 255.0f
22 |
23 | // convert from sRGB to linear
24 | startR = eocfsRGB(startR)
25 | startG = eocfsRGB(startG)
26 | startB = eocfsRGB(startB)
27 |
28 | endR = eocfsRGB(endR)
29 | endG = eocfsRGB(endG)
30 | endB = eocfsRGB(endB)
31 |
32 | // compute the interpolated color in linear space
33 | var a = startA + fraction * (endA - startA)
34 | var r = startR + fraction * (endR - startR)
35 | var g = startG + fraction * (endG - startG)
36 | var b = startB + fraction * (endB - startB)
37 |
38 | // convert back to sRGB in the [0..255] range
39 | a *= 255.0f
40 | r = oecfsRGB(r) * 255.0f
41 | g = oecfsRGB(g) * 255.0f
42 | b = oecfsRGB(b) * 255.0f
43 |
44 | return Math.round(a) shl 24 or (Math.round(r) shl 16) or (Math.round(g) shl 8) or Math.round(
45 | b
46 | )
47 | }
48 |
49 | companion object {
50 |
51 | /**
52 | * Returns an instance of `ColorEvaluator` that may be used in
53 | * [ValueAnimator.setEvaluator]. The same instance may
54 | * be used in multiple `Animator`s because it holds no state.
55 | *
56 | * @return An instance of `ColorEvaluator`.
57 | */
58 | val instance = ColorEvaluator()
59 |
60 | /**
61 | * Opto-electronic conversion function for the sRGB color space
62 | * Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
63 | */
64 | internal fun oecfsRGB(linear: Float): Float {
65 | // IEC 61966-2-1:1999
66 | return if (linear <= 0.0031308f)
67 | linear * 12.92f
68 | else
69 | (Math.pow(linear.toDouble(), 1.0 / 2.4) * 1.055 - 0.055).toFloat()
70 | }
71 |
72 | /**
73 | * Electro-optical conversion function for the sRGB color space
74 | * Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
75 | */
76 | internal fun eocfsRGB(srgb: Float): Float {
77 | // IEC 61966-2-1:1999
78 | return if (srgb <= 0.04045f) srgb / 12.92f else Math.pow(
79 | (srgb + 0.055) / 1.055,
80 | 2.4
81 | ).toFloat()
82 | }
83 | }
84 |
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/common/GlobalPositionTransition.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.common
2 |
3 | import android.animation.Animator
4 | import android.animation.ObjectAnimator
5 | import android.graphics.PointF
6 | import android.util.Property
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.transition.Transition
10 | import androidx.transition.TransitionValues
11 |
12 | class GlobalPositionTransition : Transition() {
13 | companion object {
14 | private const val POSITION_X = "RadiusTransition:positionX"
15 | private const val POSITION_Y = "RadiusTransition:positionY"
16 | private const val PARENT_WINDOW_POSITION_X = "RadiusTransition:parentPositionX"
17 | private const val PARENT_WINDOW_POSITION_Y = "RadiusTransition:parentPositionY"
18 | }
19 |
20 | override fun captureStartValues(transitionValues: TransitionValues) =
21 | captureValues(transitionValues)
22 |
23 | override fun captureEndValues(transitionValues: TransitionValues) =
24 | captureValues(transitionValues)
25 |
26 | override fun createAnimator(
27 | sceneRoot: ViewGroup,
28 | startValues: TransitionValues?,
29 | endValues: TransitionValues?
30 | ): Animator? {
31 | startValues ?: return null
32 | endValues ?: return null
33 |
34 | val startX = startValues.values[POSITION_X] as Float
35 | val startY = startValues.values[POSITION_Y] as Float
36 | val startParentWindowX = startValues.values[PARENT_WINDOW_POSITION_X] as Int
37 | val startParentWindowY = startValues.values[PARENT_WINDOW_POSITION_Y] as Int
38 | val endX = endValues.values[POSITION_X] as Float
39 | val endY = endValues.values[POSITION_Y] as Float
40 | val endParentWindowX = endValues.values[PARENT_WINDOW_POSITION_X] as Int
41 | val endParentWindowY = endValues.values[PARENT_WINDOW_POSITION_Y] as Int
42 |
43 | val startXInEndViewCoordinates = (startX + startParentWindowX) - endParentWindowX
44 | val startYInEndViewCoordinates = (startY + startParentWindowY) - endParentWindowY
45 |
46 | val path = pathMotion.getPath(
47 | startXInEndViewCoordinates,
48 | startYInEndViewCoordinates,
49 | endX,
50 | endY
51 | )
52 |
53 | return ObjectAnimator.ofFloat(
54 | endValues.view,
55 | PathProperty(object : Property(PointF::class.java, "point") {
56 | override fun set(view: View?, value: PointF?) {
57 | view ?: return
58 | value ?: return
59 |
60 | view.x = value.x
61 | view.y = value.y
62 | }
63 |
64 | override fun get(view: View?): PointF? {
65 | view ?: return null
66 | return PointF(view.x, view.y)
67 | }
68 | }, path),
69 | 0f, 1f
70 | )
71 | }
72 |
73 | private fun captureValues(transitionValues: TransitionValues) {
74 | val parent = transitionValues.view.parent as? ViewGroup ?: return
75 |
76 | val locationOfParentInWindow = IntArray(2)
77 | parent.getLocationInWindow(locationOfParentInWindow)
78 |
79 | transitionValues.values[POSITION_X] = transitionValues.view.x
80 | transitionValues.values[POSITION_Y] = transitionValues.view.y
81 | transitionValues.values[PARENT_WINDOW_POSITION_X] = locationOfParentInWindow[0]
82 | transitionValues.values[PARENT_WINDOW_POSITION_Y] = locationOfParentInWindow[1]
83 | }
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/common/PaintView.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.common
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Paint
6 | import android.graphics.Path
7 | import android.util.AttributeSet
8 | import android.view.View
9 |
10 | class PaintView @JvmOverloads constructor(
11 | context: Context,
12 | attrs: AttributeSet? = null,
13 | defStyleAttr: Int = 0
14 | ) : View(context, attrs, defStyleAttr) {
15 |
16 | private val paths = mutableListOf>()
17 |
18 | operator fun plusAssign(pathAndColor: Pair) {
19 | paths.add(pathAndColor)
20 | invalidate()
21 | }
22 |
23 | private val paint: Paint = Paint().apply {
24 | style = Paint.Style.STROKE
25 | strokeWidth = 16f
26 | isAntiAlias = true
27 | strokeJoin = Paint.Join.ROUND
28 | }
29 |
30 | override fun onDraw(canvas: Canvas?) {
31 | super.onDraw(canvas)
32 | canvas ?: return
33 |
34 | paths.forEach {
35 | paint.color = it.second
36 | canvas.drawPath(it.first, paint)
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/common/PathProperty.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.common
2 |
3 | import android.graphics.Path
4 | import android.graphics.PathMeasure
5 | import android.graphics.PointF
6 | import android.util.Property
7 |
8 | class PathProperty(private val mProperty: Property, path: Path) :
9 | Property(Float::class.java, mProperty.name) {
10 | private val pathMeasure: PathMeasure = PathMeasure(path, false)
11 | private val pathLength: Float
12 | private val position = FloatArray(2)
13 | private val pointF = PointF()
14 | private var currentFraction: Float = 0.toFloat()
15 |
16 | init {
17 | pathLength = pathMeasure.length
18 | }
19 |
20 | override fun get(target: T): Float? {
21 | return currentFraction
22 | }
23 |
24 | override fun set(target: T, fraction: Float?) {
25 | currentFraction = fraction!!
26 | pathMeasure.getPosTan(pathLength * fraction, position, null)
27 | pointF.x = position[0]
28 | pointF.y = position[1]
29 | mProperty.set(target, pointF)
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/common/RadiusTransition.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.common
2 |
3 | import android.animation.Animator
4 | import android.animation.ObjectAnimator
5 | import android.animation.PropertyValuesHolder
6 | import android.graphics.PointF
7 | import android.view.ViewGroup
8 | import androidx.transition.Transition
9 | import androidx.transition.TransitionValues
10 |
11 | class RadiusTransition : Transition() {
12 | companion object {
13 | private const val RADIUS = "RadiusTransition:radius"
14 | private const val CENTER_X = "RadiusTransition:centerX"
15 | private const val CENTER_Y = "RadiusTransition:centerY"
16 | }
17 |
18 | override fun captureStartValues(transitionValues: TransitionValues) =
19 | captureValues(transitionValues)
20 |
21 | override fun captureEndValues(transitionValues: TransitionValues) =
22 | captureValues(transitionValues)
23 |
24 | override fun createAnimator(
25 | sceneRoot: ViewGroup,
26 | startValues: TransitionValues?,
27 | endValues: TransitionValues?
28 | ): Animator? {
29 | startValues ?: return null
30 | endValues ?: return null
31 | val circularView = endValues.view as? CircularView ?: return null
32 |
33 | val startRadius = startValues.values[RADIUS] as Float
34 | val endRadius = endValues.values[RADIUS] as Float
35 |
36 | val startCenterX = startValues.values[CENTER_X] as Float
37 | val endCenterX = endValues.values[CENTER_X] as Float
38 |
39 | val startCenterY = startValues.values[CENTER_Y] as Float
40 | val endCenterY = endValues.values[CENTER_Y] as Float
41 |
42 | val radius = PropertyValuesHolder.ofFloat("radius", startRadius, endRadius)
43 | val centerX = PropertyValuesHolder.ofFloat("centerX", startCenterX, endCenterX)
44 | val centerY = PropertyValuesHolder.ofFloat("centerY", startCenterY, endCenterY)
45 |
46 | circularView.radius = startRadius
47 | circularView.center = PointF(startCenterX, startCenterY)
48 |
49 | return ObjectAnimator.ofPropertyValuesHolder(circularView, radius, centerX, centerY)
50 | }
51 |
52 | private fun captureValues(transitionValues: TransitionValues) {
53 | val circularView = transitionValues.view as? CircularView ?: return
54 | transitionValues.values[RADIUS] = circularView.radius
55 | transitionValues.values[CENTER_X] = circularView.center.x
56 | transitionValues.values[CENTER_Y] = circularView.center.y
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/coordinatorlayout/CoordinatorLayoutExample.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.coordinatorlayout
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.widget.TextView
6 | import com.karumi.androidanimations.R
7 | import com.karumi.androidanimations.coordinatorlayout.CoordinatorLayoutFragment.Example.Custom
8 | import com.karumi.androidanimations.coordinatorlayout.CoordinatorLayoutFragment.Example.Default
9 | import com.karumi.androidanimations.coordinatorlayout.CoordinatorLayoutFragment.Example.Exercise
10 |
11 |
12 | interface CoordinatorLayoutExample {
13 | class VH(itemView: View) : com.afollestad.recyclical.ViewHolder(itemView) {
14 | val row: View = itemView.findViewById(R.id.row)
15 | val exampleName: TextView = itemView.findViewById(R.id.exampleName)
16 | }
17 |
18 | class Binder(
19 | val getContext: () -> Context
20 | ) {
21 | operator fun invoke(
22 | receiver: VH,
23 | item: CoordinatorLayoutFragment.Example,
24 | onClick: () -> Unit
25 | ) = receiver.bind(item, onClick)
26 |
27 | private fun VH.bind(item: CoordinatorLayoutFragment.Example, onClick: () -> Unit) {
28 | exampleName.text = when (item) {
29 | Default -> R.string.coordinator_layout_default
30 | Custom -> R.string.coordinator_custom_behavior
31 | Exercise -> R.string.coordinator_exercise
32 | }.let { getContext().getString(it) }
33 | row.setOnClickListener { onClick() }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/coordinatorlayout/CoordinatorLayoutExerciseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.coordinatorlayout
2 |
3 | import android.annotation.SuppressLint
4 | import android.graphics.Rect
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.TextView
10 | import androidx.recyclerview.widget.DividerItemDecoration
11 | import androidx.recyclerview.widget.LinearLayoutManager
12 | import androidx.recyclerview.widget.RecyclerView
13 | import com.afollestad.recyclical.RecyclicalSetup
14 | import com.afollestad.recyclical.ViewHolder
15 | import com.afollestad.recyclical.datasource.dataSourceOf
16 | import com.afollestad.recyclical.setup
17 | import com.afollestad.recyclical.withItem
18 | import com.airbnb.lottie.LottieDrawable
19 | import com.karumi.androidanimations.R
20 | import com.karumi.androidanimations.base.BaseFragment
21 | import kotlinx.android.synthetic.main.fragment_coordinator_layout_exercise.*
22 |
23 | class CoordinatorLayoutExerciseFragment : BaseFragment() {
24 | override fun onCreateView(
25 | inflater: LayoutInflater,
26 | container: ViewGroup?,
27 | savedInstanceState: Bundle?
28 | ): View? = inflater.inflate(R.layout.fragment_coordinator_layout_exercise, container, false)
29 |
30 | @SuppressLint("SetTextI18n")
31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
32 | super.onViewCreated(view, savedInstanceState)
33 |
34 | animationView.setAnimation(R.raw.material_wave_loading)
35 | animationView.repeatMode = LottieDrawable.RESTART
36 | animationView.repeatCount = LottieDrawable.INFINITE
37 |
38 | val layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
39 | val dataSource = dataSourceOf((0..20).map { Item(it) })
40 |
41 | allItems.setup {
42 | withLayoutManager(layoutManager)
43 | withDataSource(dataSource)
44 | withItem- (R.layout.view_coordinator_exercise) {
45 | onBind(::VH) { _, item -> name.text = "#${item.id}" }
46 | }
47 | withVerticalOffset(layoutManager)
48 | }
49 | }
50 |
51 | private fun RecyclicalSetup.withVerticalOffset(layoutManager: LinearLayoutManager) {
52 | val dividerItemDecoration = object : DividerItemDecoration(
53 | requireContext(),
54 | layoutManager.orientation
55 | ) {
56 | override fun getItemOffsets(
57 | outRect: Rect,
58 | view: View,
59 | parent: RecyclerView,
60 | state: RecyclerView.State
61 | ) {
62 | super.getItemOffsets(outRect, view, parent, state)
63 | outRect.bottom =
64 | requireContext().resources.getDimension(R.dimen.margin2x).toInt()
65 | }
66 | }
67 | allItems.addItemDecoration(dividerItemDecoration)
68 | }
69 |
70 | class VH(itemView: View) : ViewHolder(itemView) {
71 | val name: TextView = itemView.findViewById(R.id.name)
72 | }
73 |
74 | data class Item(val id: Int)
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/coordinatorlayout/CoordinatorLayoutFragment.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.coordinatorlayout
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.navigation.fragment.findNavController
8 | import androidx.recyclerview.widget.LinearLayoutManager
9 | import com.afollestad.recyclical.datasource.dataSourceOf
10 | import com.afollestad.recyclical.setup
11 | import com.afollestad.recyclical.withItem
12 | import com.karumi.androidanimations.R
13 | import com.karumi.androidanimations.base.BaseFragment
14 | import kotlinx.android.synthetic.main.fragment_view_animation.*
15 |
16 | class CoordinatorLayoutFragment : BaseFragment() {
17 |
18 | private val coordinatorLayoutExample = CoordinatorLayoutExample.Binder(::requireContext)
19 |
20 | override fun onCreateView(
21 | inflater: LayoutInflater,
22 | container: ViewGroup?,
23 | savedInstanceState: Bundle?
24 | ): View? = inflater.inflate(R.layout.fragment_coordinator_layout, container, false)
25 |
26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
27 | super.onViewCreated(view, savedInstanceState)
28 | configureExamplesList()
29 | }
30 |
31 | private fun configureExamplesList() {
32 | val layoutManager = LinearLayoutManager(requireContext())
33 | val dataSource = dataSourceOf(
34 | *Example.values()
35 | )
36 |
37 | allAnimations.setup {
38 | withLayoutManager(layoutManager)
39 | withDataSource(dataSource)
40 |
41 | withItem(R.layout.view_coordinator_layout_example) {
42 | onBind(CoordinatorLayoutExample::VH) { _, item ->
43 | coordinatorLayoutExample(this, item) {
44 | val directions = when (item) {
45 | Example.Default -> CoordinatorLayoutFragmentDirections
46 | .actionCoordinatorLayoutFragmentToDefaultBehaviorFragment()
47 | Example.Custom -> CoordinatorLayoutFragmentDirections
48 | .actionCoordinatorLayoutFragmentToCustomBehaviorFragment()
49 | Example.Exercise -> CoordinatorLayoutFragmentDirections
50 | .actionCoordinatorLayoutFragmentToCoordinatorLayoutExerciseFragment()
51 | }
52 | findNavController().navigate(directions)
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
59 | enum class Example {
60 | Default, Custom, Exercise
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/coordinatorlayout/CustomBehaviorFragment.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.coordinatorlayout
2 |
3 | import android.annotation.SuppressLint
4 | import android.graphics.Rect
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.TextView
10 | import androidx.recyclerview.widget.DividerItemDecoration
11 | import androidx.recyclerview.widget.LinearLayoutManager
12 | import androidx.recyclerview.widget.RecyclerView
13 | import com.afollestad.recyclical.RecyclicalSetup
14 | import com.afollestad.recyclical.ViewHolder
15 | import com.afollestad.recyclical.datasource.dataSourceOf
16 | import com.afollestad.recyclical.setup
17 | import com.afollestad.recyclical.withItem
18 | import com.karumi.androidanimations.R
19 | import com.karumi.androidanimations.base.BaseFragment
20 | import kotlinx.android.synthetic.main.fragment_custom_behavior.*
21 |
22 |
23 | @SuppressLint("RestrictedApi")
24 | class CustomBehaviorFragment : BaseFragment() {
25 | override fun onCreateView(
26 | inflater: LayoutInflater,
27 | container: ViewGroup?,
28 | savedInstanceState: Bundle?
29 | ): View? = inflater.inflate(R.layout.fragment_custom_behavior, container, false)
30 |
31 | @SuppressLint("SetTextI18n")
32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
33 | super.onViewCreated(view, savedInstanceState)
34 |
35 | val layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
36 | val dataSource = dataSourceOf((0..10).map { Item(it) })
37 |
38 | allItems.setup {
39 | withLayoutManager(layoutManager)
40 | withDataSource(dataSource)
41 | withItem
- (R.layout.view_custom_behavior_item) {
42 | onBind(::VH) { _, item -> name.text = "#${item.id}" }
43 | }
44 | withHorizontalOffset(layoutManager)
45 | }
46 |
47 | }
48 |
49 | private fun RecyclicalSetup.withHorizontalOffset(layoutManager: LinearLayoutManager) {
50 | val dividerItemDecoration = object : DividerItemDecoration(
51 | requireContext(),
52 | layoutManager.orientation
53 | ) {
54 | override fun getItemOffsets(
55 | outRect: Rect,
56 | view: View,
57 | parent: RecyclerView,
58 | state: RecyclerView.State
59 | ) {
60 | super.getItemOffsets(outRect, view, parent, state)
61 | outRect.right =
62 | requireContext().resources.getDimension(R.dimen.margin2x).toInt()
63 | }
64 | }
65 | allItems.addItemDecoration(dividerItemDecoration)
66 | }
67 |
68 | class VH(itemView: View) : ViewHolder(itemView) {
69 | val name: TextView = itemView.findViewById(R.id.name)
70 | }
71 |
72 | data class Item(val id: Int)
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/coordinatorlayout/DefaultBehaviorFragment.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.coordinatorlayout
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
9 | import androidx.coordinatorlayout.widget.CoordinatorLayout
10 | import androidx.recyclerview.widget.LinearLayoutManager
11 | import com.afollestad.recyclical.ViewHolder
12 | import com.afollestad.recyclical.datasource.dataSourceOf
13 | import com.afollestad.recyclical.setup
14 | import com.afollestad.recyclical.withItem
15 | import com.google.android.material.appbar.AppBarLayout
16 | import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
17 | import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
18 | import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
19 | import com.karumi.androidanimations.R
20 | import com.karumi.androidanimations.base.BaseFragment
21 | import com.karumi.androidanimations.extensions.px
22 | import kotlinx.android.synthetic.main.activity_main.*
23 | import kotlinx.android.synthetic.main.fragment_default_behaviors.*
24 |
25 | @SuppressLint("RestrictedApi")
26 | class DefaultBehaviorFragment : BaseFragment() {
27 | override fun onCreateView(
28 | inflater: LayoutInflater,
29 | container: ViewGroup?,
30 | savedInstanceState: Bundle?
31 | ): View? = inflater.inflate(R.layout.fragment_default_behaviors, container, false).also {
32 | setToolbarScrollFlags(SCROLL_FLAG_SCROLL or SCROLL_FLAG_SNAP or SCROLL_FLAG_ENTER_ALWAYS)
33 | setToolbarHeight(200.px)
34 | requireActivity().floatingActionButton.visibility = View.VISIBLE
35 | }
36 |
37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
38 | super.onViewCreated(view, savedInstanceState)
39 | configureItems()
40 | }
41 |
42 | override fun onDestroyView() {
43 | setToolbarScrollFlags(0)
44 | setToolbarHeight(WRAP_CONTENT)
45 | requireActivity().floatingActionButton.visibility = View.GONE
46 | super.onDestroyView()
47 | }
48 |
49 | private fun setToolbarScrollFlags(flags: Int) {
50 | val toolbar = requireActivity().collapsingToolbarLayout
51 | val params = toolbar.layoutParams as AppBarLayout.LayoutParams
52 | toolbar.isTitleEnabled = true
53 | params.scrollFlags = flags
54 | toolbar.layoutParams = params
55 | }
56 |
57 | private fun setToolbarHeight(heightInPx: Int) {
58 | val appBarLayout = requireActivity().appBarLayout
59 | val params = appBarLayout.layoutParams as CoordinatorLayout.LayoutParams
60 | params.height = heightInPx
61 | appBarLayout.layoutParams = params
62 | }
63 |
64 | private fun configureItems() {
65 | val layoutManager = LinearLayoutManager(requireContext())
66 | val dataSource = dataSourceOf((0..100).map { Item })
67 |
68 | allItems.setup {
69 | withLayoutManager(layoutManager)
70 | withDataSource(dataSource)
71 |
72 | withItem
- (R.layout.view_default_behaviors_item) {
73 | onBind(::VH) { _, _ -> }
74 | }
75 | }
76 | }
77 |
78 | class VH(itemView: View) : ViewHolder(itemView)
79 | object Item
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/coordinatorlayout/ParallaxBehavior.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.coordinatorlayout
2 |
3 | import android.animation.ValueAnimator
4 | import android.content.Context
5 | import android.graphics.Matrix
6 | import android.util.AttributeSet
7 | import android.view.View
8 | import android.widget.ImageView
9 | import android.widget.Scroller
10 | import androidx.coordinatorlayout.widget.CoordinatorLayout
11 |
12 | class ParallaxBehavior(
13 | context: Context,
14 | attrs: AttributeSet
15 | ) : CoordinatorLayout.Behavior(context, attrs) {
16 |
17 | private val parallaxFactor = 0.2f
18 | private var isAnimatingFling = false
19 | private var previousFlingX = 0
20 | private var previousFlingY = 0
21 | private val scroller = Scroller(context)
22 |
23 | override fun onStartNestedScroll(
24 | coordinatorLayout: CoordinatorLayout,
25 | child: ImageView,
26 | directTargetChild: View,
27 | target: View,
28 | axes: Int,
29 | type: Int
30 | ): Boolean {
31 | // We stop fling animations on any scrolling stop
32 | isAnimatingFling = false
33 | return true
34 | }
35 |
36 | override fun onNestedScroll(
37 | coordinatorLayout: CoordinatorLayout,
38 | child: ImageView,
39 | target: View,
40 | dxConsumed: Int,
41 | dyConsumed: Int,
42 | dxUnconsumed: Int,
43 | dyUnconsumed: Int,
44 | type: Int
45 | ) {
46 | child.imageMatrix = child.imageMatrix
47 | .translateWithParallax(dxConsumed.toFloat(), dyConsumed.toFloat())
48 | }
49 |
50 | private fun Matrix.translateWithParallax(
51 | dx: Float,
52 | dy: Float
53 | ): Matrix = Matrix(this)
54 | .apply { postTranslate(-parallaxFactor * dx, -parallaxFactor * dy) }
55 |
56 | override fun onNestedFling(
57 | coordinatorLayout: CoordinatorLayout,
58 | child: ImageView,
59 | target: View,
60 | velocityX: Float,
61 | velocityY: Float,
62 | consumed: Boolean
63 | ): Boolean {
64 | val canScroll = if (Math.abs(velocityX) > 0) {
65 | target.canScrollHorizontally(velocityX.toInt())
66 | } else {
67 | target.canScrollVertically(velocityX.toInt())
68 | }
69 |
70 | if (!canScroll) {
71 | return false
72 | }
73 |
74 | scroller.fling(
75 | 0,
76 | 0,
77 | velocityX.toInt(),
78 | velocityY.toInt(),
79 | 0,
80 | 0,
81 | Int.MIN_VALUE,
82 | Int.MAX_VALUE
83 | )
84 |
85 | previousFlingX = 0
86 | previousFlingY = 0
87 | isAnimatingFling = true
88 | ValueAnimator.ofFloat(0f, 1f).apply {
89 | addUpdateListener { animateImageViewParallax(it, child) }
90 | duration = scroller.duration.toLong()
91 | start()
92 | }
93 | return true
94 | }
95 |
96 | private fun animateImageViewParallax(
97 | animator: ValueAnimator,
98 | child: ImageView
99 | ) {
100 | if (!isAnimatingFling) {
101 | animator.cancel()
102 | return
103 | }
104 |
105 | scroller.computeScrollOffset()
106 | val currX = scroller.currX
107 | val currY = scroller.currY
108 | val xDiff = currX - previousFlingX.toFloat()
109 | val yDiff = currY - previousFlingY.toFloat()
110 | previousFlingX = currX
111 | previousFlingY = currY
112 | child.imageMatrix = child.imageMatrix.translateWithParallax(xDiff, yDiff)
113 | }
114 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/drawablegraphicsanimation/DrawableGraphicsAnimationFragment.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.drawablegraphicsanimation
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.recyclerview.widget.LinearLayoutManager
8 | import com.afollestad.recyclical.datasource.dataSourceOf
9 | import com.afollestad.recyclical.setup
10 | import com.afollestad.recyclical.withItem
11 | import com.karumi.androidanimations.R
12 | import com.karumi.androidanimations.base.BaseFragment
13 | import kotlinx.android.synthetic.main.fragment_animated_drawable_graphics.*
14 |
15 | class DrawableGraphicsAnimationFragment : BaseFragment() {
16 |
17 | val drawableGraphicsSimpleAnimationBinder =
18 | DrawableGraphicsSimpleAnimation.Binder(::requireContext)
19 | val drawableGraphicsExerciseAnimationBinder =
20 | DrawableGraphicsExerciseAnimation.Binder(::requireContext)
21 | val drawableGraphicsLottieAnimationBinder =
22 | DrawableGraphicsLottieAnimation.Binder(::requireContext)
23 |
24 | override fun onCreateView(
25 | inflater: LayoutInflater,
26 | container: ViewGroup?,
27 | savedInstanceState: Bundle?
28 | ): View? = inflater.inflate(R.layout.fragment_animated_drawable_graphics, container, false)
29 |
30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
31 | super.onViewCreated(view, savedInstanceState)
32 | configureAllAnimations()
33 | }
34 |
35 | private fun configureAllAnimations() {
36 | val layoutManager = LinearLayoutManager(requireContext())
37 | val dataSource = dataSourceOf(
38 | *DrawableGraphicsAnimation.values(),
39 | Lottie,
40 | Exercise
41 | )
42 |
43 | allAnimations.setup {
44 | withLayoutManager(layoutManager)
45 | withDataSource(dataSource)
46 |
47 | withItem(R.layout.view_simple_drawable_graphics_animation) {
48 | onBind(DrawableGraphicsSimpleAnimation::VH) { _, item ->
49 | drawableGraphicsSimpleAnimationBinder(this, item)
50 | }
51 | }
52 | withItem(R.layout.view_lottie_drawable_graphics_animation) {
53 | onBind(DrawableGraphicsLottieAnimation::VH) { _, _ ->
54 | drawableGraphicsLottieAnimationBinder(this)
55 | }
56 | }
57 | withItem(R.layout.view_exercise_drawable_graphics_animation) {
58 | onBind(DrawableGraphicsExerciseAnimation::VH) { _, _ ->
59 | drawableGraphicsExerciseAnimationBinder(this)
60 | }
61 | }
62 | }
63 | }
64 |
65 | enum class DrawableGraphicsAnimation {
66 | Keyframe, Vector
67 | }
68 |
69 | object Lottie
70 | object Exercise
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/drawablegraphicsanimation/DrawableGraphicsExerciseAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.drawablegraphicsanimation
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.widget.Button
6 | import android.widget.ImageView
7 | import android.widget.TextView
8 | import com.karumi.androidanimations.R
9 |
10 | interface DrawableGraphicsExerciseAnimation {
11 | class VH(itemView: View) : com.afollestad.recyclical.ViewHolder(itemView) {
12 | val name: TextView = itemView.findViewById(R.id.animationName)
13 | val keyframeAnimationView: ImageView = itemView.findViewById(R.id.keyframeAnimationView)
14 | val vectorizedAnimationView: ImageView = itemView.findViewById(R.id.vectorizedAnimationView)
15 | val lottieAnimationView: ImageView = itemView.findViewById(R.id.lottieAnimationView)
16 | val playButton: Button = itemView.findViewById(R.id.playButton)
17 | val stopButton: Button = itemView.findViewById(R.id.stopButton)
18 | }
19 |
20 | class Binder(val getContext: () -> Context) {
21 | operator fun invoke(receiver: VH) = receiver.bind()
22 |
23 | private fun VH.bind() {
24 | name.text = R.string.drawable_graphics_animation_exercise
25 | .let { getContext().getString(it) }
26 |
27 | TODO(
28 | """
29 | | Render the animation that can be controlled with two buttons, one to play
30 | | and one to stop.
31 | | You have the same animation in all formats: keyframe, vectorized and
32 | | lottie. Configure all of them one by one.
33 | | """.trimMargin()
34 | )
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/drawablegraphicsanimation/DrawableGraphicsLottieAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.drawablegraphicsanimation
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.widget.TextView
6 | import com.airbnb.lottie.LottieAnimationView
7 | import com.airbnb.lottie.LottieDrawable
8 | import com.karumi.androidanimations.R
9 |
10 | interface DrawableGraphicsLottieAnimation {
11 | class VH(itemView: View) : com.afollestad.recyclical.ViewHolder(itemView) {
12 | val name: TextView = itemView.findViewById(R.id.animationName)
13 | val animationView: LottieAnimationView = itemView.findViewById(R.id.animationView)
14 | }
15 |
16 | class Binder(val getContext: () -> Context) {
17 | operator fun invoke(receiver: VH) = receiver.bind()
18 |
19 | private fun VH.bind() {
20 | name.text =
21 | R.string.drawable_graphics_animation_with_lottie.let { getContext().getString(it) }
22 | animate(animationView)
23 | }
24 |
25 | private fun animate(view: LottieAnimationView) {
26 | view.setAnimation(R.raw.material_wave_loading)
27 | view.repeatMode = LottieDrawable.RESTART
28 | view.repeatCount = LottieDrawable.INFINITE
29 | view.playAnimation()
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/drawablegraphicsanimation/DrawableGraphicsSimpleAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.drawablegraphicsanimation
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.AnimationDrawable
5 | import android.graphics.drawable.Drawable
6 | import android.view.View
7 | import android.widget.TextView
8 | import androidx.appcompat.widget.AppCompatImageView
9 | import androidx.vectordrawable.graphics.drawable.Animatable2Compat
10 | import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
11 | import com.karumi.androidanimations.R
12 | import com.karumi.androidanimations.drawablegraphicsanimation.DrawableGraphicsAnimationFragment.DrawableGraphicsAnimation
13 | import com.karumi.androidanimations.extensions.exhaustive
14 |
15 | interface DrawableGraphicsSimpleAnimation {
16 | class VH(itemView: View) : com.afollestad.recyclical.ViewHolder(itemView) {
17 | val name: TextView = itemView.findViewById(R.id.animationName)
18 | val animationView: AppCompatImageView = itemView.findViewById(R.id.animationView)
19 | }
20 |
21 | class Binder(val getContext: () -> Context) {
22 | operator fun invoke(
23 | receiver: VH,
24 | item: DrawableGraphicsAnimation
25 | ) = receiver.bind(item)
26 |
27 | private fun VH.bind(item: DrawableGraphicsAnimation) {
28 | name.text = getAnimationName(item)
29 | animate(item, animationView)
30 | }
31 |
32 | private fun getAnimationName(item: DrawableGraphicsAnimation): String = when (item) {
33 | DrawableGraphicsAnimation.Keyframe -> R.string.drawable_graphics_animation_by_keyframe
34 | DrawableGraphicsAnimation.Vector -> R.string.drawable_graphics_animation_with_vector
35 | }.let { getContext().getString(it) }
36 |
37 | private fun animate(item: DrawableGraphicsAnimation, view: AppCompatImageView) {
38 | when (item) {
39 | DrawableGraphicsAnimation.Keyframe -> {
40 | view.setBackgroundResource(R.drawable.keyframe_animation)
41 | (view.background as AnimationDrawable).start()
42 | }
43 | DrawableGraphicsAnimation.Vector -> {
44 | val animation = AnimatedVectorDrawableCompat.create(
45 | getContext(),
46 | R.drawable.vectorized_animation
47 | )?.apply {
48 | repeatAnimation()
49 | start()
50 | }
51 | view.setImageDrawable(animation)
52 | }
53 | }.exhaustive
54 | }
55 |
56 | private fun AnimatedVectorDrawableCompat.repeatAnimation() {
57 | registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
58 | override fun onAnimationEnd(drawable: Drawable?) {
59 | start()
60 | }
61 | })
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/extensions/StringResourceResolver.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.extensions
2 |
3 | import android.content.Context
4 |
5 | interface StringResourceResolver {
6 | fun Int.resolve(context: Context): String = context.getString(this)
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/extensions/animatePath.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.extensions
2 |
3 | import android.animation.ObjectAnimator
4 | import android.animation.ValueAnimator
5 | import android.graphics.Path
6 | import android.graphics.PathMeasure
7 | import android.os.Build
8 | import android.view.View
9 |
10 | fun animatePath(view: View, path: Path): ValueAnimator =
11 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
12 | ObjectAnimator.ofFloat(view, View.X, View.Y, path)
13 | } else {
14 | val coordinates = FloatArray(2)
15 | val pathMeasure = PathMeasure(path, true)
16 | ValueAnimator.ofFloat(0f, pathMeasure.length).apply {
17 | addUpdateListener {
18 | val distance = it.animatedValue as Float
19 | pathMeasure.getPosTan(distance, coordinates, null)
20 | view.translationX = coordinates[0]
21 | view.translationY = coordinates[1]
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/extensions/exhaustive.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.extensions
2 |
3 | val Any?.exhaustive: Unit get() = Unit
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/extensions/pixel.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.extensions
2 |
3 | import android.content.res.Resources
4 |
5 | val Int.dp: Int
6 | get() = (this / Resources.getSystem().displayMetrics.density).toInt()
7 |
8 | val Int.px: Int
9 | get() = (this * Resources.getSystem().displayMetrics.density).toInt()
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/extensions/transitions.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.extensions
2 |
3 | import android.view.View
4 | import androidx.core.view.ViewCompat
5 | import androidx.navigation.fragment.FragmentNavigator
6 | import androidx.navigation.fragment.FragmentNavigatorExtras
7 |
8 | fun sharedElements(vararg views: View): FragmentNavigator.Extras =
9 | FragmentNavigatorExtras(*views.map { it to it.transitionNameCompat }.toTypedArray())
10 |
11 | val View.transitionNameCompat: String
12 | get() = ViewCompat.getTransitionName(this) ?: ""
--------------------------------------------------------------------------------
/app/src/main/java/com/karumi/androidanimations/layouttransition/ExerciseLayoutTransition.kt:
--------------------------------------------------------------------------------
1 | package com.karumi.androidanimations.layouttransition
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.Button
7 | import androidx.annotation.LayoutRes
8 | import androidx.transition.Scene
9 | import com.karumi.androidanimations.R
10 |
11 | interface ExerciseLayoutTransition {
12 | class VH(itemView: View) : com.afollestad.recyclical.ViewHolder(itemView) {
13 | val masterScene: ViewGroup = itemView.findViewById(R.id.masterScene)
14 | }
15 |
16 | class Binder(val getContext: () -> Context) {
17 | operator fun invoke(receiver: VH) =
18 | receiver.bind()
19 |
20 | private fun VH.bind() {
21 | masterScene.findViewById