├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── new-animation-resquest.md └── workflows │ └── .github │ └── workflows │ └── contributors.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── runConfigurations.xml ├── studiobot.xml └── vcs.xml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sp45 │ │ └── android_animations │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── sp45 │ │ │ └── android_animations │ │ │ ├── AppNavGraph.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MainScreen.kt │ │ │ ├── animations │ │ │ ├── AnimatedPlacement.kt │ │ │ ├── AutoImageTransition.kt │ │ │ ├── BouncingBallAnimation.kt │ │ │ ├── ButtonToImage.kt │ │ │ ├── CardFlipAnimation.kt │ │ │ ├── CardFlippingAnimation.kt │ │ │ ├── CarouselSlider.kt │ │ │ ├── ConfettiAnimation.kt │ │ │ ├── ContentAnimation.kt │ │ │ ├── EmojiProgressBar.kt │ │ │ ├── ExpandableCardsAnimation.kt │ │ │ ├── ExpandingRingsAnimation.kt │ │ │ ├── FloatingElements.kt │ │ │ ├── OrbitingObjects.kt │ │ │ ├── SlidingDoor.kt │ │ │ ├── SwipeToDeleteAnimation.kt │ │ │ ├── TextCollapsingAnimation.kt │ │ │ ├── TypeWriterAnimation.kt │ │ │ ├── ValueSpringAnimation.kt │ │ │ └── WaveLoadingBar.kt │ │ │ ├── ui │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── util │ │ │ ├── AnimationCard.kt │ │ │ ├── FadingTextAnimation.kt │ │ │ ├── ThemeSwitch.kt │ │ │ ├── TopAppBar.kt │ │ │ └── WebViewComponent.kt │ └── res │ │ ├── drawable │ │ ├── avd_crakcode.xml │ │ ├── cat1.jpg │ │ ├── cat2.jpg │ │ ├── cat3.png │ │ ├── cat4.png │ │ ├── cat5.png │ │ ├── crakcode.png │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── img.png │ │ └── img_2.png │ │ ├── mipmap-anydpi │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── splash.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── sp45 │ └── android_animations │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-animation-resquest.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Animation Resquest 3 | about: Request a new animation to be added to the project. 4 | title: '' 5 | labels: '' 6 | assignees: shubhampandey45 7 | 8 | --- 9 | 10 | ## Describe the Animation 11 | Provide a clear and concise description of the animation you want to be added. 12 | - What does the animation do? 13 | - Where could it be used? 14 | 15 | --- 16 | 17 | ## Use Case 18 | Explain how this animation could be helpful for the project. 19 | - Is it relevant to a particular feature or screen? 20 | - How does it improve the user experience? 21 | 22 | --- 23 | 24 | ## Expected Behavior 25 | Describe what the animation should look like and how it should behave. 26 | - Include details like duration, effects, transitions, etc. 27 | - Mention specific attributes such as easing, timing, or interaction. 28 | 29 | --- 30 | 31 | ## References/Examples 32 | If you have any references, screenshots, videos, or links to examples of similar animations, include them here. 33 | 34 | --- 35 | 36 | ## Additional Context 37 | Add any other details or context about the animation. 38 | 39 | --- 40 | 41 | ## Optional Additional Items 42 | - **Issue default title**: `New Animation: [Short Description]` 43 | - **Assignees**: 44 | - **Labels**: `animation-request` 45 | -------------------------------------------------------------------------------- /.github/workflows/.github/workflows/contributors.yml: -------------------------------------------------------------------------------- 1 | name: Update Contributors 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | schedule: 8 | - cron: '0 0 * * *' # Runs daily at midnight (optional) 9 | 10 | jobs: 11 | update-contributors: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out the repository 15 | uses: actions/checkout@v2 16 | 17 | - name: Generate contributors list 18 | run: | 19 | echo "" > CONTRIBUTORS_LIST.md 20 | contributors=$(git log --format='%aN' | sort -u) 21 | for contributor in $contributors; do 22 | # Fetch GitHub username using GitHub API (requires API token if private) 23 | username=$(curl -s "https://api.github.com/search/users?q=${contributor}" | jq -r '.items[0].login') 24 | if [ "$username" != "null" ]; then 25 | echo "\"$username\"/" >> CONTRIBUTORS_LIST.md 26 | fi 27 | done 28 | 29 | - name: Update README with Contributors 30 | run: | 31 | sed -i '//,//{//!d}' README.md 32 | sed -i '//r CONTRIBUTORS_LIST.md' README.md 33 | 34 | - name: Commit and push changes 35 | run: | 36 | git config --local user.name "github-actions[bot]" 37 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 38 | git add README.md CONTRIBUTORS_LIST.md 39 | git commit -m "Update contributors section" 40 | git push 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.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 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | android-animations -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/studiobot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 |

Contributing to Android Animations with Jetpack Compose 🎨

2 |

3 | Thank you for your interest in contributing to the 4 | Android Animations with Jetpack Compose project! Contributions are what make the open-source community an incredible place to learn, inspire, and create. Your help in improving this project, fixing bugs, and adding new animations is highly appreciated. 5 |

6 | 7 |
8 | 9 |

🛠 How to Contribute

10 |

1. Fork the Repository

11 |

12 | Click the Fork button at the top-right corner of this repository to create your copy. 13 |

14 | 15 |

2. Clone Your Fork

16 |
17 |     git clone https://github.com/<your-username>/android-animations.git
18 |   
19 | 20 |

3. Create a New Branch

21 |

Create a new branch for your contribution. Use a descriptive branch name, such as:

22 |
23 |     git checkout -b feature/<your-feature-name>
24 |   
25 | 26 |

4. Make Changes

27 | 32 | 33 |

5. Commit Your Changes

34 |

Write a concise and descriptive commit message:

35 |
36 |     git commit -m "Add: Bouncing Ball Animation"
37 |   
38 | 39 |

6. Push to Your Fork

40 |

Push your branch to the forked repository:

41 |
42 |     git push origin feature/<your-feature-name>
43 |   
44 | 45 |

7. Open a Pull Request

46 |

47 | Go to the original repository and click New Pull Request. Fill out the pull request template with details about your changes. 48 |

49 | 50 |
51 | 52 |

✨ Contribution Guidelines

53 | 62 | 63 |
64 | 65 |

🔍 Code of Conduct

66 |

67 | This project follows the Contributor Covenant Code of Conduct. By participating, you agree to uphold a welcoming and inclusive environment. 68 |

69 | 70 |
71 | 72 |

🌟 Get Started Now!

73 |

74 |

78 |

79 | 80 |
81 | 82 |

Thank you for contributing! Together, let’s make this a great resource for Android developers. 🚀

83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 crakcode-hub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Jetpack Compose Animations 3 |

4 | 5 |

Android Animations with Jetpack Compose

6 | 7 |

8 | A repository showcasing various animations using Jetpack Compose. Perfect for developers looking to learn and practice animation techniques on Android. 9 |

10 | 11 |

12 | 13 | Issues 14 | 15 | 16 | Pull Requests 17 | 18 | 19 | Stars 20 | 21 |

22 | 23 | ## Table of Contents 24 | - About the Project 25 | - Showcase 26 | - Quick Start 27 | - Requirements 28 | - Reporting Issues 29 | - Contributing 30 | - Contributors 31 | - License 32 | 33 |

About the Project📃

34 | 35 |

This repository demonstrates various animation techniques available in Jetpack Compose, helping developers learn how to create smooth, interactive UI animations. Includes various animations, making it easy to explore and implement these animations.

36 | 37 |

Showcase⭐

38 | 39 | 40 | 46 | 52 | 58 | 59 | 60 | 66 | 72 | 78 | 79 | 80 | 86 | 92 | 98 | 99 |
41 |
42 | Swipe to delete 43 |

Swipe to delete

44 |
45 |
47 |
48 | Confetti Animation 49 |

Confetti Animation

50 |
51 |
53 |
54 | Bouncing ball 55 |

Bouncing ball

56 |
57 |
61 |
62 | Placeholder Image 63 |

Wave Loading Bar

64 |
65 |
67 |
68 | Placeholder Image 69 |

Carousel Slider

70 |
71 |
73 |
74 | Placeholder Image 75 |

Card Flip

76 |
77 |
81 |
82 | Placeholder Image 83 |

Emoji Progress Bar

84 |
85 |
87 |
88 | Placeholder Image 89 |

Type Writer Animation

90 |
91 |
93 |
94 | Placeholder Image 95 |

Floating Elements

96 |
97 |
100 | 101 | 102 |

Quick Start 🚀

103 |
    104 |
  1. Clone the repository: 105 |
    git clone https://github.com/crakcode-hub/android-animations.git
    106 |
  2. 107 |
  3. Open the project: 108 | 112 |
  4. 113 |
  5. Run the app: 114 | 118 |
  6. 119 |
120 | 121 |

Requirements 🛠️

122 | 128 | 129 |

Reporting Issues 🐛

130 |

If you encounter any bugs or have suggestions for improvement, we’d love to hear from you! Here’s how you can help:

131 |
    132 |
  1. Check if the issue already exists in the GitHub Issues section.
  2. 133 |
  3. If it doesn’t, open a new issue: 134 | 140 |
  4. 141 |
142 |

Thank you for helping us improve this project!

143 | 144 |

Contributing 🧑‍💻

145 |

We welcome contributions to make this project better! Whether you want to fix a bug, add a new feature, or improve documentation, your help is appreciated.

146 | 147 |

How to Contribute

148 |
    149 |
  1. Fork the Repository: Click the Fork button on the top-right corner of the repository page.
  2. 150 |
  3. Clone Your Fork: Clone the forked repository to your local machine: 151 |
    git clone https://github.com/your-username/android-animations.git
    152 |
  4. 153 |
  5. Create a New Branch: Use a descriptive name for your branch: 154 |
    git checkout -b feature/your-feature-name
    155 |
  6. 156 |
  7. Make Your Changes: Edit the code, fix bugs, or add features. Make sure to follow the coding guidelines and test your changes locally.
  8. 157 |
  9. Commit Your Changes: Write clear and concise commit messages: 158 |
    git commit -m "Add: Feature description"
    159 |
  10. 160 |
  11. Push Your Changes: Push the changes to your forked repository: 161 |
    git push origin feature/your-feature-name
    162 |
  12. 163 |
  13. Open a Pull Request: Navigate to the original repository and click the New Pull Request button. Provide a detailed description of the changes you’ve made.
  14. 164 |
165 | 166 |

Contribution Guidelines

167 | 173 | 174 |

Learn More✅

175 | 176 |

If you're interested in diving deeper into Android development, including advanced topics and best practices, visit our website for more resources and mentorship.

177 | 178 |

179 | 180 | Explore More on CrakCode Website 181 | 182 |

183 | 184 | ## Contributors 185 |

186 | Join us to make this project even better. Happy Animating! 🎉 187 |

188 |

189 | 190 | Contributors 191 | 192 |

193 | 194 | 195 | 196 | 197 |

License📄

198 | This project is licensed under the MIT License. 199 | 200 |

Built with 💚 by CrakCode

201 |

202 | Back to Top 🔝 203 |

204 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | We take the security of this project seriously. If you discover any vulnerabilities, please follow the steps below to report them responsibly. 6 | 7 | ### Steps to Report 8 | 1. **Email**: Send details of the vulnerability to [tarun.crakcode@gmail.com](mailto:tarun.crakcode@gmail.com). 9 | 2. **Include the following in your report**: 10 | - Description of the vulnerability. 11 | - Steps to reproduce the issue. 12 | - Potential impact of the vulnerability. 13 | - If possible, suggest a fix or mitigation strategy. 14 | 3. **Timeline**: 15 | - We will acknowledge your report within 2 business days. 16 | - Updates will be provided every 5 business days on the status of your report. 17 | - If accepted, a fix will be implemented and deployed within 30 days of the initial report (barring unforeseen circumstances). 18 | 19 | ### What to Expect 20 | - **Acknowledgment**: A confirmation that we have received your report. 21 | - **Assessment**: We will evaluate the validity and impact of the vulnerability. 22 | - **Action**: If confirmed, we will prioritize a fix and may request further information or collaboration from you. 23 | - **Credit**: With your consent, you will be acknowledged in our release notes or public announcements. 24 | 25 | ### Important Notes 26 | - Please do not share the vulnerability publicly or with others until it has been resolved. 27 | - Avoid testing vulnerabilities on live systems or end users, as this can disrupt services or compromise data. 28 | 29 | Thank you for helping to keep this project secure and safe for everyone! 🚀 30 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.kotlin.compose) 5 | } 6 | 7 | android { 8 | namespace = "com.sp45.android_animations" 9 | compileSdk = 34 10 | 11 | defaultConfig { 12 | applicationId = "com.sp45.android_animations" 13 | minSdk = 26 14 | targetSdk = 34 15 | versionCode = 1 16 | versionName = "1.0" 17 | 18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | isMinifyEnabled = false 24 | proguardFiles( 25 | getDefaultProguardFile("proguard-android-optimize.txt"), 26 | "proguard-rules.pro" 27 | ) 28 | } 29 | } 30 | compileOptions { 31 | sourceCompatibility = JavaVersion.VERSION_1_8 32 | targetCompatibility = JavaVersion.VERSION_1_8 33 | } 34 | kotlinOptions { 35 | jvmTarget = "1.8" 36 | } 37 | buildFeatures { 38 | compose = true 39 | } 40 | } 41 | 42 | dependencies { 43 | 44 | implementation(libs.androidx.core.ktx) 45 | implementation(libs.androidx.lifecycle.runtime.ktx) 46 | implementation(libs.androidx.activity.compose) 47 | implementation(platform(libs.androidx.compose.bom)) 48 | implementation(libs.androidx.ui) 49 | implementation(libs.androidx.ui.graphics) 50 | implementation(libs.androidx.ui.tooling.preview) 51 | implementation(libs.androidx.material3) 52 | implementation(libs.androidx.ui.text.google.fonts) 53 | implementation(libs.androidx.animation.android) 54 | implementation(libs.androidx.foundation.layout.android) 55 | testImplementation(libs.junit) 56 | androidTestImplementation(libs.androidx.junit) 57 | androidTestImplementation(libs.androidx.espresso.core) 58 | androidTestImplementation(platform(libs.androidx.compose.bom)) 59 | androidTestImplementation(libs.androidx.ui.test.junit4) 60 | debugImplementation(libs.androidx.ui.tooling) 61 | debugImplementation(libs.androidx.ui.test.manifest) 62 | 63 | implementation(libs.androidx.navigation.compose) 64 | implementation(libs.androidx.core.splashscreen) 65 | implementation(libs.androidx.lifecycle.viewmodel.compose) 66 | 67 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/sp45/android_animations/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.sp45.android_animations", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/AppNavGraph.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.NavHostController 5 | import androidx.navigation.NavType 6 | import androidx.navigation.compose.NavHost 7 | import androidx.navigation.compose.composable 8 | import androidx.navigation.compose.rememberNavController 9 | import androidx.navigation.navArgument 10 | import com.sp45.android_animations.animations.AutoImageTransition 11 | import com.sp45.android_animations.animations.BirthdayPopperAnimation 12 | import com.sp45.android_animations.animations.BouncingBallAnimation 13 | import com.sp45.android_animations.animations.ButtonToImage 14 | import com.sp45.android_animations.animations.CardFlipping 15 | import com.sp45.android_animations.animations.CarouselSlider 16 | import com.sp45.android_animations.animations.ContentAnimation 17 | import com.sp45.android_animations.animations.EmojiProgressBar 18 | import com.sp45.android_animations.animations.ExpandableCardAnimation 19 | import com.sp45.android_animations.animations.ExpandingRings 20 | import com.sp45.android_animations.animations.FlipCard 21 | import com.sp45.android_animations.animations.FloatingElements 22 | import com.sp45.android_animations.animations.OrbitingObjects 23 | import com.sp45.android_animations.animations.SlidingDoorAnimation 24 | import com.sp45.android_animations.animations.SwipeToDeleteAnimation 25 | import com.sp45.android_animations.animations.TextExplosion 26 | import com.sp45.android_animations.animations.TypeWriterAnimation 27 | import com.sp45.android_animations.animations.ValueSpringAnimation 28 | import com.sp45.android_animations.animations.WaveLoadingBar 29 | import com.sp45.android_animations.util.WebViewScreen 30 | import java.net.URLDecoder 31 | import java.net.URLEncoder 32 | import java.nio.charset.StandardCharsets 33 | 34 | @Composable 35 | fun AppNavGraph(navController: NavHostController = rememberNavController()) { 36 | NavHost( 37 | navController = navController, 38 | startDestination = NavigationDestinations.MAIN 39 | ) { 40 | composable(NavigationDestinations.MAIN) { 41 | MainScreen(navController) 42 | } 43 | 44 | composable(NavigationDestinations.WEB_VIEW) { 45 | WebViewScreen() 46 | } 47 | 48 | composable( 49 | route = NavigationDestinations.ANIMATION, 50 | arguments = listOf( 51 | navArgument("animationName") { 52 | type = NavType.StringType 53 | } 54 | ) 55 | ) { backStackEntry -> 56 | val encodedAnimationName = backStackEntry.arguments?.getString("animationName") ?: "" 57 | val animationName = 58 | URLDecoder.decode(encodedAnimationName, StandardCharsets.UTF_8.toString()) 59 | 60 | when (animationName) { 61 | "Swipe To Delete Animation" -> SwipeToDeleteAnimation() 62 | "Bouncing Ball Animation" -> BouncingBallAnimation() 63 | "Value Spring Animation" -> ValueSpringAnimation() 64 | "Content Animation" -> ContentAnimation() 65 | "Expandable Card Animation" -> ExpandableCardAnimation() 66 | "Wave Loading Bar" -> WaveLoadingBar() 67 | "Confetti Animation" -> BirthdayPopperAnimation() 68 | "Carousel Slider" -> CarouselSlider() 69 | "Flip Card" -> FlipCard() 70 | "Floating Elements" -> FloatingElements() 71 | "Type Writer Animation" -> TypeWriterAnimation() 72 | "Emoji Progress Bar" -> EmojiProgressBar() 73 | "Card Flipping" -> CardFlipping() 74 | "Button to Image" -> ButtonToImage() 75 | "Image Transition" -> AutoImageTransition() 76 | "Sliding Door" -> SlidingDoorAnimation() 77 | "Expanding Rings" -> ExpandingRings() 78 | "Orbiting Objects" -> OrbitingObjects() 79 | "Text Explosion" -> TextExplosion() 80 | else -> MainScreen(navController) 81 | } 82 | } 83 | } 84 | } 85 | 86 | object NavigationDestinations { 87 | const val MAIN = "main" 88 | const val ANIMATION = "animation/{animationName}" 89 | const val WEB_VIEW = "webView" 90 | 91 | fun createAnimationRoute(animationName: String): String { 92 | val encodedName = URLEncoder.encode(animationName, StandardCharsets.UTF_8.toString()) 93 | return "animation/$encodedName" 94 | } 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.foundation.isSystemInDarkTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 9 | import com.sp45.android_animations.ui.theme.AndroidAnimationsTheme 10 | import com.sp45.android_animations.util.ThemeManager 11 | 12 | class MainActivity : ComponentActivity() { 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | installSplashScreen().setKeepOnScreenCondition { 16 | false 17 | } 18 | setContent { 19 | AndroidAnimationsApp() 20 | } 21 | } 22 | } 23 | 24 | @Composable 25 | fun AndroidAnimationsApp() { 26 | val systemDarkTheme = isSystemInDarkTheme() 27 | ThemeManager.initializeTheme(systemDarkTheme) 28 | val darkTheme = ThemeManager.observeTheme() 29 | 30 | AndroidAnimationsTheme(darkTheme = darkTheme) { 31 | AppNavGraph() 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/MainScreen.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.PaddingValues 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.Spacer 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.fillMaxWidth 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.layout.size 13 | import androidx.compose.foundation.layout.width 14 | import androidx.compose.foundation.lazy.LazyColumn 15 | import androidx.compose.material.icons.Icons 16 | import androidx.compose.material.icons.filled.Info 17 | import androidx.compose.material3.Icon 18 | import androidx.compose.material3.MaterialTheme 19 | import androidx.compose.material3.Scaffold 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.graphics.Color 24 | import androidx.compose.ui.res.stringResource 25 | import androidx.compose.ui.unit.dp 26 | import androidx.navigation.NavController 27 | import com.sp45.android_animations.util.AnimationCard 28 | import com.sp45.android_animations.util.AnimationItem 29 | import com.sp45.android_animations.util.CustomTopAppBar 30 | import com.sp45.android_animations.util.FadingTextAnimation 31 | import com.sp45.android_animations.util.WebAppText 32 | 33 | val animations = listOf( 34 | AnimationItem("Swipe To Delete Animation", Color(0xFF6200EE)), 35 | AnimationItem("Bouncing Ball Animation", Color(0xFF03DAC5)), 36 | AnimationItem("Value Spring Animation", Color(0xFF00BCD4)), 37 | AnimationItem("Content Animation", Color(0xFFFFEB3B)), 38 | AnimationItem("Carousel Slider", Color(0xFF3F51B5)), 39 | AnimationItem("Expandable Card Animation", Color(0xFF4CAF50)), 40 | AnimationItem("Wave Loading Bar", Color(0xFFFF9800)), 41 | AnimationItem("Confetti Animation", Color(0xFF9C27B0)), 42 | AnimationItem("Flip Card", Color(0xFF2196F3)), 43 | AnimationItem("Floating Elements", Color(0xFFE91E63)), 44 | AnimationItem("Type Writer Animation", Color(0xFF4CAF50)), 45 | AnimationItem("Emoji Progress Bar", Color(0xFF3F51B5)), 46 | AnimationItem("Expanding Rings", Color(0xFF4CAF50)), 47 | AnimationItem("Orbiting Objects", Color(0xFF3F51B5)), 48 | AnimationItem("Card Flipping", Color(0xFFE91E63)), 49 | AnimationItem("Button to Image", Color(0xFFFF9800)), 50 | AnimationItem("Image Transition", Color(0xFF6200EE)), 51 | AnimationItem("Sliding Door", Color(0xFF00BCD4)), 52 | AnimationItem("Text Explosion", Color(0xFFE91E63)) 53 | ) 54 | 55 | @Composable 56 | fun MainScreen( 57 | navController: NavController 58 | ) { 59 | Scaffold( 60 | topBar = { 61 | CustomTopAppBar( 62 | title = "Android Animations" 63 | ) 64 | }, 65 | content = { paddingValues -> 66 | Column( 67 | modifier = Modifier 68 | .background(MaterialTheme.colorScheme.background) 69 | .padding(paddingValues) 70 | ) { 71 | Row( 72 | modifier = Modifier 73 | .fillMaxWidth() 74 | .padding(start = 10.dp, top = 7.dp, end = 10.dp, bottom = 7.dp), 75 | horizontalArrangement = Arrangement.Start, 76 | verticalAlignment = Alignment.CenterVertically, 77 | ) { 78 | Icon( 79 | Icons.Default.Info, 80 | contentDescription = stringResource(R.string.open_web_app), 81 | tint = MaterialTheme.colorScheme.primary, 82 | modifier = Modifier.size(22.dp) 83 | ) 84 | Spacer(Modifier.width(5.dp)) 85 | WebAppText( 86 | onClick = { 87 | navController.navigate(NavigationDestinations.WEB_VIEW) 88 | } 89 | ) 90 | Spacer(Modifier.width(5.dp)) 91 | FadingTextAnimation() 92 | } 93 | LazyColumn( 94 | contentPadding = PaddingValues(16.dp), 95 | verticalArrangement = Arrangement.spacedBy(16.dp), 96 | modifier = Modifier 97 | .fillMaxSize() 98 | .background(MaterialTheme.colorScheme.background) 99 | ) { 100 | items( 101 | animations.size, 102 | ) { index -> 103 | AnimationCard( 104 | animationItem = animations[index], 105 | onClick = { 106 | val route = 107 | NavigationDestinations.createAnimationRoute(animations[index].name) 108 | navController.navigate(route) 109 | } 110 | ) 111 | } 112 | } 113 | } 114 | } 115 | ) 116 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/AnimatedPlacement.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.AnimationVector2D 4 | import androidx.compose.animation.core.DeferredTargetAnimation 5 | import androidx.compose.animation.core.ExperimentalAnimatableApi 6 | import androidx.compose.animation.core.VectorConverter 7 | import androidx.compose.animation.core.tween 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.clickable 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.Column 12 | import androidx.compose.foundation.layout.Row 13 | import androidx.compose.foundation.layout.fillMaxSize 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.layout.size 16 | import androidx.compose.foundation.shape.RoundedCornerShape 17 | import androidx.compose.foundation.text.BasicText 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.getValue 20 | import androidx.compose.runtime.movableContentOf 21 | import androidx.compose.runtime.mutableStateOf 22 | import androidx.compose.runtime.remember 23 | import androidx.compose.runtime.setValue 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.geometry.Offset 27 | import androidx.compose.ui.graphics.Color 28 | import androidx.compose.ui.layout.ApproachLayoutModifierNode 29 | import androidx.compose.ui.layout.ApproachMeasureScope 30 | import androidx.compose.ui.layout.LayoutCoordinates 31 | import androidx.compose.ui.layout.LookaheadScope 32 | import androidx.compose.ui.layout.Measurable 33 | import androidx.compose.ui.layout.MeasureResult 34 | import androidx.compose.ui.layout.Placeable 35 | import androidx.compose.ui.node.ModifierNodeElement 36 | import androidx.compose.ui.text.TextStyle 37 | import androidx.compose.ui.text.font.FontWeight 38 | import androidx.compose.ui.unit.Constraints 39 | import androidx.compose.ui.unit.IntOffset 40 | import androidx.compose.ui.unit.IntSize 41 | import androidx.compose.ui.unit.dp 42 | import androidx.compose.ui.unit.round 43 | import androidx.compose.ui.unit.sp 44 | 45 | @Composable 46 | fun AnimatedPlacement() { 47 | var isInColumn by remember { mutableStateOf(true) } 48 | Column { 49 | BasicText( 50 | "With LookaheadScope", 51 | style = TextStyle.Default.copy( 52 | fontWeight = FontWeight.W900, 53 | fontSize = 30.sp 54 | ), 55 | modifier = Modifier 56 | .padding(30.dp) 57 | .align(Alignment.CenterHorizontally) 58 | ) 59 | LookaheadScope { 60 | val items = remember { 61 | movableContentOf { 62 | colors.forEach { color -> 63 | Box( 64 | Modifier 65 | .padding(15.dp) 66 | .size(100.dp, 80.dp) 67 | .then(AnimatePlacementNodeElement(this)) 68 | .background(color, RoundedCornerShape(20)) 69 | ) 70 | } 71 | } 72 | } 73 | 74 | Box( 75 | modifier = Modifier 76 | .fillMaxSize() 77 | .clickable { isInColumn = !isInColumn }) { 78 | if (isInColumn) { 79 | Column(Modifier.fillMaxSize()) { items() } 80 | } else { 81 | Row { items() } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | class AnimatedPlacementModifierNode(var lookaheadScope: LookaheadScope) : 89 | ApproachLayoutModifierNode, Modifier.Node() { 90 | @OptIn(ExperimentalAnimatableApi::class) 91 | val offsetAnimation: DeferredTargetAnimation = 92 | DeferredTargetAnimation(IntOffset.VectorConverter) 93 | 94 | override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean { 95 | return false 96 | } 97 | 98 | @OptIn(ExperimentalAnimatableApi::class) 99 | override fun Placeable.PlacementScope.isPlacementApproachInProgress( 100 | lookaheadCoordinates: LayoutCoordinates, 101 | ): Boolean { 102 | val target = 103 | with(lookaheadScope) { 104 | lookaheadScopeCoordinates.localLookaheadPositionOf(lookaheadCoordinates).round() 105 | } 106 | offsetAnimation.updateTarget(target, coroutineScope, animationSpec = tween(300)) 107 | return !offsetAnimation.isIdle 108 | } 109 | 110 | @OptIn(ExperimentalAnimatableApi::class) 111 | override fun ApproachMeasureScope.approachMeasure( 112 | measurable: Measurable, 113 | constraints: Constraints, 114 | ): MeasureResult { 115 | val placeable = measurable.measure(constraints) 116 | return layout(placeable.width, placeable.height) { 117 | val coordinates = coordinates 118 | if (coordinates != null) { 119 | val target = 120 | with(lookaheadScope) { 121 | lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates).round() 122 | } 123 | val animatedOffset = offsetAnimation.updateTarget(target, coroutineScope) 124 | val placementOffset = 125 | with(lookaheadScope) { 126 | lookaheadScopeCoordinates 127 | .localPositionOf(coordinates, Offset.Zero) 128 | .round() 129 | } 130 | // Calculates the delta between animated position in scope and current 131 | // position in scope, and places the child at the delta offset. This puts 132 | // the child layout at the animated position. 133 | val (x, y) = animatedOffset - placementOffset 134 | placeable.place(x, y) 135 | } else { 136 | placeable.place(0, 0) 137 | } 138 | } 139 | } 140 | } 141 | 142 | data class AnimatePlacementNodeElement(val lookaheadScope: LookaheadScope) : 143 | ModifierNodeElement() { 144 | 145 | override fun update(node: AnimatedPlacementModifierNode) { 146 | node.lookaheadScope = lookaheadScope 147 | } 148 | 149 | override fun create(): AnimatedPlacementModifierNode { 150 | return AnimatedPlacementModifierNode(lookaheadScope) 151 | } 152 | } 153 | 154 | val colors = listOf(Color(0xffff6f69), Color(0xffffcc5c), Color(0xff264653), Color(0xff2a9d84)) 155 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/AutoImageTransition.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.Animatable 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.Image 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.layout.Box 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.layout.size 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.LaunchedEffect 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.runtime.mutableIntStateOf 15 | import androidx.compose.runtime.remember 16 | import androidx.compose.runtime.setValue 17 | import androidx.compose.ui.Alignment 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.graphics.graphicsLayer 20 | import androidx.compose.ui.layout.ContentScale 21 | import androidx.compose.ui.res.painterResource 22 | import androidx.compose.ui.unit.dp 23 | import com.sp45.android_animations.R 24 | import kotlinx.coroutines.delay 25 | 26 | @Composable 27 | fun AutoImageTransition( 28 | displayTimeMillis: Long = 1000L, 29 | fadeDurationMillis: Int = 500 30 | ) { 31 | val images = listOf( 32 | R.drawable.img_2, 33 | R.drawable.img, 34 | R.drawable.ic_launcher_background 35 | ) 36 | 37 | var currentIndex by remember { mutableIntStateOf(0) } 38 | val fadeAnim = remember { Animatable(0f) } 39 | 40 | LaunchedEffect(currentIndex) { 41 | fadeAnim.animateTo(1f, animationSpec = tween(fadeDurationMillis)) 42 | delay(displayTimeMillis) 43 | fadeAnim.animateTo(0f, animationSpec = tween(fadeDurationMillis)) 44 | 45 | currentIndex = (currentIndex + 1) % images.size 46 | } 47 | 48 | Box( 49 | modifier = Modifier 50 | .fillMaxSize() 51 | .background(MaterialTheme.colorScheme.background), 52 | contentAlignment = Alignment.Center 53 | ) { 54 | Image( 55 | painter = painterResource(images[currentIndex]), 56 | contentDescription = "Carousel Image $currentIndex", 57 | modifier = Modifier 58 | .size(200.dp) 59 | .graphicsLayer(alpha = fadeAnim.value), 60 | contentScale = ContentScale.Crop 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/BouncingBallAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.LinearEasing 4 | import androidx.compose.animation.core.RepeatMode 5 | import androidx.compose.animation.core.infiniteRepeatable 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.foundation.Canvas 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.layout.Box 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.LaunchedEffect 15 | import androidx.compose.runtime.remember 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.geometry.Offset 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.compose.ui.unit.dp 21 | 22 | @Composable 23 | fun BouncingBallAnimation() { 24 | val ballPosition = remember { androidx.compose.animation.core.Animatable(0f) } 25 | val ballSize = 50.dp 26 | val ballColor = MaterialTheme.colorScheme.primary 27 | MaterialTheme.colorScheme.primary.copy(alpha = 0.3f) 28 | val ballScale = if (ballPosition.value > 490f) 1.2f else 1f 29 | 30 | LaunchedEffect(Unit) { 31 | ballPosition.animateTo( 32 | targetValue = 500f, 33 | animationSpec = infiniteRepeatable( 34 | animation = tween(1000, easing = LinearEasing), 35 | repeatMode = RepeatMode.Reverse 36 | ) 37 | ) 38 | } 39 | 40 | Box( 41 | modifier = Modifier 42 | .fillMaxSize() 43 | .background(MaterialTheme.colorScheme.background) 44 | .padding(100.dp,200.dp), 45 | contentAlignment = Alignment.Center, 46 | ) { 47 | Canvas(modifier = Modifier.fillMaxSize()) { 48 | drawCircle( 49 | color = Color.Black.copy(alpha = 0.3f), 50 | radius = (ballSize.toPx() / 2) * ballScale, 51 | center = Offset(size.width / 2, ballPosition.value + 8f) 52 | ) 53 | drawCircle( 54 | color = ballColor, 55 | radius = (ballSize.toPx() / 2) * ballScale, 56 | center = Offset(size.width / 2, ballPosition.value) 57 | ) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/ButtonToImage.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.core.animateFloatAsState 5 | import androidx.compose.animation.core.tween 6 | import androidx.compose.animation.fadeIn 7 | import androidx.compose.animation.fadeOut 8 | import androidx.compose.animation.scaleIn 9 | import androidx.compose.animation.scaleOut 10 | import androidx.compose.foundation.Image 11 | import androidx.compose.foundation.background 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.fillMaxSize 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.layout.size 16 | import androidx.compose.material3.Button 17 | import androidx.compose.material3.ButtonDefaults 18 | import androidx.compose.material3.MaterialTheme 19 | import androidx.compose.material3.Text 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 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.graphics.graphicsLayer 28 | import androidx.compose.ui.res.painterResource 29 | import androidx.compose.ui.res.stringResource 30 | import androidx.compose.ui.unit.dp 31 | import com.sp45.android_animations.R 32 | 33 | @Composable 34 | fun ButtonToImage() { 35 | var isVisible by remember { mutableStateOf(false) } 36 | 37 | val rotation by animateFloatAsState( 38 | targetValue = if (isVisible) 720f else 0f, 39 | animationSpec = tween(durationMillis = 1200), 40 | label = "" 41 | ) 42 | 43 | Box( 44 | modifier = Modifier 45 | .fillMaxSize() 46 | .background(MaterialTheme.colorScheme.background), 47 | contentAlignment = Alignment.Center 48 | ) { 49 | AnimatedVisibility( 50 | visible = !isVisible, 51 | enter = fadeIn(animationSpec = tween(durationMillis = 1000)), 52 | exit = fadeOut(animationSpec = tween(durationMillis = 1000)) 53 | ) { 54 | Button( 55 | onClick = { isVisible = true }, 56 | colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), 57 | modifier = Modifier 58 | .align(Alignment.BottomStart) 59 | .padding(16.dp) 60 | .size(width = 120.dp, height = 50.dp) 61 | ) { 62 | Text( 63 | text = stringResource(R.string.click_me), 64 | color = MaterialTheme.colorScheme.onPrimary 65 | ) 66 | } 67 | } 68 | 69 | AnimatedVisibility( 70 | visible = isVisible, 71 | enter = fadeIn(animationSpec = tween(durationMillis = 1200)) + 72 | scaleIn(initialScale = 0.5f, animationSpec = tween(durationMillis = 1200)), 73 | exit = fadeOut(animationSpec = tween(durationMillis = 1200)) + 74 | scaleOut(targetScale = 0.5f, animationSpec = tween(durationMillis = 1200)) 75 | ) { 76 | Image( 77 | painter = painterResource(id = R.drawable.img_2), 78 | contentDescription = stringResource(R.string.crakcode_image), 79 | modifier = Modifier 80 | .fillMaxSize() 81 | .graphicsLayer { 82 | rotationZ = rotation 83 | } 84 | ) 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/CardFlipAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.animateFloatAsState 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.Image 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.clickable 8 | import androidx.compose.foundation.layout.Arrangement 9 | import androidx.compose.foundation.layout.Box 10 | import androidx.compose.foundation.layout.Column 11 | import androidx.compose.foundation.layout.Spacer 12 | import androidx.compose.foundation.layout.fillMaxSize 13 | import androidx.compose.foundation.layout.height 14 | import androidx.compose.foundation.layout.size 15 | import androidx.compose.foundation.shape.RoundedCornerShape 16 | import androidx.compose.material3.MaterialTheme 17 | import androidx.compose.material3.Text 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.LaunchedEffect 20 | import androidx.compose.runtime.getValue 21 | import androidx.compose.runtime.mutableStateOf 22 | import androidx.compose.runtime.remember 23 | import androidx.compose.runtime.setValue 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.draw.clip 27 | import androidx.compose.ui.graphics.Brush 28 | import androidx.compose.ui.graphics.graphicsLayer 29 | import androidx.compose.ui.res.painterResource 30 | import androidx.compose.ui.res.stringResource 31 | import androidx.compose.ui.tooling.preview.Preview 32 | import androidx.compose.ui.unit.dp 33 | import com.sp45.android_animations.R 34 | 35 | @Composable 36 | fun CardFlipping() { 37 | var isFlipped by remember { mutableStateOf(false) } 38 | var showBackImage by remember { mutableStateOf(false) } 39 | 40 | val rotationY by animateFloatAsState( 41 | targetValue = if (isFlipped) 180f else 0f, 42 | animationSpec = tween(durationMillis = 1000), label = "" 43 | ) 44 | 45 | LaunchedEffect(rotationY) { 46 | if (rotationY > 90 && !showBackImage) { 47 | showBackImage = true 48 | } else if (rotationY <= 90 && showBackImage) { 49 | showBackImage = false 50 | } 51 | } 52 | 53 | Column( 54 | modifier = Modifier 55 | .fillMaxSize() 56 | .background(MaterialTheme.colorScheme.background), 57 | verticalArrangement = Arrangement.Center, 58 | horizontalAlignment = Alignment.CenterHorizontally 59 | ) { 60 | Box( 61 | modifier = Modifier 62 | .size(220.dp, 300.dp) 63 | .graphicsLayer { 64 | this.rotationY = rotationY 65 | } 66 | .clip(RoundedCornerShape(16.dp)) 67 | .background( 68 | Brush.linearGradient( 69 | colors = listOf( 70 | MaterialTheme.colorScheme.primary, 71 | MaterialTheme.colorScheme.secondary 72 | ) 73 | ) 74 | ) 75 | .clickable { isFlipped = !isFlipped }, 76 | contentAlignment = Alignment.Center 77 | ) { 78 | val imageResource = if (showBackImage) R.drawable.img else R.drawable.img_2 79 | Image( 80 | painter = painterResource(id = imageResource), 81 | contentDescription = if (showBackImage) stringResource(R.string.back_of_the_card) else stringResource( 82 | R.string.front_of_the_card 83 | ), 84 | modifier = Modifier 85 | .size(180.dp) 86 | .clip(RoundedCornerShape(12.dp)) 87 | ) 88 | } 89 | 90 | Spacer(modifier = Modifier.height(20.dp)) 91 | 92 | Text( 93 | text = if (isFlipped) stringResource(R.string.see_front) else stringResource(R.string.flip), 94 | color = MaterialTheme.colorScheme.onBackground 95 | ) 96 | } 97 | } 98 | 99 | @Preview 100 | @Composable 101 | fun RotatingCardPreview() { 102 | CardFlipping() 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/CardFlippingAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.animateFloatAsState 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.material3.Card 10 | import androidx.compose.material3.CardDefaults 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.LaunchedEffect 15 | import androidx.compose.runtime.getValue 16 | import androidx.compose.runtime.mutableStateOf 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.runtime.setValue 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.graphics.graphicsLayer 22 | import androidx.compose.ui.res.stringResource 23 | import androidx.compose.ui.text.font.FontWeight 24 | import androidx.compose.ui.unit.dp 25 | import androidx.compose.ui.unit.sp 26 | import com.sp45.android_animations.R 27 | import kotlinx.coroutines.delay 28 | 29 | @Composable 30 | fun FlipCard() { 31 | var cardFlipped by remember { mutableStateOf(false) } 32 | val rotation by animateFloatAsState( 33 | targetValue = if (cardFlipped) 180f else 0f, 34 | animationSpec = tween(600), label = "" 35 | ) 36 | 37 | LaunchedEffect(Unit) { 38 | while (true) { 39 | delay(800) 40 | cardFlipped = !cardFlipped 41 | } 42 | } 43 | 44 | Box( 45 | modifier = Modifier 46 | .fillMaxSize() 47 | .padding(16.dp), 48 | contentAlignment = Alignment.Center 49 | ) { 50 | Box( 51 | modifier = Modifier 52 | .size(200.dp) 53 | .graphicsLayer { 54 | rotationY = rotation 55 | cameraDistance = 12f * density 56 | } 57 | ) { 58 | if (rotation <= 90f) { 59 | Card( 60 | modifier = Modifier.fillMaxSize(), 61 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary), 62 | elevation = CardDefaults.cardElevation(12.dp) 63 | ) { 64 | Box( 65 | modifier = Modifier 66 | .fillMaxSize() 67 | .padding(16.dp), 68 | contentAlignment = Alignment.Center 69 | ) { 70 | Text( 71 | text = stringResource(R.string.front), 72 | fontSize = 24.sp, 73 | fontWeight = FontWeight.Bold, 74 | color = MaterialTheme.colorScheme.onPrimary 75 | ) 76 | } 77 | } 78 | } else { 79 | Card( 80 | modifier = Modifier 81 | .fillMaxSize() 82 | .graphicsLayer { rotationY = 180f }, 83 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondary), 84 | elevation = CardDefaults.cardElevation(12.dp) 85 | ) { 86 | Box( 87 | modifier = Modifier 88 | .fillMaxSize() 89 | .padding(16.dp), 90 | contentAlignment = Alignment.Center 91 | ) { 92 | Text( 93 | text = stringResource(R.string.back), 94 | fontSize = 24.sp, 95 | fontWeight = FontWeight.Bold, 96 | color = MaterialTheme.colorScheme.onSecondary 97 | ) 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/CarouselSlider.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.animation.core.animateDpAsState 5 | import androidx.compose.animation.core.animateFloatAsState 6 | import androidx.compose.foundation.Image 7 | import androidx.compose.foundation.background 8 | import androidx.compose.foundation.layout.Box 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.fillMaxWidth 11 | import androidx.compose.foundation.layout.offset 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.layout.size 14 | import androidx.compose.foundation.shape.RoundedCornerShape 15 | import androidx.compose.material3.Card 16 | import androidx.compose.material3.CardDefaults 17 | import androidx.compose.material3.MaterialTheme 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.LaunchedEffect 20 | import androidx.compose.runtime.getValue 21 | import androidx.compose.runtime.mutableIntStateOf 22 | import androidx.compose.runtime.remember 23 | import androidx.compose.runtime.setValue 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.draw.scale 27 | import androidx.compose.ui.graphics.graphicsLayer 28 | import androidx.compose.ui.layout.ContentScale 29 | import androidx.compose.ui.res.painterResource 30 | import androidx.compose.ui.unit.dp 31 | import com.sp45.android_animations.R 32 | import kotlinx.coroutines.delay 33 | 34 | @SuppressLint("UseOfNonLambdaOffsetOverload") 35 | @Composable 36 | fun CarouselSlider() { 37 | val images = listOf( 38 | R.drawable.cat1, 39 | R.drawable.cat2, 40 | R.drawable.cat3, 41 | R.drawable.cat4 42 | ) 43 | var currentIndex by remember { mutableIntStateOf(0) } 44 | val totalImages = images.size 45 | 46 | LaunchedEffect(Unit) { 47 | while (true) { 48 | delay(2000) 49 | currentIndex = (currentIndex + 1) % images.size 50 | } 51 | } 52 | 53 | Box( 54 | modifier = Modifier 55 | .fillMaxSize() 56 | .background(MaterialTheme.colorScheme.background), 57 | contentAlignment = Alignment.Center, 58 | ) { 59 | for (i in -1..1) { 60 | val index = (currentIndex + i + totalImages) % totalImages 61 | val imageRes = images[index] 62 | 63 | val isCenterImage = (i == 0) 64 | val scale by animateFloatAsState( 65 | targetValue = if (isCenterImage) 1.2f else 0.8f, label = "" 66 | ) 67 | val offsetX by animateDpAsState( 68 | targetValue = (i * 100).dp, label = "" 69 | ) 70 | 71 | Box( 72 | modifier = Modifier 73 | .offset(x = offsetX) 74 | .scale(scale) 75 | .graphicsLayer { 76 | alpha = if (isCenterImage) 1f else 0.5f 77 | }, 78 | contentAlignment = Alignment.Center 79 | ) { 80 | Card( 81 | modifier = Modifier 82 | .size(120.dp) 83 | .padding(8.dp), 84 | shape = RoundedCornerShape(16.dp), 85 | elevation = CardDefaults.cardElevation(8.dp), 86 | colors = CardDefaults.cardColors( 87 | containerColor = if (isCenterImage) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface, 88 | contentColor = if (isCenterImage) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface 89 | ) 90 | ) { 91 | Box( 92 | modifier = Modifier.fillMaxSize(), 93 | contentAlignment = Alignment.Center 94 | ) { 95 | Image( 96 | painter = painterResource(id = imageRes), 97 | contentDescription = null, 98 | contentScale = ContentScale.Crop, 99 | modifier = Modifier 100 | .fillMaxWidth() 101 | ) 102 | 103 | } 104 | } 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/ConfettiAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.Animatable 4 | import androidx.compose.animation.core.LinearEasing 5 | import androidx.compose.animation.core.tween 6 | import androidx.compose.foundation.Canvas 7 | import androidx.compose.foundation.background 8 | import androidx.compose.foundation.layout.Box 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.material3.Button 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.runtime.LaunchedEffect 16 | import androidx.compose.runtime.getValue 17 | import androidx.compose.runtime.mutableStateOf 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.runtime.rememberCoroutineScope 20 | import androidx.compose.runtime.setValue 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.geometry.Offset 24 | import androidx.compose.ui.graphics.Color 25 | import androidx.compose.ui.res.stringResource 26 | import androidx.compose.ui.unit.dp 27 | import com.sp45.android_animations.R 28 | import kotlinx.coroutines.launch 29 | import kotlin.math.PI 30 | import kotlin.math.cos 31 | import kotlin.math.sin 32 | import kotlin.random.Random 33 | 34 | private data class Confetti( 35 | var position: Offset, 36 | val color: Color, 37 | val size: Float, 38 | var velocity: Offset, 39 | var lifetime: Float 40 | ) 41 | 42 | @Composable 43 | fun BirthdayPopperAnimation() { 44 | var isPlaying by remember { mutableStateOf(false) } 45 | var confetti by remember { mutableStateOf(emptyList()) } 46 | val scope = rememberCoroutineScope() 47 | 48 | Box( 49 | modifier = Modifier 50 | .fillMaxSize() 51 | .background(MaterialTheme.colorScheme.background), 52 | contentAlignment = Alignment.Center 53 | ) { 54 | Button( 55 | onClick = { 56 | if (!isPlaying) { 57 | isPlaying = true 58 | confetti = generateConfetti(Offset(500f, 800f)) 59 | scope.launch { 60 | kotlinx.coroutines.delay(3000) 61 | isPlaying = false 62 | } 63 | } 64 | }, 65 | modifier = Modifier 66 | .align(Alignment.BottomCenter) 67 | .padding(bottom = 50.dp) 68 | ) { 69 | Text( 70 | text = stringResource(R.string.pop), 71 | color = MaterialTheme.colorScheme.onPrimary 72 | ) 73 | } 74 | 75 | if (isPlaying) { 76 | ConfettiAnimation(confetti) 77 | } 78 | } 79 | } 80 | 81 | @Composable 82 | private fun ConfettiAnimation(initialConfetti: List) { 83 | val animate = remember { Animatable(0f) } 84 | 85 | LaunchedEffect(Unit) { 86 | animate.animateTo( 87 | targetValue = 1f, 88 | animationSpec = tween( 89 | durationMillis = 3000, 90 | easing = LinearEasing 91 | ) 92 | ) 93 | } 94 | 95 | Canvas(modifier = Modifier.fillMaxSize()) { 96 | initialConfetti.forEach { confetti -> 97 | 98 | val gravity = 1000f * animate.value * animate.value 99 | 100 | val newPosition = Offset( 101 | x = confetti.position.x + (confetti.velocity.x * 900f * animate.value), 102 | y = confetti.position.y + (confetti.velocity.y * 900f * animate.value) + gravity 103 | ) 104 | 105 | val alpha = 2f - animate.value 106 | 107 | drawCircle( 108 | color = confetti.color.copy(alpha = alpha), 109 | radius = confetti.size, 110 | center = newPosition 111 | ) 112 | } 113 | } 114 | } 115 | 116 | private fun generateConfetti(origin: Offset): List { 117 | val colors = listOf( 118 | Color(0xFFFF69B4), 119 | Color(0xFFFFD700), 120 | Color(0xFF00FF00), 121 | Color(0xFF00BFFF), 122 | Color(0xFFFF1493), 123 | Color(0xFFFFA500) 124 | ) 125 | 126 | return List(200) { 127 | val angle = Random.nextDouble(PI * 0.8, PI * 2.2) 128 | val speed = Random.nextDouble(0.2, 1.0) 129 | 130 | Confetti( 131 | position = origin, 132 | color = colors.random(), 133 | size = Random.nextFloat() * 6f + 2f, 134 | velocity = Offset( 135 | x = (cos(angle) * speed).toFloat(), 136 | y = (sin(angle) * speed).toFloat() 137 | ), 138 | lifetime = 1f 139 | ) 140 | } 141 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/ContentAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.AnimatedContent 4 | import androidx.compose.animation.fadeIn 5 | import androidx.compose.animation.fadeOut 6 | import androidx.compose.animation.scaleIn 7 | import androidx.compose.animation.scaleOut 8 | import androidx.compose.animation.togetherWith 9 | import androidx.compose.foundation.Image 10 | import androidx.compose.foundation.background 11 | import androidx.compose.foundation.layout.Arrangement 12 | import androidx.compose.foundation.layout.Column 13 | import androidx.compose.foundation.layout.Spacer 14 | import androidx.compose.foundation.layout.fillMaxSize 15 | import androidx.compose.foundation.layout.height 16 | import androidx.compose.foundation.layout.size 17 | import androidx.compose.foundation.shape.RoundedCornerShape 18 | import androidx.compose.material3.Button 19 | import androidx.compose.material3.MaterialTheme 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.mutableStateOf 24 | import androidx.compose.runtime.remember 25 | import androidx.compose.runtime.setValue 26 | import androidx.compose.ui.Alignment 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.draw.clip 29 | import androidx.compose.ui.layout.ContentScale 30 | import androidx.compose.ui.res.painterResource 31 | import androidx.compose.ui.res.stringResource 32 | import androidx.compose.ui.unit.dp 33 | import com.sp45.android_animations.R 34 | 35 | @Composable 36 | fun ContentAnimation() { 37 | var isPictureOne by remember { mutableStateOf(true) } 38 | val image1 = painterResource(R.drawable.img_2) 39 | val image2 = painterResource(R.drawable.img) 40 | 41 | Column( 42 | modifier = Modifier 43 | .fillMaxSize() 44 | .background(MaterialTheme.colorScheme.background), 45 | horizontalAlignment = Alignment.CenterHorizontally, 46 | verticalArrangement = Arrangement.Top, 47 | 48 | ) { 49 | Button( 50 | onClick = { isPictureOne = !isPictureOne }, 51 | colors = androidx.compose.material3.ButtonDefaults.buttonColors( 52 | containerColor = MaterialTheme.colorScheme.primary, 53 | contentColor = MaterialTheme.colorScheme.onPrimary 54 | ) 55 | ) { 56 | Text(stringResource(R.string.change_profile_picture)) 57 | } 58 | Spacer(modifier = Modifier.height(16.dp)) 59 | AnimatedContent( 60 | targetState = isPictureOne, 61 | transitionSpec = { 62 | (fadeIn() + scaleIn(initialScale = 0.8f)).togetherWith( 63 | fadeOut() + scaleOut( 64 | targetScale = 1.2f 65 | ) 66 | ) 67 | }, label = "" 68 | ) { targetState -> 69 | Image( 70 | painter = if (targetState) image1 else image2, 71 | contentDescription = null, 72 | contentScale = ContentScale.FillWidth, 73 | modifier = Modifier 74 | .size(150.dp) 75 | .clip(RoundedCornerShape(16.dp)) 76 | .background(MaterialTheme.colorScheme.surface) 77 | ) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/EmojiProgressBar.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.LinearEasing 4 | import androidx.compose.animation.core.RepeatMode 5 | import androidx.compose.animation.core.animateFloat 6 | import androidx.compose.animation.core.animateFloatAsState 7 | import androidx.compose.animation.core.infiniteRepeatable 8 | import androidx.compose.animation.core.rememberInfiniteTransition 9 | import androidx.compose.animation.core.tween 10 | import androidx.compose.foundation.background 11 | import androidx.compose.foundation.layout.Arrangement 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.Column 14 | import androidx.compose.foundation.layout.Row 15 | import androidx.compose.foundation.layout.fillMaxSize 16 | import androidx.compose.foundation.layout.fillMaxWidth 17 | import androidx.compose.foundation.layout.height 18 | import androidx.compose.foundation.layout.padding 19 | import androidx.compose.material3.MaterialTheme 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.graphics.Color 26 | import androidx.compose.ui.graphics.graphicsLayer 27 | import androidx.compose.ui.unit.dp 28 | import androidx.compose.ui.unit.sp 29 | 30 | @Composable 31 | fun EmojiProgressBar() { 32 | val totalCheckpoints = 5 33 | val durationMillis = 3000 34 | val infiniteTransition = rememberInfiniteTransition(label = "") 35 | val progressAnim by infiniteTransition.animateFloat( 36 | initialValue = 0f, 37 | targetValue = 1f, 38 | animationSpec = infiniteRepeatable( 39 | animation = tween(durationMillis, easing = LinearEasing), 40 | repeatMode = RepeatMode.Restart 41 | ), label = "" 42 | ) 43 | 44 | Column( 45 | modifier = Modifier 46 | .fillMaxWidth() 47 | .fillMaxSize() 48 | .background(MaterialTheme.colorScheme.background), 49 | horizontalAlignment = Alignment.CenterHorizontally, 50 | verticalArrangement = Arrangement.Center 51 | ) { 52 | ProgressBarEmojiScaling( 53 | progress = progressAnim, 54 | totalCheckpoints = totalCheckpoints 55 | ) 56 | } 57 | } 58 | 59 | @Composable 60 | fun ProgressBarEmojiScaling(progress: Float, totalCheckpoints: Int) { 61 | val emojis = listOf("🌟", "🔥", "💪", "🎯", "✅") 62 | val checkpointFraction = 1f / (totalCheckpoints - 1) 63 | 64 | Box( 65 | modifier = Modifier 66 | .fillMaxWidth() 67 | .padding(horizontal = 16.dp) 68 | .height(50.dp), 69 | contentAlignment = Alignment.Center 70 | ) { 71 | Box( 72 | modifier = Modifier 73 | .fillMaxWidth() 74 | .height(4.dp) 75 | .background(MaterialTheme.colorScheme.onBackground.copy(alpha = 0.2f)) 76 | ) { 77 | Box( 78 | modifier = Modifier 79 | .fillMaxWidth(fraction = progress) 80 | .height(4.dp) 81 | .background(MaterialTheme.colorScheme.primary) 82 | ) 83 | } 84 | 85 | Row( 86 | modifier = Modifier 87 | .fillMaxWidth() 88 | .padding(horizontal = 16.dp), 89 | horizontalArrangement = Arrangement.SpaceBetween, 90 | verticalAlignment = Alignment.CenterVertically 91 | ) { 92 | emojis.forEachIndexed { index, emoji -> 93 | val isReached = progress >= (index * checkpointFraction) 94 | val scale by animateFloatAsState( 95 | targetValue = if (isReached && progress <= ((index + 1) * checkpointFraction)) 1.5f else 1f, 96 | animationSpec = tween(durationMillis = 300), label = "" 97 | ) 98 | 99 | Text( 100 | text = emoji, 101 | fontSize = 24.sp, 102 | color = if (isReached) Color(0xFF4CAF50) else Color.LightGray, 103 | modifier = Modifier.graphicsLayer(scaleX = scale, scaleY = scale) 104 | ) 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/ExpandableCardsAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.EaseInOut 4 | import androidx.compose.animation.core.animateDpAsState 5 | import androidx.compose.animation.core.tween 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.clickable 8 | import androidx.compose.foundation.layout.Arrangement 9 | import androidx.compose.foundation.layout.Box 10 | import androidx.compose.foundation.layout.Column 11 | import androidx.compose.foundation.layout.fillMaxHeight 12 | import androidx.compose.foundation.layout.fillMaxSize 13 | import androidx.compose.foundation.layout.height 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.layout.width 16 | import androidx.compose.foundation.shape.RoundedCornerShape 17 | import androidx.compose.material3.Card 18 | import androidx.compose.material3.CardDefaults 19 | import androidx.compose.material3.MaterialTheme 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.mutableStateOf 24 | import androidx.compose.runtime.remember 25 | import androidx.compose.runtime.setValue 26 | import androidx.compose.ui.Alignment 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.res.stringResource 29 | import androidx.compose.ui.text.font.FontWeight 30 | import androidx.compose.ui.text.style.TextAlign 31 | import androidx.compose.ui.unit.dp 32 | import androidx.compose.ui.unit.sp 33 | import com.sp45.android_animations.R 34 | 35 | @Composable 36 | fun ExpandableCardAnimation() { 37 | var expanded by remember { mutableStateOf(false) } 38 | val cardHeight by animateDpAsState( 39 | targetValue = if (expanded) 250.dp else 120.dp, 40 | animationSpec = tween(durationMillis = 500, easing = EaseInOut), label = "" 41 | ) 42 | val cardWidth by animateDpAsState( 43 | targetValue = if (expanded) 250.dp else 120.dp, 44 | animationSpec = tween(durationMillis = 500, easing = EaseInOut), label = "" 45 | ) 46 | 47 | Column( 48 | modifier = Modifier 49 | .fillMaxSize() 50 | .background(MaterialTheme.colorScheme.background), 51 | verticalArrangement = Arrangement.Center, 52 | horizontalAlignment = Alignment.CenterHorizontally 53 | ) { 54 | Box( 55 | modifier = Modifier, 56 | contentAlignment = Alignment.Center, 57 | ) { 58 | Card( 59 | modifier = Modifier 60 | .height(cardHeight) 61 | .width(cardWidth) 62 | .fillMaxHeight() 63 | .clickable { expanded = !expanded }, 64 | shape = RoundedCornerShape(24.dp), 65 | elevation = CardDefaults.cardElevation(8.dp), 66 | colors = CardDefaults.cardColors( 67 | containerColor = MaterialTheme.colorScheme.primary, 68 | contentColor = MaterialTheme.colorScheme.onPrimary 69 | ), 70 | ) { 71 | Box( 72 | modifier = Modifier 73 | .fillMaxSize() 74 | .padding(16.dp), 75 | contentAlignment = Alignment.Center 76 | ) { 77 | Text( 78 | text = 79 | if (expanded) 80 | stringResource(R.string.tap_to_collapse) 81 | else 82 | stringResource(R.string.tap_to_expand), 83 | fontSize = if (expanded) 20.sp else 18.sp, 84 | fontWeight = FontWeight.Bold, 85 | style = MaterialTheme.typography.bodyLarge, 86 | color = MaterialTheme.colorScheme.onPrimary, 87 | textAlign = TextAlign.Center 88 | ) 89 | } 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/ExpandingRingsAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.LinearEasing 4 | import androidx.compose.animation.core.RepeatMode 5 | import androidx.compose.animation.core.animateFloat 6 | import androidx.compose.animation.core.infiniteRepeatable 7 | import androidx.compose.animation.core.rememberInfiniteTransition 8 | import androidx.compose.animation.core.tween 9 | import androidx.compose.foundation.Canvas 10 | import androidx.compose.foundation.background 11 | import androidx.compose.foundation.layout.Arrangement 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.Column 14 | import androidx.compose.foundation.layout.fillMaxSize 15 | import androidx.compose.foundation.layout.size 16 | import androidx.compose.material3.MaterialTheme 17 | import androidx.compose.material3.Text 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.getValue 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.res.stringResource 24 | import androidx.compose.ui.text.style.TextAlign 25 | import androidx.compose.ui.unit.dp 26 | import androidx.compose.ui.unit.sp 27 | import com.sp45.android_animations.R 28 | 29 | @Composable 30 | fun ExpandingRings() { 31 | val infiniteTransition = rememberInfiniteTransition(label = "") 32 | 33 | @Composable 34 | fun CreateRingAnimation(delayMillis: Int): Pair { 35 | val radius by infiniteTransition.animateFloat( 36 | initialValue = 0f, 37 | targetValue = 300f, 38 | animationSpec = infiniteRepeatable( 39 | animation = tween(1200, easing = LinearEasing, delayMillis = delayMillis), 40 | repeatMode = RepeatMode.Restart 41 | ), label = "" 42 | ) 43 | val alpha by infiniteTransition.animateFloat( 44 | initialValue = 1f, 45 | targetValue = 0f, 46 | animationSpec = infiniteRepeatable( 47 | animation = tween(1200, easing = LinearEasing, delayMillis = delayMillis), 48 | repeatMode = RepeatMode.Restart 49 | ), label = "" 50 | ) 51 | return radius to alpha 52 | } 53 | 54 | val (radius1, alpha1) = CreateRingAnimation(0) 55 | val (radius2, alpha2) = CreateRingAnimation(400) 56 | val (radius3, alpha3) = CreateRingAnimation(800) 57 | 58 | Column( 59 | modifier = Modifier 60 | .fillMaxSize() 61 | .background(MaterialTheme.colorScheme.background), 62 | verticalArrangement = Arrangement.Center, 63 | horizontalAlignment = Alignment.CenterHorizontally 64 | ) { 65 | Box( 66 | modifier = Modifier.fillMaxSize(0.3f), 67 | contentAlignment = Alignment.Center 68 | ) { 69 | Canvas(modifier = Modifier.size(600.dp)) { 70 | drawCircle(color = Color.Cyan.copy(alpha = alpha1), radius = radius1) 71 | drawCircle(color = Color.Magenta.copy(alpha = alpha2), radius = radius2) 72 | drawCircle(color = Color.Yellow.copy(alpha = alpha3), radius = radius3) 73 | } 74 | } 75 | Text( 76 | text = stringResource(R.string.loading), 77 | fontSize = 20.sp, 78 | color = MaterialTheme.colorScheme.onBackground, 79 | textAlign = TextAlign.Center 80 | ) 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/FloatingElements.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.FastOutSlowInEasing 4 | import androidx.compose.animation.core.InfiniteTransition 5 | import androidx.compose.animation.core.LinearEasing 6 | import androidx.compose.animation.core.RepeatMode 7 | import androidx.compose.animation.core.animateFloat 8 | import androidx.compose.animation.core.infiniteRepeatable 9 | import androidx.compose.animation.core.rememberInfiniteTransition 10 | import androidx.compose.animation.core.tween 11 | import androidx.compose.foundation.background 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.fillMaxSize 14 | import androidx.compose.foundation.layout.offset 15 | import androidx.compose.foundation.layout.size 16 | import androidx.compose.foundation.shape.CircleShape 17 | import androidx.compose.foundation.shape.RoundedCornerShape 18 | import androidx.compose.material3.MaterialTheme 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.runtime.getValue 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.draw.alpha 24 | import androidx.compose.ui.draw.rotate 25 | import androidx.compose.ui.draw.scale 26 | import androidx.compose.ui.unit.dp 27 | 28 | @Composable 29 | fun FloatingElements() { 30 | val infiniteTransition = rememberInfiniteTransition(label = "") 31 | 32 | Box( 33 | modifier = Modifier 34 | .fillMaxSize() 35 | .background(MaterialTheme.colorScheme.background) 36 | ) { 37 | repeat(6) { index -> 38 | AnimatedElement( 39 | infiniteTransition = infiniteTransition, 40 | index = index 41 | ) 42 | } 43 | } 44 | } 45 | 46 | @Composable 47 | private fun AnimatedElement( 48 | infiniteTransition: InfiniteTransition, 49 | index: Int 50 | ) { 51 | val baseDelay = 500 * index 52 | 53 | val scale by infiniteTransition.animateFloat( 54 | initialValue = 0.8f, 55 | targetValue = 1.2f, 56 | animationSpec = infiniteRepeatable( 57 | animation = tween( 58 | durationMillis = 2000 + (index * 100), 59 | easing = FastOutSlowInEasing, 60 | delayMillis = baseDelay 61 | ), 62 | repeatMode = RepeatMode.Reverse 63 | ), label = "" 64 | ) 65 | 66 | val rotation by infiniteTransition.animateFloat( 67 | initialValue = 0f, 68 | targetValue = 360f, 69 | animationSpec = infiniteRepeatable( 70 | animation = tween( 71 | durationMillis = 3000 + (index * 200), 72 | easing = LinearEasing, 73 | delayMillis = baseDelay 74 | ) 75 | ), label = "" 76 | ) 77 | 78 | val xOffset by infiniteTransition.animateFloat( 79 | initialValue = -100f, 80 | targetValue = 100f, 81 | animationSpec = infiniteRepeatable( 82 | animation = tween( 83 | durationMillis = 4000 + (index * 300), 84 | easing = FastOutSlowInEasing, 85 | delayMillis = baseDelay 86 | ), 87 | repeatMode = RepeatMode.Reverse 88 | ), label = "" 89 | ) 90 | 91 | val yOffset by infiniteTransition.animateFloat( 92 | initialValue = -50f, 93 | targetValue = 50f, 94 | animationSpec = infiniteRepeatable( 95 | animation = tween( 96 | durationMillis = 3000 + (index * 300), 97 | easing = FastOutSlowInEasing, 98 | delayMillis = baseDelay 99 | ), 100 | repeatMode = RepeatMode.Reverse 101 | ), label = "" 102 | ) 103 | 104 | val alpha by infiniteTransition.animateFloat( 105 | initialValue = 0.4f, 106 | targetValue = 0.8f, 107 | animationSpec = infiniteRepeatable( 108 | animation = tween( 109 | durationMillis = 2000, 110 | easing = FastOutSlowInEasing, 111 | delayMillis = baseDelay 112 | ), 113 | repeatMode = RepeatMode.Reverse 114 | ), label = "" 115 | ) 116 | 117 | val elementColors = listOf( 118 | MaterialTheme.colorScheme.primary, 119 | MaterialTheme.colorScheme.secondary, 120 | MaterialTheme.colorScheme.tertiary, 121 | MaterialTheme.colorScheme.error, 122 | MaterialTheme.colorScheme.inverseOnSurface, 123 | MaterialTheme.colorScheme.inversePrimary 124 | ) 125 | 126 | val isSquare = index % 2 == 0 127 | val size = (40 + (index * 5)).dp 128 | 129 | Box( 130 | modifier = Modifier 131 | .fillMaxSize(), 132 | contentAlignment = Alignment.Center 133 | ) { 134 | Box( 135 | modifier = Modifier 136 | .offset(x = xOffset.dp, y = yOffset.dp) 137 | .scale(scale) 138 | .rotate(rotation) 139 | .alpha(alpha) 140 | .size(size) 141 | .background( 142 | elementColors[index % elementColors.size], 143 | if (isSquare) RoundedCornerShape(8.dp) else CircleShape 144 | ) 145 | ) 146 | } 147 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/OrbitingObjects.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.InfiniteTransition 4 | import androidx.compose.animation.core.LinearEasing 5 | import androidx.compose.animation.core.RepeatMode 6 | import androidx.compose.animation.core.animateFloat 7 | import androidx.compose.animation.core.infiniteRepeatable 8 | import androidx.compose.animation.core.rememberInfiniteTransition 9 | import androidx.compose.animation.core.tween 10 | import androidx.compose.foundation.Canvas 11 | import androidx.compose.foundation.background 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.fillMaxSize 14 | import androidx.compose.material3.MaterialTheme 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.graphics.Color 19 | import androidx.compose.ui.unit.dp 20 | import kotlin.math.cos 21 | import kotlin.math.sin 22 | 23 | @Composable 24 | fun OrbitingObjects() { 25 | val infiniteTransition = rememberInfiniteTransition(label = "") 26 | 27 | val orbits = listOf( 28 | Triple(100f, 4000, Color.Red), 29 | Triple(150f, 6000, Color.Blue), 30 | Triple(200f, 8000, Color.Green), 31 | Triple(250f, 10000, Color.Yellow) 32 | ) 33 | 34 | val animatedAngles = orbits.map { (_, speed, _) -> 35 | createOrbitAnimation(speed, infiniteTransition) 36 | } 37 | 38 | Box( 39 | modifier = Modifier 40 | .fillMaxSize() 41 | .background(MaterialTheme.colorScheme.background), 42 | contentAlignment = Alignment.Center 43 | ) { 44 | Canvas(modifier = Modifier.fillMaxSize()) { 45 | val centerX = size.width / 2 46 | val centerY = size.height / 2 47 | 48 | orbits.forEachIndexed { index, (radius, _, color) -> 49 | val angle = animatedAngles[index] 50 | 51 | val radians = Math.toRadians(angle.toDouble()) 52 | 53 | val x = centerX + radius * cos(radians).toFloat() 54 | val y = centerY + radius * sin(radians).toFloat() 55 | 56 | drawCircle(color = color, radius = 10.dp.toPx(), center = androidx.compose.ui.geometry.Offset(x, y)) 57 | } 58 | 59 | drawCircle(color = Color.Gray, radius = 20.dp.toPx(), center = androidx.compose.ui.geometry.Offset(centerX, centerY)) 60 | } 61 | } 62 | } 63 | 64 | @Composable 65 | fun createOrbitAnimation(speedMillis: Int, infiniteTransition: InfiniteTransition): Float { 66 | return infiniteTransition.animateFloat( 67 | initialValue = 0f, 68 | targetValue = 360f, 69 | animationSpec = infiniteRepeatable( 70 | animation = tween(speedMillis, easing = LinearEasing), 71 | repeatMode = RepeatMode.Restart 72 | ), label = "" 73 | ).value 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/SlidingDoor.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.animation.core.FastOutSlowInEasing 5 | import androidx.compose.animation.core.animateDpAsState 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.foundation.Image 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.gestures.detectHorizontalDragGestures 10 | import androidx.compose.foundation.layout.Arrangement 11 | import androidx.compose.foundation.layout.Box 12 | import androidx.compose.foundation.layout.Column 13 | import androidx.compose.foundation.layout.Spacer 14 | import androidx.compose.foundation.layout.fillMaxSize 15 | import androidx.compose.foundation.layout.height 16 | import androidx.compose.foundation.layout.padding 17 | import androidx.compose.foundation.layout.width 18 | import androidx.compose.foundation.shape.RoundedCornerShape 19 | import androidx.compose.material3.MaterialTheme 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.mutableFloatStateOf 24 | import androidx.compose.runtime.remember 25 | import androidx.compose.runtime.setValue 26 | import androidx.compose.ui.Alignment 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.draw.clip 29 | import androidx.compose.ui.input.pointer.pointerInput 30 | import androidx.compose.ui.layout.ContentScale 31 | import androidx.compose.ui.res.painterResource 32 | import androidx.compose.ui.res.stringResource 33 | import androidx.compose.ui.tooling.preview.Preview 34 | import androidx.compose.ui.unit.dp 35 | import com.sp45.android_animations.R 36 | 37 | @SuppressLint("AutoboxingStateCreation") 38 | @Composable 39 | fun SlidingDoorAnimation() { 40 | var widthState by remember { mutableFloatStateOf(0f) } 41 | val width by animateDpAsState( 42 | targetValue = widthState.dp, 43 | animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing), label = "" 44 | ) 45 | 46 | Box( 47 | modifier = Modifier 48 | .fillMaxSize() 49 | .background(MaterialTheme.colorScheme.background) 50 | .pointerInput(Unit) { 51 | detectHorizontalDragGestures { _, dragAmount -> 52 | val dragChange = dragAmount * 5f 53 | widthState = (widthState + dragChange).coerceIn(0f, 500f) 54 | } 55 | } 56 | ) { 57 | Column( 58 | modifier = Modifier 59 | .fillMaxSize() 60 | .padding(16.dp), 61 | verticalArrangement = Arrangement.Center, 62 | horizontalAlignment = Alignment.CenterHorizontally 63 | ) { 64 | Box( 65 | modifier = Modifier 66 | .width(width) 67 | .height(300.dp) 68 | .clip(RoundedCornerShape(16.dp)) 69 | ) { 70 | 71 | Image( 72 | painter = painterResource(id = R.drawable.img_2), 73 | contentDescription = stringResource(R.string.hidden_content), 74 | modifier = Modifier.fillMaxSize(), 75 | contentScale = ContentScale.Crop 76 | ) 77 | } 78 | 79 | Spacer(modifier = Modifier.height(20.dp)) 80 | 81 | Text( 82 | text = stringResource(R.string.Swipe_right_or_left_to_open_or_close_the_door), 83 | modifier = Modifier.align(Alignment.CenterHorizontally), 84 | color = MaterialTheme.colorScheme.onBackground 85 | ) 86 | } 87 | } 88 | } 89 | 90 | @Preview 91 | @Composable 92 | fun PreviewSlidingDoorAnimation() { 93 | SlidingDoorAnimation() 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/SwipeToDeleteAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.core.Animatable 5 | import androidx.compose.animation.core.FastOutSlowInEasing 6 | import androidx.compose.animation.core.Spring 7 | import androidx.compose.animation.core.spring 8 | import androidx.compose.animation.core.tween 9 | import androidx.compose.animation.fadeIn 10 | import androidx.compose.animation.fadeOut 11 | import androidx.compose.animation.slideInVertically 12 | import androidx.compose.animation.slideOutVertically 13 | import androidx.compose.foundation.background 14 | import androidx.compose.foundation.gestures.detectHorizontalDragGestures 15 | import androidx.compose.foundation.layout.Arrangement 16 | import androidx.compose.foundation.layout.Box 17 | import androidx.compose.foundation.layout.Column 18 | import androidx.compose.foundation.layout.Row 19 | import androidx.compose.foundation.layout.Spacer 20 | import androidx.compose.foundation.layout.fillMaxSize 21 | import androidx.compose.foundation.layout.fillMaxWidth 22 | import androidx.compose.foundation.layout.offset 23 | import androidx.compose.foundation.layout.padding 24 | import androidx.compose.foundation.layout.size 25 | import androidx.compose.foundation.layout.width 26 | import androidx.compose.foundation.shape.RoundedCornerShape 27 | import androidx.compose.material.icons.Icons 28 | import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowLeft 29 | import androidx.compose.material.icons.rounded.Delete 30 | import androidx.compose.material.icons.rounded.Refresh 31 | import androidx.compose.material3.ButtonDefaults 32 | import androidx.compose.material3.Card 33 | import androidx.compose.material3.CardDefaults 34 | import androidx.compose.material3.ElevatedButton 35 | import androidx.compose.material3.Icon 36 | import androidx.compose.material3.MaterialTheme 37 | import androidx.compose.material3.Text 38 | import androidx.compose.runtime.Composable 39 | import androidx.compose.runtime.getValue 40 | import androidx.compose.runtime.mutableFloatStateOf 41 | import androidx.compose.runtime.mutableStateOf 42 | import androidx.compose.runtime.remember 43 | import androidx.compose.runtime.rememberCoroutineScope 44 | import androidx.compose.runtime.setValue 45 | import androidx.compose.ui.Alignment 46 | import androidx.compose.ui.Modifier 47 | import androidx.compose.ui.draw.alpha 48 | import androidx.compose.ui.draw.clip 49 | import androidx.compose.ui.input.pointer.pointerInput 50 | import androidx.compose.ui.res.stringResource 51 | import androidx.compose.ui.unit.IntOffset 52 | import androidx.compose.ui.unit.dp 53 | import com.sp45.android_animations.R 54 | import kotlinx.coroutines.launch 55 | import kotlin.math.roundToInt 56 | 57 | @Composable 58 | fun SwipeToDeleteAnimation() { 59 | var cardVisible by remember { mutableStateOf(true) } 60 | var showUndo by remember { mutableStateOf(false) } 61 | 62 | Box( 63 | modifier = Modifier.fillMaxSize(), 64 | contentAlignment = Alignment.Center 65 | ) { 66 | if (cardVisible) { 67 | SwipeAbleCard( 68 | modifier = Modifier.fillMaxWidth(), 69 | onDismiss = { 70 | cardVisible = false 71 | showUndo = true 72 | } 73 | ) 74 | } 75 | 76 | AnimatedVisibility( 77 | visible = showUndo, 78 | enter = fadeIn() + slideInVertically(), 79 | exit = fadeOut() + slideOutVertically() 80 | ) { 81 | ElevatedButton( 82 | onClick = { 83 | cardVisible = true 84 | showUndo = false 85 | }, 86 | modifier = Modifier.padding(top = 8.dp), 87 | colors = ButtonDefaults.elevatedButtonColors( 88 | containerColor = MaterialTheme.colorScheme.secondaryContainer, 89 | contentColor = MaterialTheme.colorScheme.onSecondaryContainer 90 | ), 91 | elevation = ButtonDefaults.elevatedButtonElevation( 92 | defaultElevation = 6.dp, 93 | pressedElevation = 8.dp 94 | ) 95 | ) { 96 | Icon( 97 | imageVector = Icons.Rounded.Refresh, 98 | contentDescription = stringResource(R.string.undo), 99 | modifier = Modifier.size(16.dp) 100 | ) 101 | Spacer(modifier = Modifier.width(8.dp)) 102 | Text(stringResource(R.string.undo)) 103 | } 104 | } 105 | } 106 | } 107 | 108 | @Composable 109 | fun SwipeAbleCard( 110 | modifier: Modifier = Modifier, 111 | onDismiss: () -> Unit 112 | ) { 113 | val coroutineScope = rememberCoroutineScope() 114 | val offsetX = remember { Animatable(0f) } 115 | val dismissThreshold = 300f 116 | 117 | val swipeBackgroundColor = MaterialTheme.colorScheme.errorContainer 118 | val swipeIconColor = MaterialTheme.colorScheme.onErrorContainer 119 | val cardContainerColor = MaterialTheme.colorScheme.surface 120 | val cardTextColor = MaterialTheme.colorScheme.onSurface 121 | 122 | val swipeProgress by remember(offsetX.value) { 123 | mutableFloatStateOf((-offsetX.value / dismissThreshold).coerceIn(0f, 1f)) 124 | } 125 | 126 | Box( 127 | modifier = modifier 128 | .padding(16.dp) 129 | .clip(RoundedCornerShape(16.dp)) 130 | ) { 131 | Box( 132 | modifier = Modifier 133 | .matchParentSize() 134 | .background(swipeBackgroundColor.copy(alpha = 0.9f * swipeProgress)), 135 | contentAlignment = Alignment.CenterEnd 136 | ) { 137 | Row( 138 | modifier = Modifier.padding(end = 24.dp), 139 | verticalAlignment = Alignment.CenterVertically 140 | ) { 141 | Icon( 142 | imageVector = Icons.Rounded.Delete, 143 | contentDescription = stringResource(R.string.delete), 144 | tint = swipeIconColor, 145 | modifier = Modifier 146 | .size(24.dp) 147 | .alpha(swipeProgress) 148 | ) 149 | Spacer(modifier = Modifier.width(8.dp)) 150 | Text( 151 | text = stringResource(R.string.delete), 152 | color = swipeIconColor, 153 | style = MaterialTheme.typography.bodyLarge, 154 | modifier = Modifier.alpha(swipeProgress) 155 | ) 156 | } 157 | } 158 | 159 | Card( 160 | modifier = Modifier 161 | .fillMaxWidth() 162 | .offset { IntOffset(offsetX.value.roundToInt(), 0) } 163 | .pointerInput(Unit) { 164 | detectHorizontalDragGestures( 165 | onDragEnd = { 166 | coroutineScope.launch { 167 | if (offsetX.value < -dismissThreshold) { 168 | offsetX.animateTo( 169 | targetValue = -size.width.toFloat(), 170 | animationSpec = tween( 171 | durationMillis = 300, 172 | easing = FastOutSlowInEasing 173 | ) 174 | ) 175 | onDismiss() 176 | } else { 177 | offsetX.animateTo( 178 | targetValue = 0f, 179 | animationSpec = spring( 180 | dampingRatio = Spring.DampingRatioMediumBouncy, 181 | stiffness = Spring.StiffnessLow 182 | ) 183 | ) 184 | } 185 | } 186 | }, 187 | onDragCancel = { 188 | coroutineScope.launch { 189 | offsetX.animateTo(0f) 190 | } 191 | }, 192 | onHorizontalDrag = { _, dragAmount -> 193 | coroutineScope.launch { 194 | val newOffset = offsetX.value + dragAmount 195 | offsetX.snapTo(newOffset.coerceAtMost(0f)) 196 | } 197 | } 198 | ) 199 | }, 200 | colors = CardDefaults.cardColors( 201 | containerColor = cardContainerColor 202 | ), 203 | elevation = CardDefaults.cardElevation( 204 | defaultElevation = 4.dp, 205 | pressedElevation = 8.dp 206 | ) 207 | ) { 208 | Row( 209 | modifier = Modifier 210 | .fillMaxWidth() 211 | .padding(16.dp), 212 | horizontalArrangement = Arrangement.SpaceBetween, 213 | verticalAlignment = Alignment.CenterVertically 214 | ) { 215 | Column { 216 | Text( 217 | text = stringResource(R.string.swipe_to_delete), 218 | style = MaterialTheme.typography.titleMedium, 219 | color = cardTextColor 220 | ) 221 | Text( 222 | text = stringResource(R.string.slide_left_to_remove_this_item), 223 | style = MaterialTheme.typography.bodyMedium, 224 | color = cardTextColor.copy(alpha = 0.7f), 225 | modifier = Modifier.padding(top = 4.dp) 226 | ) 227 | } 228 | Icon( 229 | imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowLeft, 230 | contentDescription = stringResource(R.string.swipe_left), 231 | tint = cardTextColor.copy(alpha = 0.7f), 232 | modifier = Modifier.size(24.dp) 233 | ) 234 | } 235 | } 236 | } 237 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/TextCollapsingAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.animation.core.LinearEasing 5 | import androidx.compose.animation.core.RepeatMode 6 | import androidx.compose.animation.core.animateFloat 7 | import androidx.compose.animation.core.infiniteRepeatable 8 | import androidx.compose.animation.core.rememberInfiniteTransition 9 | import androidx.compose.animation.core.tween 10 | import androidx.compose.foundation.background 11 | import androidx.compose.foundation.layout.Arrangement 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.Row 14 | import androidx.compose.foundation.layout.fillMaxSize 15 | import androidx.compose.foundation.layout.offset 16 | import androidx.compose.material3.MaterialTheme 17 | import androidx.compose.material3.Text 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.remember 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.res.stringResource 24 | import androidx.compose.ui.text.TextStyle 25 | import androidx.compose.ui.unit.dp 26 | import androidx.compose.ui.unit.sp 27 | import com.sp45.android_animations.R 28 | import kotlin.random.Random 29 | 30 | @SuppressLint("UseOfNonLambdaOffsetOverload") 31 | @Composable 32 | fun TextExplosion() { 33 | val text = stringResource(R.string.crakcode) 34 | val infiniteTransition = rememberInfiniteTransition(label = "") 35 | 36 | val randomOffsets = remember { 37 | text.map { 38 | Pair( 39 | Random.nextFloat() * 500 - 250, 40 | Random.nextFloat() * 500 - 250 41 | ) 42 | } 43 | } 44 | 45 | val animations = randomOffsets.mapIndexed { _, (targetX, targetY) -> 46 | val offsetX = infiniteTransition.animateFloat( 47 | initialValue = 0f, 48 | targetValue = targetX, 49 | animationSpec = infiniteRepeatable( 50 | animation = tween( 51 | durationMillis = 3000, 52 | easing = LinearEasing 53 | ), 54 | repeatMode = RepeatMode.Reverse 55 | ), label = "" 56 | ) 57 | 58 | val offsetY = infiniteTransition.animateFloat( 59 | initialValue = 0f, 60 | targetValue = targetY, 61 | animationSpec = infiniteRepeatable( 62 | animation = tween( 63 | durationMillis = 3000, 64 | easing = LinearEasing 65 | ), 66 | repeatMode = RepeatMode.Reverse 67 | ), label = "" 68 | ) 69 | 70 | Pair(offsetX, offsetY) 71 | } 72 | 73 | Box( 74 | modifier = Modifier 75 | .fillMaxSize() 76 | .background(MaterialTheme.colorScheme.background), 77 | contentAlignment = Alignment.Center 78 | ) { 79 | Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { 80 | text.forEachIndexed { index, char -> 81 | val (offsetX, offsetY) = animations[index] 82 | 83 | Box( 84 | modifier = Modifier 85 | .offset(offsetX.value.dp, offsetY.value.dp), 86 | contentAlignment = Alignment.Center 87 | ) { 88 | Text( 89 | text = char.toString(), 90 | style = TextStyle( 91 | color = Color.Green, 92 | fontSize = 32.sp 93 | ) 94 | ) 95 | } 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/TypeWriterAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.animateContentSize 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.LaunchedEffect 15 | import androidx.compose.runtime.mutableIntStateOf 16 | import androidx.compose.runtime.mutableStateOf 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.graphics.Color 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.compose.ui.text.TextStyle 23 | import androidx.compose.ui.text.font.FontWeight 24 | import androidx.compose.ui.unit.dp 25 | import androidx.compose.ui.unit.sp 26 | import com.sp45.android_animations.R 27 | import kotlinx.coroutines.delay 28 | 29 | @Composable 30 | fun TypeWriterAnimation() { 31 | Box( modifier = Modifier.fillMaxSize(), 32 | contentAlignment = Alignment.Center){ 33 | TypeWriterComponent( 34 | text = stringResource(R.string.type_writer_animation), 35 | textStyle = TextStyle( 36 | fontSize = 24.sp, 37 | fontWeight = FontWeight.Bold, 38 | color = MaterialTheme.colorScheme.onBackground 39 | ), 40 | cursorColor = MaterialTheme.colorScheme.onBackground 41 | ) 42 | } 43 | } 44 | 45 | 46 | @Composable 47 | fun TypeWriterComponent( 48 | text: String, 49 | textStyle: TextStyle = MaterialTheme.typography.bodyLarge.copy( 50 | fontSize = 24.sp, 51 | fontWeight = MaterialTheme.typography.bodyLarge.fontWeight, 52 | color = MaterialTheme.colorScheme.onBackground 53 | ), 54 | cursorColor: Color = MaterialTheme.colorScheme.onBackground, 55 | cursorBlinkDuration: Int = 600, 56 | charAppearanceDuration: Int = 350, 57 | charAppearanceDelay: Int = 130 58 | ) { 59 | val currentTextIndex = remember { mutableIntStateOf(0) } 60 | val cursorVisibility = remember { mutableStateOf(true) } 61 | 62 | LaunchedEffect(text) { 63 | while (true) { 64 | while (true) { 65 | cursorVisibility.value = !cursorVisibility.value 66 | delay(cursorBlinkDuration.toLong()) 67 | } 68 | } 69 | } 70 | 71 | LaunchedEffect(text) { 72 | for (i in text.indices) { 73 | currentTextIndex.intValue = i 74 | delay(charAppearanceDelay.toLong()) 75 | } 76 | } 77 | 78 | Row( 79 | modifier = Modifier 80 | .fillMaxWidth() 81 | .padding(16.dp), 82 | verticalAlignment = Alignment.CenterVertically, 83 | horizontalArrangement = Arrangement.Center 84 | ) { 85 | Text( 86 | text.take(currentTextIndex.intValue), 87 | style = textStyle, 88 | modifier = Modifier.animateContentSize( 89 | animationSpec = tween(charAppearanceDuration) 90 | ) 91 | ) 92 | if (cursorVisibility.value) { 93 | Text( 94 | "|", 95 | style = textStyle.copy(color = cursorColor), 96 | modifier = Modifier.padding(start = 4.dp) 97 | ) 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/ValueSpringAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.animateColorAsState 4 | import androidx.compose.animation.core.Spring 5 | import androidx.compose.animation.core.animateDpAsState 6 | import androidx.compose.animation.core.spring 7 | import androidx.compose.foundation.background 8 | import androidx.compose.foundation.layout.Box 9 | import androidx.compose.foundation.layout.Column 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.fillMaxWidth 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.layout.size 14 | import androidx.compose.foundation.shape.RoundedCornerShape 15 | import androidx.compose.material3.MaterialTheme 16 | import androidx.compose.material3.Slider 17 | import androidx.compose.material3.SliderDefaults 18 | import androidx.compose.material3.Text 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.runtime.getValue 21 | import androidx.compose.runtime.mutableFloatStateOf 22 | import androidx.compose.runtime.remember 23 | import androidx.compose.runtime.setValue 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.draw.clip 27 | import androidx.compose.ui.graphics.Color 28 | import androidx.compose.ui.res.stringResource 29 | import androidx.compose.ui.text.font.FontWeight 30 | import androidx.compose.ui.unit.dp 31 | import com.sp45.android_animations.R 32 | 33 | @Composable 34 | fun ValueSpringAnimation() { 35 | Column( 36 | modifier = Modifier 37 | .fillMaxSize() 38 | .background(MaterialTheme.colorScheme.background), 39 | horizontalAlignment = Alignment.CenterHorizontally 40 | ) { 41 | var sliderPosition by remember { mutableFloatStateOf(0f) } 42 | var roundnessPercent by remember { mutableFloatStateOf(0f) } 43 | 44 | Column( 45 | modifier = Modifier 46 | .fillMaxWidth() 47 | .padding(16.dp), 48 | horizontalAlignment = Alignment.CenterHorizontally 49 | ) { 50 | Slider( 51 | value = sliderPosition, 52 | onValueChange = { sliderPosition = it }, 53 | valueRange = 0f..300f, 54 | steps = 5, 55 | colors = SliderDefaults.colors( 56 | thumbColor = MaterialTheme.colorScheme.primary, 57 | activeTrackColor = MaterialTheme.colorScheme.primary, 58 | inactiveTrackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) 59 | ) 60 | ) 61 | Text( 62 | text = stringResource(R.string.dynamic_circle_of_size_dp, sliderPosition.toInt()), 63 | color = MaterialTheme.colorScheme.onBackground, 64 | style = MaterialTheme.typography.bodyMedium, 65 | fontWeight = FontWeight.Bold 66 | ) 67 | Slider( 68 | value = roundnessPercent, 69 | onValueChange = { roundnessPercent = it }, 70 | valueRange = 0f..100f, 71 | steps = 5, 72 | colors = SliderDefaults.colors( 73 | thumbColor = MaterialTheme.colorScheme.secondary, 74 | activeTrackColor = MaterialTheme.colorScheme.secondary, 75 | inactiveTrackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) 76 | ) 77 | ) 78 | Text( 79 | text = stringResource( 80 | R.string.dynamic_circle_of_roundness, 81 | roundnessPercent.toInt() 82 | ), 83 | color = MaterialTheme.colorScheme.onBackground, 84 | style = MaterialTheme.typography.bodyMedium, 85 | fontWeight = FontWeight.Bold 86 | ) 87 | } 88 | 89 | val sliderSize by animateDpAsState( 90 | targetValue = sliderPosition.dp, 91 | label = "Size animation", 92 | animationSpec = spring( 93 | dampingRatio = Spring.DampingRatioHighBouncy, 94 | stiffness = Spring.StiffnessVeryLow 95 | ) 96 | ) 97 | 98 | val color: Color by animateColorAsState( 99 | targetValue = when { 100 | sliderPosition < 100f -> Color.Green 101 | sliderPosition < 200f -> Color.Yellow 102 | else -> Color.Red 103 | }, 104 | label = stringResource(R.string.color_animation) 105 | ) 106 | 107 | Box( 108 | modifier = Modifier.fillMaxSize(), 109 | contentAlignment = Alignment.Center 110 | ) { 111 | val roundnessDp = (roundnessPercent / 100f) * (sliderSize.value / 2) 112 | Box( 113 | modifier = Modifier 114 | .size(sliderSize) 115 | .clip(RoundedCornerShape(roundnessDp.dp)) 116 | .background(color), 117 | contentAlignment = Alignment.Center 118 | ) { 119 | 120 | } 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/animations/WaveLoadingBar.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.animations 2 | 3 | import androidx.compose.animation.core.LinearEasing 4 | import androidx.compose.animation.core.RepeatMode 5 | import androidx.compose.animation.core.animateFloat 6 | import androidx.compose.animation.core.infiniteRepeatable 7 | import androidx.compose.animation.core.rememberInfiniteTransition 8 | import androidx.compose.animation.core.tween 9 | import androidx.compose.foundation.Canvas 10 | import androidx.compose.foundation.background 11 | import androidx.compose.foundation.layout.Arrangement 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.Column 14 | import androidx.compose.foundation.layout.fillMaxSize 15 | import androidx.compose.foundation.layout.fillMaxWidth 16 | import androidx.compose.foundation.layout.height 17 | import androidx.compose.foundation.layout.padding 18 | import androidx.compose.material3.MaterialTheme 19 | import androidx.compose.material3.Text 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.getValue 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.graphics.Color 25 | import androidx.compose.ui.graphics.drawscope.translate 26 | import androidx.compose.ui.res.stringResource 27 | import androidx.compose.ui.unit.dp 28 | import com.sp45.android_animations.R 29 | 30 | @Composable 31 | fun WaveLoadingBar() { 32 | val waveColors = listOf(Color(0xFF4CAF50), Color(0xFF2196F3), Color(0xFFFF5722)) 33 | val waveOffsets = listOf(0f, 0.4f, 0.8f) 34 | 35 | Column( 36 | modifier = Modifier 37 | .fillMaxSize() 38 | .background(MaterialTheme.colorScheme.background), 39 | horizontalAlignment = Alignment.CenterHorizontally, 40 | verticalArrangement = Arrangement.Center 41 | ) { 42 | Box( 43 | modifier = Modifier 44 | .fillMaxWidth() 45 | .height(100.dp) 46 | .padding(vertical = 24.dp) 47 | ) { 48 | waveColors.forEachIndexed { index, color -> 49 | Wave( 50 | waveColor = color, 51 | offsetFactor = waveOffsets[index], 52 | modifier = Modifier.fillMaxSize() 53 | ) 54 | } 55 | } 56 | 57 | Text( 58 | text = stringResource(R.string.loading), 59 | color = MaterialTheme.colorScheme.onBackground, 60 | style = MaterialTheme.typography.bodyLarge 61 | ) 62 | } 63 | } 64 | 65 | @Composable 66 | fun Wave(waveColor: Color, offsetFactor: Float, modifier: Modifier = Modifier) { 67 | val infiniteTransition = rememberInfiniteTransition(label = "") 68 | val waveAnim by infiniteTransition.animateFloat( 69 | initialValue = 0f, 70 | targetValue = 1f, 71 | animationSpec = infiniteRepeatable( 72 | animation = tween(2000, easing = LinearEasing), 73 | repeatMode = RepeatMode.Restart 74 | ), label = "" 75 | ) 76 | 77 | Canvas(modifier = modifier) { 78 | val width = size.width 79 | val height = size.height 80 | val waveHeight = 20.dp.toPx() 81 | 82 | val wavePoints = List(10) { i -> 83 | val x = width * i / 9 84 | val y = height / 2 + waveHeight * kotlin.math.sin((i + waveAnim * 10 + offsetFactor * 10) * 0.5f) 85 | x to y 86 | } 87 | 88 | translate(left = -width / 10) { 89 | drawPath( 90 | path = androidx.compose.ui.graphics.Path().apply { 91 | moveTo(0f, height / 2) 92 | wavePoints.forEach { (x, y) -> 93 | lineTo(x, y) 94 | } 95 | lineTo(width, height) 96 | lineTo(0f, height) 97 | close() 98 | }, 99 | color = waveColor.copy(alpha = 0.5f) 100 | ) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.ui.theme 2 | import androidx.compose.ui.graphics.Color 3 | 4 | val primaryLight = Color(0xFF365E9D) 5 | val onPrimaryLight = Color(0xFFFFFFFF) 6 | val primaryContainerLight = Color(0xFF82A8EC) 7 | val onPrimaryContainerLight = Color(0xFF001C40) 8 | val secondaryLight = Color(0xFF515F79) 9 | val onSecondaryLight = Color(0xFFFFFFFF) 10 | val secondaryContainerLight = Color(0xFFD9E4FF) 11 | val onSecondaryContainerLight = Color(0xFF3C4962) 12 | val tertiaryLight = Color(0xFF814A87) 13 | val onTertiaryLight = Color(0xFFFFFFFF) 14 | val tertiaryContainerLight = Color(0xFFD091D4) 15 | val onTertiaryContainerLight = Color(0xFF36013F) 16 | val errorLight = Color(0xFFBA1A1A) 17 | val onErrorLight = Color(0xFFFFFFFF) 18 | val errorContainerLight = Color(0xFFFFDAD6) 19 | val onErrorContainerLight = Color(0xFF410002) 20 | val backgroundLight = Color(0xFFF9F9FF) 21 | val onBackgroundLight = Color(0xFF1A1C20) 22 | val surfaceLight = Color(0xFFF9F9FF) 23 | val onSurfaceLight = Color(0xFF1A1C20) 24 | val surfaceVariantLight = Color(0xFFDFE2EE) 25 | val onSurfaceVariantLight = Color(0xFF434750) 26 | val outlineLight = Color(0xFF737781) 27 | val outlineVariantLight = Color(0xFFC3C6D2) 28 | val scrimLight = Color(0xFF000000) 29 | val inverseSurfaceLight = Color(0xFF2F3035) 30 | val inverseOnSurfaceLight = Color(0xFFF1F0F6) 31 | val inversePrimaryLight = Color(0xFFAAC7FF) 32 | val surfaceDimLight = Color(0xFFDAD9DF) 33 | val surfaceBrightLight = Color(0xFFF9F9FF) 34 | val surfaceContainerLowestLight = Color(0xFFFFFFFF) 35 | val surfaceContainerLowLight = Color(0xFFF3F3F9) 36 | val surfaceContainerLight = Color(0xFFEEEDF3) 37 | val surfaceContainerHighLight = Color(0xFFE8E7ED) 38 | val surfaceContainerHighestLight = Color(0xFFE2E2E8) 39 | 40 | val primaryDark = Color(0xFFAAC7FF) 41 | val onPrimaryDark = Color(0xFF002F64) 42 | val primaryContainerDark = Color(0xFF6E94D6) 43 | val onPrimaryContainerDark = Color(0xFF000000) 44 | val secondaryDark = Color(0xFFB9C7E5) 45 | val onSecondaryDark = Color(0xFF233148) 46 | val secondaryContainerDark = Color(0xFF323F58) 47 | val onSecondaryContainerDark = Color(0xFFC6D4F3) 48 | val tertiaryDark = Color(0xFFF2B0F5) 49 | val onTertiaryDark = Color(0xFF4D1A55) 50 | val tertiaryContainerDark = Color(0xFFBC7FC0) 51 | val onTertiaryContainerDark = Color(0xFF000000) 52 | val errorDark = Color(0xFFFFB4AB) 53 | val onErrorDark = Color(0xFF690005) 54 | val errorContainerDark = Color(0xFF93000A) 55 | val onErrorContainerDark = Color(0xFFFFDAD6) 56 | val backgroundDark = Color(0xFF121317) 57 | val onBackgroundDark = Color(0xFFE2E2E8) 58 | val surfaceDark = Color(0xFF121317) 59 | val onSurfaceDark = Color(0xFFE2E2E8) 60 | val surfaceVariantDark = Color(0xFF434750) 61 | val onSurfaceVariantDark = Color(0xFFC3C6D2) 62 | val outlineDark = Color(0xFF8D909B) 63 | val outlineVariantDark = Color(0xFF434750) 64 | val scrimDark = Color(0xFF000000) 65 | val inverseSurfaceDark = Color(0xFFE2E2E8) 66 | val inverseOnSurfaceDark = Color(0xFF2F3035) 67 | val inversePrimaryDark = Color(0xFF365E9D) 68 | val surfaceDimDark = Color(0xFF121317) 69 | val surfaceBrightDark = Color(0xFF38393E) 70 | val surfaceContainerLowestDark = Color(0xFF0C0E12) 71 | val surfaceContainerLowDark = Color(0xFF1A1C20) 72 | val surfaceContainerDark = Color(0xFF1E2024) 73 | val surfaceContainerHighDark = Color(0xFF282A2E) 74 | val surfaceContainerHighestDark = Color(0xFF333539) -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.darkColorScheme 6 | import androidx.compose.material3.lightColorScheme 7 | import androidx.compose.runtime.Composable 8 | 9 | private val lightScheme = lightColorScheme( 10 | primary = primaryLight, 11 | onPrimary = onPrimaryLight, 12 | primaryContainer = primaryContainerLight, 13 | onPrimaryContainer = onPrimaryContainerLight, 14 | secondary = secondaryLight, 15 | onSecondary = onSecondaryLight, 16 | secondaryContainer = secondaryContainerLight, 17 | onSecondaryContainer = onSecondaryContainerLight, 18 | tertiary = tertiaryLight, 19 | onTertiary = onTertiaryLight, 20 | tertiaryContainer = tertiaryContainerLight, 21 | onTertiaryContainer = onTertiaryContainerLight, 22 | error = errorLight, 23 | onError = onErrorLight, 24 | errorContainer = errorContainerLight, 25 | onErrorContainer = onErrorContainerLight, 26 | background = backgroundLight, 27 | onBackground = onBackgroundLight, 28 | surface = surfaceLight, 29 | onSurface = onSurfaceLight, 30 | surfaceVariant = surfaceVariantLight, 31 | onSurfaceVariant = onSurfaceVariantLight, 32 | outline = outlineLight, 33 | outlineVariant = outlineVariantLight, 34 | scrim = scrimLight, 35 | inverseSurface = inverseSurfaceLight, 36 | inverseOnSurface = inverseOnSurfaceLight, 37 | inversePrimary = inversePrimaryLight, 38 | surfaceDim = surfaceDimLight, 39 | surfaceBright = surfaceBrightLight, 40 | surfaceContainerLowest = surfaceContainerLowestLight, 41 | surfaceContainerLow = surfaceContainerLowLight, 42 | surfaceContainer = surfaceContainerLight, 43 | surfaceContainerHigh = surfaceContainerHighLight, 44 | surfaceContainerHighest = surfaceContainerHighestLight, 45 | ) 46 | 47 | private val darkScheme = darkColorScheme( 48 | primary = primaryDark, 49 | onPrimary = onPrimaryDark, 50 | primaryContainer = primaryContainerDark, 51 | onPrimaryContainer = onPrimaryContainerDark, 52 | secondary = secondaryDark, 53 | onSecondary = onSecondaryDark, 54 | secondaryContainer = secondaryContainerDark, 55 | onSecondaryContainer = onSecondaryContainerDark, 56 | tertiary = tertiaryDark, 57 | onTertiary = onTertiaryDark, 58 | tertiaryContainer = tertiaryContainerDark, 59 | onTertiaryContainer = onTertiaryContainerDark, 60 | error = errorDark, 61 | onError = onErrorDark, 62 | errorContainer = errorContainerDark, 63 | onErrorContainer = onErrorContainerDark, 64 | background = backgroundDark, 65 | onBackground = onBackgroundDark, 66 | surface = surfaceDark, 67 | onSurface = onSurfaceDark, 68 | surfaceVariant = surfaceVariantDark, 69 | onSurfaceVariant = onSurfaceVariantDark, 70 | outline = outlineDark, 71 | outlineVariant = outlineVariantDark, 72 | scrim = scrimDark, 73 | inverseSurface = inverseSurfaceDark, 74 | inverseOnSurface = inverseOnSurfaceDark, 75 | inversePrimary = inversePrimaryDark, 76 | surfaceDim = surfaceDimDark, 77 | surfaceBright = surfaceBrightDark, 78 | surfaceContainerLowest = surfaceContainerLowestDark, 79 | surfaceContainerLow = surfaceContainerLowDark, 80 | surfaceContainer = surfaceContainerDark, 81 | surfaceContainerHigh = surfaceContainerHighDark, 82 | surfaceContainerHighest = surfaceContainerHighestDark, 83 | ) 84 | 85 | @Composable 86 | fun AndroidAnimationsTheme( 87 | darkTheme: Boolean = isSystemInDarkTheme(), 88 | content: @Composable () -> Unit 89 | ) { 90 | val colorScheme = if (darkTheme) darkScheme else lightScheme 91 | 92 | MaterialTheme( 93 | colorScheme = colorScheme, 94 | typography = AppTypography, 95 | content = content 96 | ) 97 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.font.FontFamily 5 | import androidx.compose.ui.text.googlefonts.Font 6 | import androidx.compose.ui.text.googlefonts.GoogleFont 7 | import com.sp45.android_animations.R 8 | 9 | val provider = GoogleFont.Provider( 10 | providerAuthority = "com.google.android.gms.fonts", 11 | providerPackage = "com.google.android.gms", 12 | certificates = R.array.com_google_android_gms_fonts_certs 13 | ) 14 | 15 | val bodyFontFamily = FontFamily( 16 | Font( 17 | googleFont = GoogleFont("Montserrat Alternates"), 18 | fontProvider = provider, 19 | ) 20 | ) 21 | 22 | val displayFontFamily = FontFamily( 23 | Font( 24 | googleFont = GoogleFont("Abril Fatface"), 25 | fontProvider = provider, 26 | ) 27 | ) 28 | 29 | val baseline = Typography() 30 | 31 | val AppTypography = Typography( 32 | displayLarge = baseline.displayLarge.copy(fontFamily = displayFontFamily), 33 | displayMedium = baseline.displayMedium.copy(fontFamily = displayFontFamily), 34 | displaySmall = baseline.displaySmall.copy(fontFamily = displayFontFamily), 35 | headlineLarge = baseline.headlineLarge.copy(fontFamily = displayFontFamily), 36 | headlineMedium = baseline.headlineMedium.copy(fontFamily = displayFontFamily), 37 | headlineSmall = baseline.headlineSmall.copy(fontFamily = displayFontFamily), 38 | titleLarge = baseline.titleLarge.copy(fontFamily = displayFontFamily), 39 | titleMedium = baseline.titleMedium.copy(fontFamily = displayFontFamily), 40 | titleSmall = baseline.titleSmall.copy(fontFamily = displayFontFamily), 41 | bodyLarge = baseline.bodyLarge.copy(fontFamily = bodyFontFamily), 42 | bodyMedium = baseline.bodyMedium.copy(fontFamily = bodyFontFamily), 43 | bodySmall = baseline.bodySmall.copy(fontFamily = bodyFontFamily), 44 | labelLarge = baseline.labelLarge.copy(fontFamily = bodyFontFamily), 45 | labelMedium = baseline.labelMedium.copy(fontFamily = bodyFontFamily), 46 | labelSmall = baseline.labelSmall.copy(fontFamily = bodyFontFamily), 47 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/util/AnimationCard.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.util 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.animateContentSize 5 | import androidx.compose.animation.core.animateFloatAsState 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.animation.fadeIn 8 | import androidx.compose.animation.fadeOut 9 | import androidx.compose.animation.scaleIn 10 | import androidx.compose.animation.scaleOut 11 | import androidx.compose.foundation.background 12 | import androidx.compose.foundation.clickable 13 | import androidx.compose.foundation.interaction.MutableInteractionSource 14 | import androidx.compose.foundation.interaction.collectIsHoveredAsState 15 | import androidx.compose.foundation.layout.Arrangement 16 | import androidx.compose.foundation.layout.Box 17 | import androidx.compose.foundation.layout.Column 18 | import androidx.compose.foundation.layout.Row 19 | import androidx.compose.foundation.layout.Spacer 20 | import androidx.compose.foundation.layout.fillMaxWidth 21 | import androidx.compose.foundation.layout.height 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.foundation.shape.RoundedCornerShape 24 | import androidx.compose.material.icons.Icons 25 | import androidx.compose.material.icons.filled.PlayArrow 26 | import androidx.compose.material.ripple.rememberRipple 27 | import androidx.compose.material3.Card 28 | import androidx.compose.material3.CardDefaults 29 | import androidx.compose.material3.Icon 30 | import androidx.compose.material3.IconButton 31 | import androidx.compose.material3.MaterialTheme 32 | import androidx.compose.material3.Text 33 | import androidx.compose.runtime.Composable 34 | import androidx.compose.runtime.LaunchedEffect 35 | import androidx.compose.runtime.getValue 36 | import androidx.compose.runtime.mutableStateOf 37 | import androidx.compose.runtime.remember 38 | import androidx.compose.runtime.setValue 39 | import androidx.compose.ui.Alignment 40 | import androidx.compose.ui.ExperimentalComposeUiApi 41 | import androidx.compose.ui.Modifier 42 | import androidx.compose.ui.draw.clip 43 | import androidx.compose.ui.draw.drawBehind 44 | import androidx.compose.ui.geometry.Offset 45 | import androidx.compose.ui.graphics.Color 46 | import androidx.compose.ui.graphics.drawscope.rotate 47 | import androidx.compose.ui.graphics.graphicsLayer 48 | import androidx.compose.ui.input.pointer.pointerInteropFilter 49 | import androidx.compose.ui.res.stringResource 50 | import androidx.compose.ui.unit.dp 51 | import com.sp45.android_animations.R 52 | 53 | data class AnimationItem( 54 | val name: String, 55 | val color: Color 56 | ) 57 | 58 | @OptIn(ExperimentalComposeUiApi::class) 59 | @Composable 60 | fun AnimationCard( 61 | animationItem: AnimationItem, 62 | onClick: () -> Unit 63 | ) { 64 | val interactionSource = remember { MutableInteractionSource() } 65 | val isHovered by interactionSource.collectIsHoveredAsState() 66 | 67 | var isPressed by remember { mutableStateOf(false) } 68 | var isVisible by remember { mutableStateOf(false) } 69 | 70 | val scale by animateFloatAsState( 71 | targetValue = if (isHovered) 1.02f else 1f 72 | ) 73 | 74 | val accentColor = animationItem.color 75 | 76 | LaunchedEffect(Unit) { 77 | isVisible = true 78 | } 79 | 80 | AnimatedVisibility( 81 | visible = isVisible, 82 | enter = fadeIn(animationSpec = tween(500)) + scaleIn(animationSpec = tween(500)), 83 | exit = fadeOut(animationSpec = tween(500)) + scaleOut(animationSpec = tween(500)) 84 | ) { 85 | Card( 86 | modifier = Modifier 87 | .fillMaxWidth() 88 | .graphicsLayer { 89 | scaleX = scale 90 | scaleY = scale 91 | } 92 | .pointerInteropFilter { 93 | when (it.action) { 94 | android.view.MotionEvent.ACTION_DOWN -> isPressed = true 95 | android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> isPressed = 96 | false 97 | } 98 | false 99 | } 100 | .clickable( 101 | interactionSource = interactionSource, 102 | indication = rememberRipple(bounded = true), 103 | onClick = onClick 104 | ) 105 | .animateContentSize(), 106 | shape = RoundedCornerShape(16.dp), 107 | colors = CardDefaults.cardColors( 108 | containerColor = MaterialTheme.colorScheme.secondaryContainer, 109 | contentColor = MaterialTheme.colorScheme.onSecondaryContainer 110 | ), 111 | elevation = CardDefaults.cardElevation(2.dp) 112 | ) { 113 | Box( 114 | modifier = Modifier 115 | .fillMaxWidth() 116 | .drawBehind { 117 | rotate(45f) { 118 | drawCircle( 119 | color = accentColor.copy(alpha = 0.1f), 120 | radius = size.width * 0.2f, 121 | center = Offset(size.width * 0.8f, size.height * 0.2f) 122 | ) 123 | } 124 | } 125 | .padding(16.dp), 126 | ) { 127 | Row( 128 | modifier = Modifier.fillMaxWidth(), 129 | horizontalArrangement = Arrangement.SpaceBetween, 130 | verticalAlignment = Alignment.CenterVertically 131 | ) { 132 | Column { 133 | 134 | Text( 135 | text = animationItem.name, 136 | style = MaterialTheme.typography.titleMedium, 137 | color = MaterialTheme.colorScheme.onSurface 138 | ) 139 | Spacer(modifier = Modifier.height(4.dp)) 140 | 141 | Text( 142 | text = stringResource(R.string.tap_to_preview), 143 | style = MaterialTheme.typography.bodyMedium, 144 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) 145 | ) 146 | } 147 | 148 | IconButton( 149 | onClick = onClick, 150 | modifier = Modifier 151 | .clip(RoundedCornerShape(12.dp)) 152 | .background(accentColor.copy(alpha = 0.2f)) 153 | ) { 154 | Icon( 155 | imageVector = Icons.Default.PlayArrow, 156 | contentDescription = stringResource(R.string.play_animation), 157 | tint = accentColor 158 | ) 159 | } 160 | } 161 | } 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/util/FadingTextAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.util 2 | 3 | import androidx.compose.animation.Crossfade 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.LaunchedEffect 11 | import androidx.compose.runtime.mutableIntStateOf 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.res.stringResource 16 | import androidx.compose.ui.text.TextStyle 17 | import androidx.compose.ui.unit.sp 18 | import com.sp45.android_animations.R 19 | import kotlinx.coroutines.delay 20 | 21 | @Composable 22 | fun FadingTextAnimation() { 23 | val messages: List = listOf( 24 | stringResource(R.string.resources), 25 | stringResource(R.string.mentorship), 26 | stringResource(R.string.interview_prep) 27 | ) 28 | val fadeDuration = 1000 29 | val pauseDuration = 1500 30 | val currentMessageIndex = remember { mutableIntStateOf(0) } 31 | 32 | LaunchedEffect(Unit) { 33 | while (true) { 34 | delay((fadeDuration + pauseDuration).toLong()) 35 | currentMessageIndex.intValue = (currentMessageIndex.intValue + 1) % messages.size 36 | } 37 | } 38 | 39 | Box( 40 | contentAlignment = Alignment.Center, 41 | modifier = Modifier.background(MaterialTheme.colorScheme.background) 42 | ) { 43 | Crossfade( 44 | targetState = currentMessageIndex.intValue, 45 | animationSpec = tween(durationMillis = fadeDuration) 46 | ) { index -> 47 | Text( 48 | text = messages[index], 49 | style = TextStyle(fontSize = 16.sp), 50 | color = MaterialTheme.colorScheme.onBackground 51 | ) 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/util/ThemeSwitch.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.util 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.material3.Switch 5 | import androidx.compose.material3.SwitchDefaults 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.mutableStateOf 8 | 9 | @Composable 10 | fun ThemeSwitch(darkTheme: Boolean, onThemeChange: () -> Unit) { 11 | Switch( 12 | checked = darkTheme, 13 | onCheckedChange = { onThemeChange() }, 14 | colors = SwitchDefaults.colors( 15 | checkedTrackColor = MaterialTheme.colorScheme.secondaryContainer, 16 | checkedThumbColor = MaterialTheme.colorScheme.primary, 17 | uncheckedTrackColor = MaterialTheme.colorScheme.secondaryContainer, 18 | uncheckedThumbColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) 19 | ), 20 | enabled = true 21 | ) 22 | } 23 | 24 | object ThemeManager { 25 | private var _isDarkTheme = mutableStateOf(false) 26 | private var isInitialized = false 27 | 28 | fun initializeTheme(defaultDarkTheme: Boolean) { 29 | if (!isInitialized) { 30 | _isDarkTheme.value = defaultDarkTheme 31 | isInitialized = true 32 | } 33 | } 34 | 35 | fun toggleTheme() { 36 | _isDarkTheme.value = !_isDarkTheme.value 37 | } 38 | 39 | @Composable 40 | fun observeTheme(): Boolean = _isDarkTheme.value 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/util/TopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.util 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.ExperimentalMaterial3Api 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.material3.TopAppBar 10 | import androidx.compose.material3.TopAppBarDefaults 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.text.font.FontWeight 14 | import androidx.compose.ui.unit.dp 15 | 16 | @OptIn(ExperimentalMaterial3Api::class) 17 | @Composable 18 | fun CustomTopAppBar(title: String) { 19 | TopAppBar( 20 | title = { 21 | Text( 22 | text = title, 23 | style = MaterialTheme.typography.titleLarge, 24 | color = MaterialTheme.colorScheme.onPrimary, 25 | fontWeight = FontWeight.Bold 26 | ) 27 | }, 28 | actions = { 29 | Row(modifier = Modifier.padding(end = 8.dp)) { 30 | ThemeSwitch( 31 | darkTheme = ThemeManager.observeTheme() 32 | ) { 33 | ThemeManager.toggleTheme() 34 | } 35 | } 36 | }, 37 | colors = TopAppBarDefaults.topAppBarColors( 38 | containerColor = MaterialTheme.colorScheme.primary, 39 | actionIconContentColor = MaterialTheme.colorScheme.onPrimary, 40 | ), 41 | modifier = Modifier.fillMaxWidth() 42 | ) 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sp45/android_animations/util/WebViewComponent.kt: -------------------------------------------------------------------------------- 1 | package com.sp45.android_animations.util 2 | 3 | import android.annotation.SuppressLint 4 | import android.webkit.WebView 5 | import android.webkit.WebViewClient 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.res.stringResource 13 | import androidx.compose.ui.text.buildAnnotatedString 14 | import androidx.compose.ui.text.style.TextDecoration 15 | import androidx.compose.ui.text.withStyle 16 | import androidx.compose.ui.viewinterop.AndroidView 17 | import com.sp45.android_animations.R 18 | 19 | @SuppressLint("SetJavaScriptEnabled") 20 | @Composable 21 | fun WebViewScreen() { 22 | val url = stringResource(R.string.https_www_crakcode_in) 23 | AndroidView( 24 | modifier = Modifier.fillMaxSize(), 25 | factory = { context -> 26 | WebView(context).apply { 27 | settings.javaScriptEnabled = true 28 | webViewClient = WebViewClient() 29 | loadUrl(url) 30 | } 31 | } 32 | ) 33 | } 34 | 35 | @Composable 36 | fun WebAppText(onClick: () -> Unit) { 37 | val annotatedString = buildAnnotatedString { 38 | pushStringAnnotation(tag = "URL", annotation = "https://www.crakcode.in/") 39 | withStyle( 40 | style = MaterialTheme.typography.bodyLarge.copy( 41 | color = MaterialTheme.colorScheme.primary, 42 | textDecoration = TextDecoration.Underline 43 | ).toSpanStyle() 44 | ) { 45 | append(stringResource(R.string.visit_crakcode_website_for)) 46 | } 47 | pop() 48 | } 49 | 50 | Text( 51 | text = annotatedString, 52 | style = MaterialTheme.typography.bodyMedium, 53 | modifier = Modifier.clickable { onClick() }, 54 | color = MaterialTheme.colorScheme.onBackground 55 | ) 56 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/avd_crakcode.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 17 | 22 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | 54 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cat1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/drawable/cat1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/cat2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/drawable/cat2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/cat3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/drawable/cat3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/cat4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/drawable/cat4.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/cat5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/drawable/cat5.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/crakcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/drawable/crakcode.png -------------------------------------------------------------------------------- /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/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/drawable/img.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/drawable/img_2.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crakcode-hub/android-animations/aef97409b7e549057d022e376be676d0d5ff03c6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com_google_android_gms_fonts_certs 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android Animations 3 | Front 4 | Back 5 | Pop! 6 | Change Profile Picture 7 | Swipe Up to Collapse 8 | Swipe Down to Expand 9 | Undo 10 | Delete 11 | Swipe to Delete 12 | Slide left to remove this item 13 | Swipe left 14 | Type Writer Animation! 15 | Dynamic Circle of size : %1$d.dp 16 | Dynamic Circle of roundness : %1$d%% 17 | Color Animation 18 | Loading… 19 | Open Web App 20 | Visit CrakCode Website for 21 | https://www.crakcode.in/ 22 | Resources 23 | Mentorship 24 | Interview Prep 25 | Play Animation 26 | Tap to preview 27 | Text Explosion 28 | Orbiting Objects 29 | Expanding Rings 30 | Image Transition 31 | Confetti Animation 32 | Rotate the Card 33 | Button to Image 34 | Swipe right or left to open/close the door 35 | Hidden Content 36 | CrakCode 37 | Tap to Flip 38 | Tap to see the front 39 | Tap to Collapse 40 | Tap to Expand 41 | CrakCode Image 42 | Click Me! 43 | Back of the card 44 | Front of the card 45 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |