├── .editorconfig ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ └── Feature_request.md ├── pull_request_template.md └── workflows │ ├── android.yml │ └── publish.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── api │ └── app.api ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── skydoves │ │ └── orbitaldemo │ │ ├── AnimationSpecs.kt │ │ ├── ContainerTransformDemo.kt │ │ ├── ItemUtils.kt │ │ ├── MainActivity.kt │ │ ├── MockUtils.kt │ │ ├── OrbitalLazyColumnSample.kt │ │ ├── Poster.kt │ │ ├── PosterDetails.kt │ │ ├── ScreenTransitionSample.kt │ │ └── ui │ │ ├── Colors.kt │ │ ├── Theme.kt │ │ └── Type.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── ic_launcher_background.xml │ └── poster.png │ ├── mipmap-anydpi-v26 │ ├── 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 │ ├── colors.xml │ ├── strings.xml │ └── themes.xml ├── benchmark-app ├── .gitignore ├── api │ └── benchmark-app.api ├── build.gradle.kts └── src │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── com │ │ │ └── skydoves │ │ │ └── orbital │ │ │ └── benchmark │ │ │ └── app │ │ │ ├── MainActivity.kt │ │ │ └── profiles │ │ │ ├── AnimationSpecs.kt │ │ │ ├── OrbitalMovementProfiles.kt │ │ │ ├── OrbitalSharedElementTransitionProfiles.kt │ │ │ └── OrbitalTransformationProfiles.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── 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 │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── release │ └── generated │ └── baselineProfiles │ └── baseline-prof.txt ├── benchmark ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── skydoves │ └── orbital │ └── benchmark │ └── BaselineProfileGenerator.kt ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── skydoves │ └── orbital │ └── Configuration.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── orbital ├── .gitignore ├── api │ ├── android │ │ └── orbital.api │ ├── desktop │ │ └── orbital.api │ ├── jvm │ │ └── orbital.api │ └── orbital.api ├── build.gradle.kts └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── baseline-prof.txt │ └── commonMain │ └── kotlin │ └── com │ └── skydoves │ └── orbital │ ├── AnimateBounds.kt │ ├── AnimateMovement.kt │ ├── AnimateSharedElementTransition.kt │ ├── AnimateTransformation.kt │ ├── DeferredAnimation.kt │ ├── Orbital.kt │ ├── OrbitalScope.kt │ ├── RememberMovables.kt │ └── RememberableContent.kt ├── previews ├── preview0.gif ├── preview1.gif ├── preview2.gif ├── preview3.gif ├── preview4.gif └── preview5.gif ├── scripts └── publish-module.gradle.kts ├── settings.gradle.kts └── spotless ├── copyright.kt ├── copyright.kts └── copyright.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | # Most of the standard properties are supported 4 | indent_size=2 5 | max_line_length=100 -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://help.github.com/articles/about-codeowners/ 5 | 6 | # The '*' pattern is global owners. 7 | # Not adding in this PR, but I'd like to try adding a global owner set with the entire team. 8 | # One interpretation of their docs is that global owners are added only if not removed 9 | # by a more local rule. 10 | 11 | # Order is important. The last matching pattern has the most precedence. 12 | # The folders are ordered as follows: 13 | 14 | # In each subsection folders are ordered first by depth, then alphabetically. 15 | # This should make it easy to add new rules without breaking existing ones. 16 | * @skydoves -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: skydoves 2 | custom: ["https://www.paypal.me/skydoves", "https://www.buymeacoffee.com/skydoves"] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is crashing or not working as intended 4 | 5 | --- 6 | 7 | **Please complete the following information:** 8 | - Library Version [e.g. v1.0.0] 9 | - Affected Device(s) [e.g. Samsung Galaxy s10 with Android 9.0] 10 | 11 | **Describe the Bug:** 12 | 13 | Add a clear description about the problem. 14 | 15 | **Expected Behavior:** 16 | 17 | A clear description of what you expected to happen. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem?** 8 | 9 | A clear and concise description of what the problem is. 10 | 11 | **Describe the solution you'd like:** 12 | 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered:** 16 | 17 | A clear description of any alternative solutions you've considered. 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Guidelines 2 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 3 | 4 | ### Types of changes 5 | What types of changes does your code introduce? 6 | 7 | - [ ] Bugfix (non-breaking change which fixes an issue) 8 | - [ ] New feature (non-breaking change which adds functionality) 9 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 10 | 11 | ### Preparing a pull request for review 12 | Ensure your change is properly formatted by running: 13 | 14 | ```gradle 15 | $ ./gradlew spotlessApply 16 | ``` 17 | 18 | Please correct any failures before requesting a review. -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | lint: 11 | name: Spotless check 12 | runs-on: macos-latest 13 | steps: 14 | - name: Check out code 15 | uses: actions/checkout@v2 16 | - name: Set up JDK 17 | uses: actions/setup-java@v1 18 | with: 19 | distribution: zulu 20 | java-version: 17 21 | - name: spotless 22 | run: ./gradlew spotlessCheck 23 | 24 | api_check: 25 | name: API check 26 | runs-on: macos-latest 27 | steps: 28 | - name: Check out code 29 | uses: actions/checkout@v2 30 | - name: Set up JDK 31 | uses: actions/setup-java@v1 32 | with: 33 | distribution: zulu 34 | java-version: 17 35 | - name: API check 36 | run: ./gradlew apiCheck 37 | 38 | build: 39 | runs-on: macos-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | 43 | - name: set up JDK 44 | uses: actions/setup-java@v1 45 | with: 46 | distribution: zulu 47 | java-version: 17 48 | 49 | - name: Cache Gradle and wrapper 50 | uses: actions/cache@v2 51 | with: 52 | path: | 53 | ~/.gradle/caches 54 | ~/.gradle/wrapper 55 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 56 | restore-keys: | 57 | ${{ runner.os }}-gradle- 58 | 59 | - name: Make Gradle executable 60 | run: chmod +x ./gradlew 61 | 62 | - name: Build with Gradle 63 | run: | 64 | ./gradlew --scan --stacktrace \ 65 | assemble -x :benchmark:pixel6api31Setup -x :benchmark:pixel6api31NonMinifiedReleaseAndroidTest -x :benchmark:collectNonMinifiedReleaseBaselineProfile -x :benchmark:compileBenchmarkReleaseKotlin -x :benchmark:processBenchmarkReleaseJavaRes -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [ released ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | name: Snapshot build and publish 11 | runs-on: macos-latest 12 | steps: 13 | - name: Check out code 14 | uses: actions/checkout@v3.1.0 15 | 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v3.5.1 18 | with: 19 | distribution: 'zulu' 20 | java-version: 17 21 | 22 | - name: Grant Permission to Execute Gradle 23 | run: chmod +x gradlew 24 | 25 | - name: Release build 26 | run: ./gradlew assemble --scan -x :benchmark:pixel6api31Setup -x :benchmark:pixel6api31NonMinifiedReleaseAndroidTest -x :benchmark:collectNonMinifiedReleaseBaselineProfile 27 | 28 | - name: Publish to MavenCentral 29 | run: | 30 | ./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache 31 | env: 32 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }} 33 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_PASSWORD }} 34 | ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }} 35 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} 36 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Kotlin class files 12 | /.kotlin 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | out/ 18 | 19 | # Gradle files 20 | /.idea 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # Intellij 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/dictionaries 45 | .idea/libraries 46 | app/.idea/ 47 | 48 | # Mac 49 | *.DS_Store 50 | 51 | # Keystore files 52 | *.jks 53 | 54 | # External native build folder generated in Android Studio 2.2 and later 55 | .externalNativeBuild 56 | 57 | # Google Services (e.g. APIs or Firebase) 58 | google-services.json 59 | 60 | # Freeline 61 | freeline.py 62 | freeline/ 63 | freeline_project_description.json 64 | -------------------------------------------------------------------------------- /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 | skydoves (Jaewoong Eum). 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 | ## How to contribute 2 | We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. 3 | 4 | ## Preparing a pull request for review 5 | Ensure your change is properly formatted by running: 6 | 7 | ```gradle 8 | ./gradlew spotlessApply 9 | ``` 10 | 11 | Then dump binary API of this library that is public in sense of Kotlin visibilities and ensures that the public binary API wasn't changed in a way that make this change binary incompatible. 12 | 13 | ```gradle 14 | ./gradlew apiDump 15 | ``` 16 | 17 | Please correct any failures before requesting a review. 18 | 19 | ## Code reviews 20 | All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) for more information on using pull requests. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/api/app.api: -------------------------------------------------------------------------------- 1 | public final class com/skydoves/orbitaldemo/AnimationSpecsKt { 2 | public static final fun getMovementSpec ()Landroidx/compose/animation/core/SpringSpec; 3 | public static final fun getTransformationSpec ()Landroidx/compose/animation/core/SpringSpec; 4 | } 5 | 6 | public final class com/skydoves/orbitaldemo/BuildConfig { 7 | public static final field APPLICATION_ID Ljava/lang/String; 8 | public static final field BUILD_TYPE Ljava/lang/String; 9 | public static final field DEBUG Z 10 | public static final field VERSION_CODE I 11 | public static final field VERSION_NAME Ljava/lang/String; 12 | public fun ()V 13 | } 14 | 15 | public final class com/skydoves/orbitaldemo/ComposableSingletons$ContainerTransformDemoKt { 16 | public static final field INSTANCE Lcom/skydoves/orbitaldemo/ComposableSingletons$ContainerTransformDemoKt; 17 | public static field lambda-1 Lkotlin/jvm/functions/Function2; 18 | public fun ()V 19 | public final fun getLambda-1$app_release ()Lkotlin/jvm/functions/Function2; 20 | } 21 | 22 | public final class com/skydoves/orbitaldemo/ComposableSingletons$MainActivityKt { 23 | public static final field INSTANCE Lcom/skydoves/orbitaldemo/ComposableSingletons$MainActivityKt; 24 | public static field lambda-1 Lkotlin/jvm/functions/Function2; 25 | public static field lambda-2 Lkotlin/jvm/functions/Function2; 26 | public static field lambda-3 Lkotlin/jvm/functions/Function3; 27 | public fun ()V 28 | public final fun getLambda-1$app_release ()Lkotlin/jvm/functions/Function2; 29 | public final fun getLambda-2$app_release ()Lkotlin/jvm/functions/Function2; 30 | public final fun getLambda-3$app_release ()Lkotlin/jvm/functions/Function3; 31 | } 32 | 33 | public final class com/skydoves/orbitaldemo/ComposableSingletons$OrbitalLazyColumnSampleKt { 34 | public static final field INSTANCE Lcom/skydoves/orbitaldemo/ComposableSingletons$OrbitalLazyColumnSampleKt; 35 | public static field lambda-1 Lkotlin/jvm/functions/Function3; 36 | public fun ()V 37 | public final fun getLambda-1$app_release ()Lkotlin/jvm/functions/Function3; 38 | } 39 | 40 | public final class com/skydoves/orbitaldemo/ComposableSingletons$ScreenTransitionSampleKt { 41 | public static final field INSTANCE Lcom/skydoves/orbitaldemo/ComposableSingletons$ScreenTransitionSampleKt; 42 | public static field lambda-1 Lkotlin/jvm/functions/Function3; 43 | public static field lambda-2 Lkotlin/jvm/functions/Function3; 44 | public fun ()V 45 | public final fun getLambda-1$app_release ()Lkotlin/jvm/functions/Function3; 46 | public final fun getLambda-2$app_release ()Lkotlin/jvm/functions/Function3; 47 | } 48 | 49 | public final class com/skydoves/orbitaldemo/ContainerTransformDemoKt { 50 | public static final fun ContainerTransformDemo (Lcom/skydoves/orbitaldemo/MyModel;Landroidx/compose/runtime/Composer;II)V 51 | public static final fun DetailView (Landroidx/compose/animation/AnimatedVisibilityScope;Landroidx/compose/animation/SharedTransitionScope;Lcom/skydoves/orbitaldemo/MyModel;Lcom/skydoves/orbitaldemo/Kitty;Lcom/skydoves/orbitaldemo/Kitty;Landroidx/compose/runtime/Composer;I)V 52 | public static final fun Details (Landroidx/compose/animation/SharedTransitionScope;Lcom/skydoves/orbitaldemo/Kitty;Landroidx/compose/runtime/Composer;I)V 53 | public static final fun GridView (Landroidx/compose/animation/AnimatedVisibilityScope;Landroidx/compose/animation/SharedTransitionScope;Lcom/skydoves/orbitaldemo/MyModel;Landroidx/compose/runtime/Composer;I)V 54 | public static final fun KittyItem (Landroidx/compose/animation/AnimatedVisibilityScope;Landroidx/compose/animation/SharedTransitionScope;Lcom/skydoves/orbitaldemo/Kitty;Landroidx/compose/runtime/Composer;I)V 55 | public static final fun SearchBar (Landroidx/compose/runtime/Composer;I)V 56 | } 57 | 58 | public final class com/skydoves/orbitaldemo/ItemUtils { 59 | public static final field $stable I 60 | public static final field INSTANCE Lcom/skydoves/orbitaldemo/ItemUtils; 61 | public final fun getUrls ()Ljava/util/List; 62 | } 63 | 64 | public final class com/skydoves/orbitaldemo/Kitty { 65 | public static final field $stable I 66 | public fun (Ljava/lang/String;ILjava/lang/String;I)V 67 | public final fun component1 ()Ljava/lang/String; 68 | public final fun component2 ()I 69 | public final fun component3 ()Ljava/lang/String; 70 | public final fun component4 ()I 71 | public final fun copy (Ljava/lang/String;ILjava/lang/String;I)Lcom/skydoves/orbitaldemo/Kitty; 72 | public static synthetic fun copy$default (Lcom/skydoves/orbitaldemo/Kitty;Ljava/lang/String;ILjava/lang/String;IILjava/lang/Object;)Lcom/skydoves/orbitaldemo/Kitty; 73 | public fun equals (Ljava/lang/Object;)Z 74 | public final fun getBreed ()Ljava/lang/String; 75 | public final fun getId ()I 76 | public final fun getName ()Ljava/lang/String; 77 | public final fun getPhotoResId ()I 78 | public fun hashCode ()I 79 | public fun toString ()Ljava/lang/String; 80 | } 81 | 82 | public final class com/skydoves/orbitaldemo/MainActivity : androidx/activity/ComponentActivity { 83 | public static final field $stable I 84 | public fun ()V 85 | public final fun NavigationComposeShared (Landroidx/compose/runtime/Composer;I)V 86 | } 87 | 88 | public final class com/skydoves/orbitaldemo/MainActivity$Animal { 89 | public static final field $stable I 90 | public fun (Ljava/lang/String;Ljava/lang/String;I)V 91 | public final fun component1 ()Ljava/lang/String; 92 | public final fun component2 ()Ljava/lang/String; 93 | public final fun component3 ()I 94 | public final fun copy (Ljava/lang/String;Ljava/lang/String;I)Lcom/skydoves/orbitaldemo/MainActivity$Animal; 95 | public static synthetic fun copy$default (Lcom/skydoves/orbitaldemo/MainActivity$Animal;Ljava/lang/String;Ljava/lang/String;IILjava/lang/Object;)Lcom/skydoves/orbitaldemo/MainActivity$Animal; 96 | public fun equals (Ljava/lang/Object;)Z 97 | public final fun getDescription ()Ljava/lang/String; 98 | public final fun getImage ()I 99 | public final fun getName ()Ljava/lang/String; 100 | public fun hashCode ()I 101 | public fun toString ()Ljava/lang/String; 102 | } 103 | 104 | public final class com/skydoves/orbitaldemo/MockUtils { 105 | public static final field $stable I 106 | public static final field INSTANCE Lcom/skydoves/orbitaldemo/MockUtils; 107 | public final fun getMockPoster ()Lcom/skydoves/orbitaldemo/Poster; 108 | public final fun getMockPosters ()Ljava/util/List; 109 | } 110 | 111 | public final class com/skydoves/orbitaldemo/MyModel { 112 | public static final field $stable I 113 | public fun ()V 114 | public final fun getItems ()Ljava/util/List; 115 | public final fun getSelected ()Lcom/skydoves/orbitaldemo/Kitty; 116 | public final fun setSelected (Lcom/skydoves/orbitaldemo/Kitty;)V 117 | } 118 | 119 | public final class com/skydoves/orbitaldemo/Poster { 120 | public static final field $stable I 121 | public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLkotlin/jvm/internal/DefaultConstructorMarker;)V 122 | public final fun component1 ()Ljava/lang/String; 123 | public final fun component2 ()Ljava/lang/String; 124 | public final fun component3 ()Ljava/lang/String; 125 | public final fun component4 ()Ljava/lang/String; 126 | public final fun component5 ()Ljava/lang/String; 127 | public final fun component6 ()Ljava/lang/String; 128 | public final fun component7-0d7_KjU ()J 129 | public final fun copy-PE3pjmc (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)Lcom/skydoves/orbitaldemo/Poster; 130 | public static synthetic fun copy-PE3pjmc$default (Lcom/skydoves/orbitaldemo/Poster;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JILjava/lang/Object;)Lcom/skydoves/orbitaldemo/Poster; 131 | public fun equals (Ljava/lang/Object;)Z 132 | public final fun getColor-0d7_KjU ()J 133 | public final fun getDescription ()Ljava/lang/String; 134 | public final fun getGif ()Ljava/lang/String; 135 | public final fun getName ()Ljava/lang/String; 136 | public final fun getPlaytime ()Ljava/lang/String; 137 | public final fun getPoster ()Ljava/lang/String; 138 | public final fun getRelease ()Ljava/lang/String; 139 | public fun hashCode ()I 140 | public fun toString ()Ljava/lang/String; 141 | } 142 | 143 | public final class com/skydoves/orbitaldemo/PosterDetailsKt { 144 | public static final fun PosterDetails (Lcom/skydoves/orbitaldemo/Poster;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V 145 | } 146 | 147 | public final class com/skydoves/orbitaldemo/Screen : java/lang/Enum { 148 | public static final field A Lcom/skydoves/orbitaldemo/Screen; 149 | public static final field B Lcom/skydoves/orbitaldemo/Screen; 150 | public static fun getEntries ()Lkotlin/enums/EnumEntries; 151 | public static fun valueOf (Ljava/lang/String;)Lcom/skydoves/orbitaldemo/Screen; 152 | public static fun values ()[Lcom/skydoves/orbitaldemo/Screen; 153 | } 154 | 155 | public final class com/skydoves/orbitaldemo/ScreenTransitionSampleKt { 156 | public static final fun ScreenTransitionSample (Landroidx/compose/runtime/Composer;I)V 157 | } 158 | 159 | public final class com/skydoves/orbitaldemo/ui/ColorsKt { 160 | public static final fun getBackground ()J 161 | public static final fun getBackground800 ()J 162 | public static final fun getBackground900 ()J 163 | public static final fun getPurple200 ()J 164 | public static final fun getPurple500 ()J 165 | public static final fun getShimmerHighLight ()J 166 | public static final fun getWhite87 ()J 167 | } 168 | 169 | public final class com/skydoves/orbitaldemo/ui/ThemeKt { 170 | public static final fun OrbitalTheme (ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V 171 | } 172 | 173 | public final class com/skydoves/orbitaldemo/ui/TypeKt { 174 | public static final fun getDarkTypography ()Landroidx/compose/material/Typography; 175 | public static final fun getLightTypography ()Landroidx/compose/material/Typography; 176 | } 177 | 178 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Designed and developed by 2023 skydoves (Jaewoong Eum) 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import com.skydoves.orbital.Configuration 16 | 17 | @Suppress("DSL_SCOPE_VIOLATION") 18 | plugins { 19 | id(libs.plugins.android.application.get().pluginId) 20 | id(libs.plugins.kotlin.android.get().pluginId) 21 | id(libs.plugins.compose.compiler.get().pluginId) 22 | } 23 | 24 | android { 25 | compileSdk = Configuration.compileSdk 26 | namespace = "com.skydoves.orbitaldemo" 27 | defaultConfig { 28 | applicationId = "com.skydoves.orbitaldemo" 29 | minSdk = Configuration.minSdk 30 | targetSdk = Configuration.targetSdk 31 | versionCode = Configuration.versionCode 32 | versionName = Configuration.versionName 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility = JavaVersion.VERSION_11 37 | targetCompatibility = JavaVersion.VERSION_11 38 | } 39 | 40 | kotlinOptions { 41 | jvmTarget = libs.versions.jvmTarget.get() 42 | } 43 | 44 | buildFeatures { 45 | compose = true 46 | buildConfig = true 47 | } 48 | 49 | packaging { 50 | resources { 51 | excludes.add("/META-INF/{AL2.0,LGPL2.1}") 52 | } 53 | } 54 | 55 | lint { 56 | abortOnError = false 57 | } 58 | } 59 | 60 | dependencies { 61 | implementation(project(":orbital")) 62 | 63 | implementation(platform(libs.androidx.compose.bom)) 64 | implementation(libs.androidx.activity.compose) 65 | implementation("androidx.compose.animation:animation:1.7.0-beta03") 66 | implementation("androidx.compose.ui:ui:1.7.0-beta03") 67 | implementation(libs.androidx.compose.ui.tooling) 68 | implementation(libs.androidx.compose.foundation) 69 | implementation(libs.androidx.compose.runtime) 70 | implementation(libs.androidx.compose.material) 71 | implementation(libs.androidx.compose.constraintlayout) 72 | implementation(libs.androidx.compose.material.iconsExtended) 73 | implementation(libs.androidx.compose.navigation) 74 | 75 | implementation(libs.landscapist.glide) 76 | implementation(libs.landscapist.placeholder) 77 | implementation(libs.landscapist.animation) 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 22 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/AnimationSpecs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo 17 | 18 | import androidx.compose.animation.core.Spring 19 | import androidx.compose.animation.core.SpringSpec 20 | import androidx.compose.ui.unit.IntOffset 21 | import androidx.compose.ui.unit.IntSize 22 | 23 | val transformationSpec = SpringSpec( 24 | dampingRatio = Spring.DampingRatioMediumBouncy, 25 | stiffness = 200f, 26 | ) 27 | 28 | val movementSpec = SpringSpec( 29 | dampingRatio = Spring.DampingRatioMediumBouncy, 30 | stiffness = 200f, 31 | ) 32 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/ItemUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo 17 | 18 | object ItemUtils { 19 | val urls = listOf( 20 | "https://user-images.githubusercontent.com/" + 21 | "24237865/75087936-5c1d9f80-553e-11ea-81d3-a912634dd8f7.jpg", 22 | "https://user-images.githubusercontent.com/" + 23 | "24237865/75087934-5a53dc00-553e-11ea-94f1-494c1c68a574.jpg", 24 | "https://user-images.githubusercontent.com/" + 25 | "24237865/75087937-5c1d9f80-553e-11ea-8fc9-a7e520addde0.jpg", 26 | "https://user-images.githubusercontent.com/" + 27 | "24237865/75088201-0ba84100-5542-11ea-8587-0c2823b05351.jpg", 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:OptIn(ExperimentalSharedTransitionApi::class) 17 | 18 | package com.skydoves.orbitaldemo 19 | 20 | import android.os.Bundle 21 | import androidx.activity.ComponentActivity 22 | import androidx.activity.compose.setContent 23 | import androidx.annotation.DrawableRes 24 | import androidx.compose.animation.ExperimentalSharedTransitionApi 25 | import androidx.compose.animation.SharedTransitionLayout 26 | import androidx.compose.animation.core.SpringSpec 27 | import androidx.compose.animation.core.tween 28 | import androidx.compose.foundation.Image 29 | import androidx.compose.foundation.background 30 | import androidx.compose.foundation.clickable 31 | import androidx.compose.foundation.layout.Arrangement 32 | import androidx.compose.foundation.layout.Column 33 | import androidx.compose.foundation.layout.Row 34 | import androidx.compose.foundation.layout.Spacer 35 | import androidx.compose.foundation.layout.aspectRatio 36 | import androidx.compose.foundation.layout.fillMaxSize 37 | import androidx.compose.foundation.layout.fillMaxWidth 38 | import androidx.compose.foundation.layout.padding 39 | import androidx.compose.foundation.layout.size 40 | import androidx.compose.foundation.layout.width 41 | import androidx.compose.foundation.lazy.LazyColumn 42 | import androidx.compose.foundation.lazy.itemsIndexed 43 | import androidx.compose.material.Text 44 | import androidx.compose.runtime.Composable 45 | import androidx.compose.runtime.getValue 46 | import androidx.compose.runtime.mutableStateOf 47 | import androidx.compose.runtime.remember 48 | import androidx.compose.runtime.saveable.rememberSaveable 49 | import androidx.compose.runtime.setValue 50 | import androidx.compose.ui.Alignment 51 | import androidx.compose.ui.Modifier 52 | import androidx.compose.ui.geometry.Rect 53 | import androidx.compose.ui.graphics.Color 54 | import androidx.compose.ui.layout.ContentScale 55 | import androidx.compose.ui.res.painterResource 56 | import androidx.compose.ui.unit.dp 57 | import androidx.compose.ui.unit.sp 58 | import androidx.navigation.NavType 59 | import androidx.navigation.compose.NavHost 60 | import androidx.navigation.compose.composable 61 | import androidx.navigation.compose.rememberNavController 62 | import androidx.navigation.navArgument 63 | import com.skydoves.landscapist.ImageOptions 64 | import com.skydoves.landscapist.glide.GlideImage 65 | import com.skydoves.orbital.Orbital 66 | import com.skydoves.orbital.animateMovement 67 | import com.skydoves.orbital.animateSharedElementTransition 68 | import com.skydoves.orbital.animateTransformation 69 | import com.skydoves.orbital.rememberContentWithOrbitalScope 70 | import com.skydoves.orbitaldemo.ui.OrbitalTheme 71 | 72 | class MainActivity : ComponentActivity() { 73 | 74 | override fun onCreate(savedInstanceState: Bundle?) { 75 | super.onCreate(savedInstanceState) 76 | 77 | setContent { 78 | OrbitalTheme { 79 | ContainerTransformDemo() 80 | } 81 | } 82 | } 83 | 84 | @Composable 85 | fun NavigationComposeShared() { 86 | SharedTransitionLayout { 87 | val listAnimals = remember { 88 | listOf( 89 | Animal("Lion", "", R.drawable.poster), 90 | Animal("Lizard", "", R.drawable.poster), 91 | Animal("Elephant", "", R.drawable.poster), 92 | Animal("Penguin", "", R.drawable.poster), 93 | ) 94 | } 95 | val boundsTransform = { _: Rect, _: Rect -> tween(1400) } 96 | 97 | val navController = rememberNavController() 98 | NavHost(navController = navController, startDestination = "home") { 99 | composable("home") { 100 | LazyColumn( 101 | modifier = Modifier 102 | .background(Color.Black) 103 | .fillMaxSize() 104 | .padding(8.dp), 105 | verticalArrangement = Arrangement.spacedBy(8.dp), 106 | ) { 107 | itemsIndexed(listAnimals) { index, item -> 108 | Row( 109 | Modifier.clickable { 110 | navController.navigate("details/$index") 111 | }, 112 | ) { 113 | Spacer(modifier = Modifier.width(8.dp)) 114 | Image( 115 | painterResource(id = item.image), 116 | contentDescription = item.description, 117 | contentScale = ContentScale.Crop, 118 | modifier = Modifier 119 | .size(100.dp) 120 | .sharedElement( 121 | rememberSharedContentState(key = "image-$index"), 122 | animatedVisibilityScope = this@composable, 123 | boundsTransform = boundsTransform, 124 | ), 125 | ) 126 | Spacer(modifier = Modifier.width(8.dp)) 127 | Text( 128 | item.name, 129 | fontSize = 18.sp, 130 | modifier = Modifier 131 | .align(Alignment.CenterVertically) 132 | .sharedElement( 133 | rememberSharedContentState(key = "text-$index"), 134 | animatedVisibilityScope = this@composable, 135 | boundsTransform = boundsTransform, 136 | ), 137 | ) 138 | } 139 | } 140 | } 141 | } 142 | composable( 143 | "details/{animal}", 144 | arguments = listOf(navArgument("animal") { type = NavType.IntType }), 145 | ) { backStackEntry -> 146 | val animalId = backStackEntry.arguments?.getInt("animal") 147 | val animal = listAnimals[animalId!!] 148 | Column( 149 | Modifier 150 | .fillMaxSize() 151 | .background(Color.Black) 152 | .clickable { 153 | navController.navigate("home") 154 | }, 155 | ) { 156 | Image( 157 | painterResource(id = animal.image), 158 | contentDescription = animal.description, 159 | contentScale = ContentScale.Crop, 160 | modifier = Modifier 161 | .aspectRatio(1f) 162 | .fillMaxWidth() 163 | .sharedElement( 164 | rememberSharedContentState(key = "image-$animalId"), 165 | animatedVisibilityScope = this@composable, 166 | boundsTransform = boundsTransform, 167 | ), 168 | ) 169 | Text( 170 | animal.name, 171 | fontSize = 18.sp, 172 | modifier = 173 | Modifier 174 | .fillMaxWidth() 175 | .sharedElement( 176 | rememberSharedContentState(key = "text-$animalId"), 177 | animatedVisibilityScope = this@composable, 178 | boundsTransform = boundsTransform, 179 | ), 180 | ) 181 | } 182 | } 183 | } 184 | } 185 | } 186 | 187 | data class Animal( 188 | val name: String, 189 | val description: String, 190 | @DrawableRes val image: Int, 191 | ) 192 | 193 | @Composable 194 | private fun OrbitalTransformationExample() { 195 | var isTransformed by rememberSaveable { mutableStateOf(false) } 196 | val poster = rememberContentWithOrbitalScope { 197 | GlideImage( 198 | modifier = if (isTransformed) { 199 | Modifier.size(300.dp, 620.dp) 200 | } else { 201 | Modifier.size(100.dp, 220.dp) 202 | }.animateTransformation(this, transformationSpec), 203 | imageModel = { MockUtils.getMockPoster().poster }, 204 | imageOptions = ImageOptions(contentScale = ContentScale.Fit), 205 | ) 206 | } 207 | 208 | Orbital( 209 | modifier = Modifier 210 | .clickable { isTransformed = !isTransformed }, 211 | ) { 212 | Column( 213 | Modifier.fillMaxSize(), 214 | horizontalAlignment = Alignment.CenterHorizontally, 215 | verticalArrangement = Arrangement.Center, 216 | ) { 217 | poster() 218 | } 219 | } 220 | } 221 | 222 | @Composable 223 | private fun OrbitalMovementExample() { 224 | var isTransformed by rememberSaveable { mutableStateOf(false) } 225 | val poster = rememberContentWithOrbitalScope { 226 | GlideImage( 227 | modifier = if (isTransformed) { 228 | Modifier.size(130.dp, 220.dp) 229 | } else { 230 | Modifier.size(130.dp, 220.dp) 231 | }.animateMovement(this, movementSpec), 232 | imageModel = { ItemUtils.urls[3] }, 233 | imageOptions = ImageOptions(contentScale = ContentScale.Fit), 234 | ) 235 | } 236 | 237 | Orbital( 238 | modifier = Modifier 239 | .clickable { isTransformed = !isTransformed }, 240 | ) { 241 | if (isTransformed) { 242 | Column( 243 | Modifier.fillMaxSize(), 244 | horizontalAlignment = Alignment.CenterHorizontally, 245 | verticalArrangement = Arrangement.Center, 246 | ) { 247 | poster() 248 | } 249 | } else { 250 | Column( 251 | Modifier 252 | .fillMaxSize() 253 | .padding(20.dp), 254 | horizontalAlignment = Alignment.End, 255 | verticalArrangement = Arrangement.Bottom, 256 | ) { 257 | poster() 258 | } 259 | } 260 | } 261 | } 262 | 263 | @Composable 264 | private fun OrbitalSharedElementTransitionExample() { 265 | var isTransformed by rememberSaveable { mutableStateOf(false) } 266 | val item = MockUtils.getMockPosters()[3] 267 | val poster = rememberContentWithOrbitalScope { 268 | GlideImage( 269 | modifier = if (isTransformed) { 270 | Modifier.fillMaxSize() 271 | } else { 272 | Modifier.size(130.dp, 220.dp) 273 | }.animateSharedElementTransition( 274 | this, 275 | SpringSpec(stiffness = 500f), 276 | SpringSpec(stiffness = 500f), 277 | ), 278 | imageModel = { item.poster }, 279 | imageOptions = ImageOptions(contentScale = ContentScale.Fit), 280 | ) 281 | } 282 | 283 | Orbital( 284 | modifier = Modifier 285 | .clickable { isTransformed = !isTransformed }, 286 | ) { 287 | if (isTransformed) { 288 | PosterDetails( 289 | poster = item, 290 | sharedElementContent = { poster() }, 291 | pressOnBack = {}, 292 | ) 293 | } else { 294 | Column( 295 | Modifier 296 | .fillMaxSize() 297 | .padding(20.dp), 298 | horizontalAlignment = Alignment.End, 299 | verticalArrangement = Arrangement.Bottom, 300 | ) { 301 | poster() 302 | } 303 | } 304 | } 305 | } 306 | 307 | @Composable 308 | private fun OrbitalMultipleSharedElementTransitionExample() { 309 | var isTransformed by rememberSaveable { mutableStateOf(false) } 310 | val items = rememberContentWithOrbitalScope { 311 | MockUtils.getMockPosters().take(4).forEach { item -> 312 | GlideImage( 313 | modifier = if (isTransformed) { 314 | Modifier.size(140.dp, 180.dp) 315 | } else { 316 | Modifier.size(100.dp, 220.dp) 317 | } 318 | .animateSharedElementTransition(this, movementSpec, transformationSpec) 319 | .padding(8.dp), 320 | imageModel = { item.poster }, 321 | imageOptions = ImageOptions(contentScale = ContentScale.Fit), 322 | ) 323 | } 324 | } 325 | 326 | Orbital( 327 | modifier = Modifier 328 | .fillMaxSize() 329 | .clickable { isTransformed = !isTransformed }, 330 | isTransformed = isTransformed, 331 | onStartContent = { 332 | Column( 333 | Modifier.fillMaxSize(), 334 | horizontalAlignment = Alignment.CenterHorizontally, 335 | verticalArrangement = Arrangement.Center, 336 | ) { 337 | items() 338 | } 339 | }, 340 | onTransformedContent = { 341 | Row( 342 | verticalAlignment = Alignment.CenterVertically, 343 | ) { items() } 344 | }, 345 | ) 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/OrbitalLazyColumnSample.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo 17 | 18 | import androidx.compose.animation.AnimatedVisibility 19 | import androidx.compose.animation.core.MutableTransitionState 20 | import androidx.compose.animation.core.Spring 21 | import androidx.compose.animation.core.spring 22 | import androidx.compose.animation.fadeIn 23 | import androidx.compose.foundation.background 24 | import androidx.compose.foundation.clickable 25 | import androidx.compose.foundation.layout.Column 26 | import androidx.compose.foundation.layout.Row 27 | import androidx.compose.foundation.layout.fillMaxWidth 28 | import androidx.compose.foundation.layout.padding 29 | import androidx.compose.foundation.layout.size 30 | import androidx.compose.foundation.lazy.LazyColumn 31 | import androidx.compose.foundation.lazy.items 32 | import androidx.compose.foundation.shape.RoundedCornerShape 33 | import androidx.compose.material.Text 34 | import androidx.compose.runtime.Composable 35 | import androidx.compose.runtime.getValue 36 | import androidx.compose.runtime.mutableStateOf 37 | import androidx.compose.runtime.remember 38 | import androidx.compose.runtime.saveable.rememberSaveable 39 | import androidx.compose.runtime.setValue 40 | import androidx.compose.ui.Modifier 41 | import androidx.compose.ui.draw.clip 42 | import androidx.compose.ui.graphics.Color 43 | import androidx.compose.ui.text.font.FontWeight 44 | import androidx.compose.ui.text.style.TextOverflow 45 | import androidx.compose.ui.unit.IntSize 46 | import androidx.compose.ui.unit.dp 47 | import androidx.compose.ui.unit.sp 48 | import com.skydoves.landscapist.ImageOptions 49 | import com.skydoves.landscapist.animation.crossfade.CrossfadePlugin 50 | import com.skydoves.landscapist.components.rememberImageComponent 51 | import com.skydoves.landscapist.glide.GlideImage 52 | import com.skydoves.orbital.Orbital 53 | import com.skydoves.orbital.animateBounds 54 | import com.skydoves.orbital.rememberMovableContentOf 55 | 56 | @Composable 57 | internal fun OrbitalLazyColumnSample() { 58 | val mocks = MockUtils.getMockPosters() 59 | 60 | Orbital { 61 | LazyColumn { 62 | items(mocks, key = { it.name }) { poster -> 63 | var expanded by rememberSaveable { mutableStateOf(false) } 64 | AnimatedVisibility( 65 | remember { MutableTransitionState(false) } 66 | .apply { targetState = true }, 67 | enter = fadeIn(), 68 | ) { 69 | Orbital( 70 | modifier = Modifier 71 | .fillMaxWidth() 72 | .clickable { 73 | expanded = !expanded 74 | } 75 | .background(color = poster.color, shape = RoundedCornerShape(10.dp)), 76 | ) { 77 | val title = rememberMovableContentOf { 78 | Column( 79 | modifier = Modifier 80 | .padding(10.dp) 81 | .animateBounds(Modifier), 82 | ) { 83 | Text( 84 | text = poster.name, 85 | fontSize = 18.sp, 86 | color = Color.Black, 87 | fontWeight = FontWeight.Bold, 88 | ) 89 | 90 | Text( 91 | text = poster.description, 92 | color = Color.Gray, 93 | fontSize = 12.sp, 94 | maxLines = 3, 95 | overflow = TextOverflow.Ellipsis, 96 | fontWeight = FontWeight.Bold, 97 | ) 98 | } 99 | } 100 | val image = rememberMovableContentOf { 101 | GlideImage( 102 | imageModel = { poster.poster }, 103 | component = rememberImageComponent { 104 | +CrossfadePlugin() 105 | }, 106 | modifier = Modifier 107 | .padding(10.dp) 108 | .animateBounds( 109 | if (expanded) { 110 | Modifier.fillMaxWidth() 111 | } else { 112 | Modifier.size(80.dp) 113 | }, 114 | spring(stiffness = Spring.StiffnessLow), 115 | ) 116 | .clip(RoundedCornerShape(5.dp)), 117 | imageOptions = ImageOptions(requestSize = IntSize(600, 600)), 118 | ) 119 | } 120 | 121 | if (expanded) { 122 | Column { 123 | image() 124 | title() 125 | } 126 | } else { 127 | Row { 128 | image() 129 | title() 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/Poster.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo 17 | 18 | import androidx.compose.ui.graphics.Color 19 | 20 | data class Poster( 21 | val name: String, 22 | val release: String, 23 | val playtime: String, 24 | val description: String, 25 | val poster: String, 26 | val gif: String, 27 | val color: Color, 28 | ) 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/PosterDetails.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo 17 | 18 | import androidx.compose.foundation.background 19 | import androidx.compose.foundation.clickable 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.fillMaxSize 23 | import androidx.compose.foundation.layout.fillMaxWidth 24 | import androidx.compose.foundation.layout.padding 25 | import androidx.compose.foundation.rememberScrollState 26 | import androidx.compose.foundation.verticalScroll 27 | import androidx.compose.material.Icon 28 | import androidx.compose.material.MaterialTheme 29 | import androidx.compose.material.Text 30 | import androidx.compose.material.icons.Icons 31 | import androidx.compose.material.icons.filled.ArrowBack 32 | import androidx.compose.runtime.Composable 33 | import androidx.compose.ui.Modifier 34 | import androidx.compose.ui.graphics.Color 35 | import androidx.compose.ui.text.style.TextOverflow 36 | import androidx.compose.ui.unit.dp 37 | import androidx.constraintlayout.compose.ConstraintLayout 38 | 39 | @Composable 40 | fun PosterDetails( 41 | poster: Poster, 42 | sharedElementContent: @Composable () -> Unit, 43 | pressOnBack: () -> Unit, 44 | ) { 45 | Column( 46 | modifier = Modifier 47 | .verticalScroll(rememberScrollState()) 48 | .background(MaterialTheme.colors.background) 49 | .fillMaxSize(), 50 | ) { 51 | ConstraintLayout { 52 | val (arrow, image, title, content, _, _) = createRefs() 53 | 54 | Box( 55 | modifier = Modifier 56 | .constrainAs(image) { 57 | top.linkTo(parent.top) 58 | } 59 | .fillMaxWidth(), 60 | ) { 61 | sharedElementContent() 62 | } 63 | 64 | Text( 65 | text = poster.name, 66 | style = MaterialTheme.typography.h1, 67 | overflow = TextOverflow.Ellipsis, 68 | maxLines = 1, 69 | modifier = Modifier 70 | .constrainAs(title) { 71 | top.linkTo(image.bottom) 72 | } 73 | .padding(start = 16.dp, top = 12.dp), 74 | ) 75 | 76 | Text( 77 | text = poster.description, 78 | style = MaterialTheme.typography.body2, 79 | modifier = Modifier 80 | .constrainAs(content) { 81 | top.linkTo(title.bottom) 82 | } 83 | .padding(16.dp), 84 | ) 85 | 86 | Icon( 87 | imageVector = Icons.Filled.ArrowBack, 88 | tint = Color.White, 89 | contentDescription = null, 90 | modifier = Modifier 91 | .constrainAs(arrow) { 92 | top.linkTo(parent.top) 93 | } 94 | .padding(12.dp) 95 | .clickable(onClick = { pressOnBack() }), 96 | ) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/ScreenTransitionSample.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo 17 | 18 | import androidx.compose.animation.core.Spring 19 | import androidx.compose.animation.core.spring 20 | import androidx.compose.foundation.background 21 | import androidx.compose.foundation.clickable 22 | import androidx.compose.foundation.layout.Column 23 | import androidx.compose.foundation.layout.Row 24 | import androidx.compose.foundation.layout.fillMaxSize 25 | import androidx.compose.foundation.layout.fillMaxWidth 26 | import androidx.compose.foundation.layout.padding 27 | import androidx.compose.foundation.layout.size 28 | import androidx.compose.foundation.shape.RoundedCornerShape 29 | import androidx.compose.material.Text 30 | import androidx.compose.runtime.Composable 31 | import androidx.compose.runtime.getValue 32 | import androidx.compose.runtime.mutableStateOf 33 | import androidx.compose.runtime.saveable.rememberSaveable 34 | import androidx.compose.runtime.setValue 35 | import androidx.compose.ui.Modifier 36 | import androidx.compose.ui.draw.clip 37 | import androidx.compose.ui.graphics.Color 38 | import androidx.compose.ui.text.font.FontWeight 39 | import androidx.compose.ui.text.style.TextOverflow 40 | import androidx.compose.ui.unit.IntOffset 41 | import androidx.compose.ui.unit.IntSize 42 | import androidx.compose.ui.unit.dp 43 | import androidx.compose.ui.unit.sp 44 | import com.skydoves.landscapist.ImageOptions 45 | import com.skydoves.landscapist.animation.crossfade.CrossfadePlugin 46 | import com.skydoves.landscapist.components.rememberImageComponent 47 | import com.skydoves.landscapist.glide.GlideImage 48 | import com.skydoves.orbital.Orbital 49 | import com.skydoves.orbital.animateBounds 50 | import com.skydoves.orbital.rememberMovableContentOf 51 | 52 | enum class Screen { 53 | A, B 54 | } 55 | 56 | @Composable 57 | fun ScreenTransitionSample() { 58 | Orbital { 59 | var screen by rememberSaveable { mutableStateOf(Screen.A) } 60 | val sizeAnim = spring(stiffness = Spring.StiffnessLow) 61 | val positionAnim = spring(stiffness = Spring.StiffnessLow) 62 | val image = rememberMovableContentOf { 63 | GlideImage( 64 | imageModel = { MockUtils.getMockPoster().poster }, 65 | component = rememberImageComponent { 66 | +CrossfadePlugin() 67 | }, 68 | modifier = Modifier 69 | .padding(10.dp) 70 | .animateBounds( 71 | modifier = if (screen == Screen.A) { 72 | Modifier.size(80.dp) 73 | } else { 74 | Modifier.fillMaxWidth() 75 | }, 76 | sizeAnimationSpec = sizeAnim, 77 | positionAnimationSpec = positionAnim, 78 | ) 79 | .clip(RoundedCornerShape(12.dp)), 80 | imageOptions = ImageOptions(requestSize = IntSize(600, 600)), 81 | ) 82 | } 83 | 84 | val title = rememberMovableContentOf { 85 | Column( 86 | modifier = Modifier 87 | .padding(10.dp) 88 | .animateBounds( 89 | modifier = Modifier, 90 | sizeAnimationSpec = sizeAnim, 91 | positionAnimationSpec = positionAnim, 92 | ), 93 | ) { 94 | Text( 95 | text = MockUtils.getMockPoster().name, 96 | fontSize = 18.sp, 97 | color = Color.Black, 98 | fontWeight = FontWeight.Bold, 99 | ) 100 | 101 | Text( 102 | text = MockUtils.getMockPoster().description, 103 | color = Color.Gray, 104 | fontSize = 12.sp, 105 | maxLines = 3, 106 | overflow = TextOverflow.Ellipsis, 107 | fontWeight = FontWeight.Bold, 108 | ) 109 | } 110 | } 111 | 112 | if (screen == Screen.A) { 113 | ScreenA( 114 | sharedContent = { 115 | image() 116 | title() 117 | }, 118 | ) { 119 | screen = Screen.B 120 | } 121 | } else { 122 | ScreenB( 123 | sharedContent = { 124 | image() 125 | title() 126 | }, 127 | ) { 128 | screen = Screen.A 129 | } 130 | } 131 | } 132 | } 133 | 134 | @Composable 135 | private fun ScreenA( 136 | sharedContent: @Composable () -> Unit, 137 | navigateToScreenB: () -> Unit, 138 | ) { 139 | Orbital { 140 | Row( 141 | modifier = Modifier 142 | .background(color = Color(0xFFffd7d7)) 143 | .fillMaxSize() 144 | .clickable { 145 | navigateToScreenB.invoke() 146 | }, 147 | ) { 148 | sharedContent() 149 | } 150 | } 151 | } 152 | 153 | @Composable 154 | private fun ScreenB( 155 | sharedContent: @Composable () -> Unit, 156 | navigateToScreenA: () -> Unit, 157 | ) { 158 | Orbital { 159 | Column( 160 | modifier = Modifier 161 | .background(color = Color(0xFFe3ffd9)) 162 | .fillMaxSize() 163 | .clickable { 164 | navigateToScreenA() 165 | }, 166 | ) { 167 | sharedContent() 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/ui/Colors.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo.ui 17 | 18 | import androidx.compose.ui.graphics.Color 19 | 20 | val purple200 = Color(0xFF651FFF) 21 | val purple500 = Color(0xFF6200EA) 22 | val background = Color(0xFF2B292B) 23 | val background800 = Color(0xFF424242) 24 | val background900 = Color(0xFF212121) 25 | val white87 = Color(0Xddffffff) 26 | val shimmerHighLight = Color(0xA3C2C2C2) 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/ui/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo.ui 17 | 18 | import androidx.compose.foundation.isSystemInDarkTheme 19 | import androidx.compose.material.MaterialTheme 20 | import androidx.compose.material.darkColors 21 | import androidx.compose.material.lightColors 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.graphics.Color 24 | 25 | private val DarkColorPalette = darkColors( 26 | background = background, 27 | surface = background800, 28 | primary = purple200, 29 | primaryVariant = purple500, 30 | secondary = purple500, 31 | onPrimary = Color.White, 32 | onSecondary = Color.White, 33 | ) 34 | 35 | private val LightColorPalette = lightColors( 36 | background = Color.White, 37 | surface = Color.White, 38 | primary = purple200, 39 | primaryVariant = purple500, 40 | secondary = purple500, 41 | onPrimary = Color.White, 42 | onSecondary = Color.White, 43 | ) 44 | 45 | @Composable 46 | fun OrbitalTheme( 47 | darkTheme: Boolean = isSystemInDarkTheme(), 48 | content: @Composable () -> Unit, 49 | ) { 50 | val colors = if (darkTheme) { 51 | DarkColorPalette 52 | } else { 53 | LightColorPalette 54 | } 55 | 56 | val typography = if (darkTheme) { 57 | DarkTypography 58 | } else { 59 | LightTypography 60 | } 61 | 62 | MaterialTheme( 63 | colors = colors, 64 | typography = typography, 65 | content = content, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/orbitaldemo/ui/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbitaldemo.ui 17 | 18 | import androidx.compose.material.Typography 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.compose.ui.text.TextStyle 21 | import androidx.compose.ui.text.font.FontFamily 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.unit.sp 24 | 25 | // set of dark material typography styles to start with. 26 | val DarkTypography = Typography( 27 | h1 = TextStyle( 28 | fontFamily = FontFamily.Default, 29 | fontWeight = FontWeight.Bold, 30 | color = Color.White, 31 | fontSize = 28.sp, 32 | ), 33 | h2 = TextStyle( 34 | fontFamily = FontFamily.Default, 35 | fontWeight = FontWeight.Bold, 36 | color = Color.White, 37 | fontSize = 21.sp, 38 | ), 39 | body1 = TextStyle( 40 | fontFamily = FontFamily.Default, 41 | fontWeight = FontWeight.Normal, 42 | color = Color.White, 43 | fontSize = 14.sp, 44 | ), 45 | body2 = TextStyle( 46 | fontFamily = FontFamily.Default, 47 | fontWeight = FontWeight.Normal, 48 | color = white87, 49 | fontSize = 14.sp, 50 | ), 51 | ) 52 | 53 | // set of light material typography styles to start with. 54 | val LightTypography = Typography( 55 | h1 = TextStyle( 56 | fontFamily = FontFamily.Default, 57 | fontWeight = FontWeight.Bold, 58 | color = background900, 59 | fontSize = 28.sp, 60 | ), 61 | h2 = TextStyle( 62 | fontFamily = FontFamily.Default, 63 | fontWeight = FontWeight.Bold, 64 | color = background900, 65 | fontSize = 21.sp, 66 | ), 67 | body1 = TextStyle( 68 | fontFamily = FontFamily.Default, 69 | fontWeight = FontWeight.Normal, 70 | color = background800, 71 | fontSize = 14.sp, 72 | ), 73 | body2 = TextStyle( 74 | fontFamily = FontFamily.Default, 75 | fontWeight = FontWeight.Normal, 76 | color = background800, 77 | fontSize = 14.sp, 78 | ), 79 | ) 80 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 25 | 31 | 34 | 37 | 38 | 39 | 40 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 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 | 175 | 180 | 185 | 186 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/drawable/poster.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #FFBB86FC 19 | #FF6200EE 20 | #FF3700B3 21 | #FF03DAC5 22 | #FF018786 23 | #FF000000 24 | #FFFFFFFF 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | OrbitalDemo 19 | Settings 20 | 21 | First Fragment 22 | Second Fragment 23 | Next 24 | Previous 25 | 26 | Hello first fragment 27 | Hello second fragment. Arg: %1$s 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /benchmark-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /benchmark-app/api/benchmark-app.api: -------------------------------------------------------------------------------- 1 | public final class com/skydoves/orbital/benchmark/app/BuildConfig { 2 | public static final field APPLICATION_ID Ljava/lang/String; 3 | public static final field BUILD_TYPE Ljava/lang/String; 4 | public static final field DEBUG Z 5 | public fun ()V 6 | } 7 | 8 | public final class com/skydoves/orbital/benchmark/app/ComposableSingletons$MainActivityKt { 9 | public static final field INSTANCE Lcom/skydoves/orbital/benchmark/app/ComposableSingletons$MainActivityKt; 10 | public static field lambda-1 Lkotlin/jvm/functions/Function2; 11 | public fun ()V 12 | public final fun getLambda-1$benchmark_app_release ()Lkotlin/jvm/functions/Function2; 13 | } 14 | 15 | public final class com/skydoves/orbital/benchmark/app/MainActivity : androidx/activity/ComponentActivity { 16 | public static final field $stable I 17 | public fun ()V 18 | } 19 | 20 | public final class com/skydoves/orbital/benchmark/app/profiles/AnimationSpecsKt { 21 | public static final fun getMovementSpec ()Landroidx/compose/animation/core/SpringSpec; 22 | public static final fun getTransformationSpec ()Landroidx/compose/animation/core/SpringSpec; 23 | } 24 | 25 | public final class com/skydoves/orbital/benchmark/app/profiles/OrbitalMovementProfilesKt { 26 | public static final fun OrbitalMovementProfiles (Landroidx/compose/runtime/Composer;I)V 27 | } 28 | 29 | public final class com/skydoves/orbital/benchmark/app/profiles/OrbitalSharedElementTransitionProfilesKt { 30 | public static final fun OrbitalSharedElementTransitionProfiles (Landroidx/compose/runtime/Composer;I)V 31 | } 32 | 33 | public final class com/skydoves/orbital/benchmark/app/profiles/OrbitalTransformationProfilesKt { 34 | public static final fun OrbitalTransformationProfiles (Landroidx/compose/runtime/Composer;I)V 35 | } 36 | 37 | -------------------------------------------------------------------------------- /benchmark-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.skydoves.orbital.Configuration 2 | 3 | plugins { 4 | id(libs.plugins.android.application.get().pluginId) 5 | id(libs.plugins.kotlin.android.get().pluginId) 6 | id(libs.plugins.compose.compiler.get().pluginId) 7 | id(libs.plugins.baseline.profile.get().pluginId) 8 | } 9 | 10 | android { 11 | namespace = "com.skydoves.orbital.benchmark.app" 12 | compileSdk = Configuration.compileSdk 13 | 14 | defaultConfig { 15 | applicationId = "com.skydoves.orbital.benchmark.app" 16 | minSdk = 23 17 | targetSdk = Configuration.targetSdk 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility = JavaVersion.VERSION_11 22 | targetCompatibility = JavaVersion.VERSION_11 23 | } 24 | 25 | kotlinOptions { 26 | jvmTarget = libs.versions.jvmTarget.get() 27 | } 28 | 29 | buildFeatures { 30 | compose = true 31 | buildConfig = true 32 | } 33 | 34 | lint { 35 | abortOnError = false 36 | } 37 | 38 | buildTypes { 39 | debug { 40 | isDebuggable = true 41 | } 42 | create("benchmark") { 43 | initWith(buildTypes.getByName("release")) 44 | signingConfig = signingConfigs.getByName("debug") 45 | matchingFallbacks += listOf("release") 46 | isDebuggable = false 47 | } 48 | } 49 | } 50 | 51 | dependencies { 52 | implementation(project(":orbital")) 53 | 54 | implementation(platform(libs.androidx.compose.bom)) 55 | implementation(libs.androidx.activity.compose) 56 | implementation(libs.androidx.compose.ui) 57 | implementation(libs.androidx.compose.ui.tooling) 58 | implementation(libs.androidx.compose.foundation) 59 | implementation(libs.androidx.compose.runtime) 60 | implementation(libs.androidx.compose.material) 61 | implementation(libs.androidx.compose.constraintlayout) 62 | implementation(libs.androidx.compose.material.iconsExtended) 63 | 64 | baselineProfile(project(":benchmark")) 65 | } -------------------------------------------------------------------------------- /benchmark-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 27 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /benchmark-app/src/main/kotlin/com/skydoves/orbital/benchmark/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital.benchmark.app 17 | 18 | import android.os.Bundle 19 | import androidx.activity.ComponentActivity 20 | import androidx.activity.compose.setContent 21 | import androidx.compose.foundation.layout.Column 22 | import com.skydoves.orbital.benchmark.app.profiles.OrbitalMovementProfiles 23 | import com.skydoves.orbital.benchmark.app.profiles.OrbitalSharedElementTransitionProfiles 24 | import com.skydoves.orbital.benchmark.app.profiles.OrbitalTransformationProfiles 25 | 26 | class MainActivity : ComponentActivity() { 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | 30 | setContent { 31 | Column { 32 | OrbitalMovementProfiles() 33 | OrbitalTransformationProfiles() 34 | OrbitalSharedElementTransitionProfiles() 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /benchmark-app/src/main/kotlin/com/skydoves/orbital/benchmark/app/profiles/AnimationSpecs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital.benchmark.app.profiles 17 | 18 | import androidx.compose.animation.core.Spring 19 | import androidx.compose.animation.core.SpringSpec 20 | import androidx.compose.ui.unit.IntOffset 21 | import androidx.compose.ui.unit.IntSize 22 | 23 | val transformationSpec = SpringSpec( 24 | dampingRatio = Spring.DampingRatioMediumBouncy, 25 | stiffness = 200f, 26 | ) 27 | 28 | val movementSpec = SpringSpec( 29 | dampingRatio = Spring.DampingRatioMediumBouncy, 30 | stiffness = 200f, 31 | ) 32 | -------------------------------------------------------------------------------- /benchmark-app/src/main/kotlin/com/skydoves/orbital/benchmark/app/profiles/OrbitalMovementProfiles.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital.benchmark.app.profiles 17 | 18 | import androidx.compose.foundation.background 19 | import androidx.compose.foundation.clickable 20 | import androidx.compose.foundation.layout.Arrangement 21 | import androidx.compose.foundation.layout.Box 22 | import androidx.compose.foundation.layout.Column 23 | import androidx.compose.foundation.layout.fillMaxWidth 24 | import androidx.compose.foundation.layout.padding 25 | import androidx.compose.foundation.layout.size 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.runtime.getValue 28 | import androidx.compose.runtime.mutableStateOf 29 | import androidx.compose.runtime.saveable.rememberSaveable 30 | import androidx.compose.runtime.setValue 31 | import androidx.compose.ui.Alignment 32 | import androidx.compose.ui.Modifier 33 | import androidx.compose.ui.graphics.Color 34 | import androidx.compose.ui.unit.dp 35 | import com.skydoves.orbital.Orbital 36 | import com.skydoves.orbital.animateMovement 37 | import com.skydoves.orbital.rememberContentWithOrbitalScope 38 | 39 | @Composable 40 | fun OrbitalMovementProfiles() { 41 | var isTransformed by rememberSaveable { mutableStateOf(true) } 42 | val item = rememberContentWithOrbitalScope { 43 | Box( 44 | modifier = if (isTransformed) { 45 | Modifier.size(60.dp, 60.dp) 46 | } else { 47 | Modifier.size(30.dp, 30.dp) 48 | } 49 | .background(Color.Green) 50 | .animateMovement(this, movementSpec), 51 | ) 52 | } 53 | 54 | Orbital( 55 | modifier = Modifier 56 | .padding(20.dp) 57 | .fillMaxWidth() 58 | .clickable { isTransformed = !isTransformed }, 59 | isTransformed = isTransformed, 60 | onStartContent = { 61 | Column( 62 | horizontalAlignment = Alignment.Start, 63 | verticalArrangement = Arrangement.Center, 64 | ) { 65 | item() 66 | } 67 | }, 68 | onTransformedContent = { 69 | Column( 70 | horizontalAlignment = Alignment.End, 71 | verticalArrangement = Arrangement.Center, 72 | ) { 73 | item() 74 | } 75 | }, 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /benchmark-app/src/main/kotlin/com/skydoves/orbital/benchmark/app/profiles/OrbitalSharedElementTransitionProfiles.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital.benchmark.app.profiles 17 | 18 | import androidx.compose.animation.core.SpringSpec 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.clickable 21 | import androidx.compose.foundation.layout.Arrangement 22 | import androidx.compose.foundation.layout.Box 23 | import androidx.compose.foundation.layout.Column 24 | import androidx.compose.foundation.layout.fillMaxSize 25 | import androidx.compose.foundation.layout.fillMaxWidth 26 | import androidx.compose.foundation.layout.padding 27 | import androidx.compose.foundation.layout.size 28 | import androidx.compose.runtime.Composable 29 | import androidx.compose.runtime.getValue 30 | import androidx.compose.runtime.mutableStateOf 31 | import androidx.compose.runtime.saveable.rememberSaveable 32 | import androidx.compose.runtime.setValue 33 | import androidx.compose.ui.Alignment 34 | import androidx.compose.ui.Modifier 35 | import androidx.compose.ui.graphics.Color 36 | import androidx.compose.ui.unit.dp 37 | import com.skydoves.orbital.Orbital 38 | import com.skydoves.orbital.animateSharedElementTransition 39 | import com.skydoves.orbital.rememberContentWithOrbitalScope 40 | 41 | @Composable 42 | fun OrbitalSharedElementTransitionProfiles() { 43 | var isTransformed by rememberSaveable { mutableStateOf(false) } 44 | val item = rememberContentWithOrbitalScope { 45 | Box( 46 | modifier = if (isTransformed) { 47 | Modifier.fillMaxSize() 48 | } else { 49 | Modifier.size(60.dp, 60.dp) 50 | } 51 | .animateSharedElementTransition( 52 | this, 53 | SpringSpec(stiffness = 500f), 54 | SpringSpec(stiffness = 500f), 55 | ) 56 | .background(Color.Yellow), 57 | ) 58 | } 59 | 60 | Orbital( 61 | modifier = Modifier 62 | .fillMaxWidth() 63 | .clickable { isTransformed = !isTransformed }, 64 | ) { 65 | if (isTransformed) { 66 | Column( 67 | horizontalAlignment = Alignment.End, 68 | verticalArrangement = Arrangement.Center, 69 | ) { 70 | Box( 71 | modifier = Modifier 72 | .size(90.dp, 90.dp) 73 | .background(Color.Blue), 74 | ) 75 | } 76 | } else { 77 | Column( 78 | Modifier.padding(20.dp), 79 | ) { 80 | item() 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /benchmark-app/src/main/kotlin/com/skydoves/orbital/benchmark/app/profiles/OrbitalTransformationProfiles.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital.benchmark.app.profiles 17 | 18 | import androidx.compose.foundation.background 19 | import androidx.compose.foundation.clickable 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.foundation.layout.size 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.getValue 26 | import androidx.compose.runtime.mutableStateOf 27 | import androidx.compose.runtime.saveable.rememberSaveable 28 | import androidx.compose.runtime.setValue 29 | import androidx.compose.ui.Modifier 30 | import androidx.compose.ui.graphics.Color 31 | import androidx.compose.ui.unit.dp 32 | import com.skydoves.orbital.Orbital 33 | import com.skydoves.orbital.animateTransformation 34 | import com.skydoves.orbital.rememberContentWithOrbitalScope 35 | 36 | @Composable 37 | fun OrbitalTransformationProfiles() { 38 | var isTransformed by rememberSaveable { mutableStateOf(false) } 39 | val item = rememberContentWithOrbitalScope { 40 | Box( 41 | modifier = if (isTransformed) { 42 | Modifier.size(50.dp, 50.dp) 43 | } else { 44 | Modifier.size(20.dp, 20.dp) 45 | } 46 | .background(Color.Blue) 47 | .animateTransformation(this, transformationSpec), 48 | ) 49 | } 50 | 51 | Orbital( 52 | modifier = Modifier 53 | .clickable { isTransformed = !isTransformed }, 54 | ) { 55 | Column( 56 | Modifier.padding(20.dp), 57 | ) { 58 | item() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /benchmark-app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 25 | 31 | 34 | 37 | 38 | 39 | 40 | 46 | 47 | -------------------------------------------------------------------------------- /benchmark-app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 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 | 175 | 180 | 185 | 186 | -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/benchmark-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /benchmark-app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #FFBB86FC 19 | #FF6200EE 20 | #FF3700B3 21 | #FF03DAC5 22 | #FF018786 23 | #FF000000 24 | #FFFFFFFF 25 | 26 | -------------------------------------------------------------------------------- /benchmark-app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | benchmark-app 19 | 20 | -------------------------------------------------------------------------------- /benchmark-app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /benchmark/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.skydoves.orbital.Configuration 2 | 3 | plugins { 4 | id(libs.plugins.android.test.get().pluginId) 5 | id(libs.plugins.kotlin.android.get().pluginId) 6 | id(libs.plugins.baseline.profile.get().pluginId) 7 | } 8 | 9 | android { 10 | namespace = "com.skydoves.orbital.benchmark" 11 | compileSdk = Configuration.compileSdk 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_11 15 | targetCompatibility = JavaVersion.VERSION_11 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = libs.versions.jvmTarget.get() 20 | } 21 | 22 | defaultConfig { 23 | minSdk = 24 24 | targetSdk = Configuration.targetSdk 25 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 26 | } 27 | 28 | targetProjectPath = ":benchmark-app" 29 | 30 | testOptions.managedDevices.devices { 31 | maybeCreate("pixel6api31").apply { 32 | device = "Pixel 6" 33 | apiLevel = 31 34 | systemImageSource = "aosp" 35 | } 36 | } 37 | } 38 | 39 | // This is the plugin configuration. Everything is optional. Defaults are in the 40 | // comments. In this example, you use the GMD added earlier and disable connected devices. 41 | baselineProfile { 42 | 43 | // This specifies the managed devices to use that you run the tests on. The default 44 | // is none. 45 | managedDevices += "pixel6api31" 46 | 47 | // This enables using connected devices to generate profiles. The default is true. 48 | // When using connected devices, they must be rooted or API 33 and higher. 49 | useConnectedDevices = false 50 | } 51 | 52 | dependencies { 53 | implementation(libs.androidx.test.runner) 54 | implementation(libs.androidx.test.uiautomator) 55 | implementation(libs.androidx.benchmark.macro) 56 | implementation(libs.androidx.profileinstaller) 57 | } -------------------------------------------------------------------------------- /benchmark/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /benchmark/src/main/kotlin/com/skydoves/orbital/benchmark/BaselineProfileGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital.benchmark 17 | 18 | import android.os.Build 19 | import androidx.annotation.RequiresApi 20 | import androidx.benchmark.macro.junit4.BaselineProfileRule 21 | import org.junit.Rule 22 | import org.junit.Test 23 | 24 | @RequiresApi(Build.VERSION_CODES.P) 25 | class BaselineProfileGenerator { 26 | @get:Rule 27 | val baselineProfileRule = BaselineProfileRule() 28 | 29 | @Test 30 | fun startup() = 31 | baselineProfileRule.collect( 32 | packageName = "com.skydoves.orbital.benchmark.app", 33 | stableIterations = 2, 34 | maxIterations = 8, 35 | ) { 36 | pressHome() 37 | // This block defines the app's critical user journey. Here we are interested in 38 | // optimizing for app startup. But you can also navigate and scroll 39 | // through your most important UI. 40 | startActivityAndWait() 41 | device.waitForIdle() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Designed and developed by 2023 skydoves (Jaewoong Eum) 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @Suppress("DSL_SCOPE_VIOLATION") 16 | plugins { 17 | alias(libs.plugins.android.application) apply false 18 | alias(libs.plugins.android.library) apply false 19 | alias(libs.plugins.kotlin.multiplatform) apply false 20 | alias(libs.plugins.jetbrains.compose) apply false 21 | alias(libs.plugins.baseline.profile) apply false 22 | alias(libs.plugins.compose.compiler) apply false 23 | alias(libs.plugins.kotlin.binary.compatibility) 24 | alias(libs.plugins.nexus.plugin) 25 | alias(libs.plugins.spotless) 26 | alias(libs.plugins.dokka) 27 | } 28 | 29 | subprojects { 30 | apply(plugin = rootProject.libs.plugins.spotless.get().pluginId) 31 | 32 | tasks.withType { 33 | kotlinOptions.jvmTarget = libs.versions.jvmTarget.get() 34 | kotlinOptions.freeCompilerArgs += listOf( 35 | "-Xcontext-receivers" 36 | ) 37 | } 38 | 39 | configure { 40 | kotlin { 41 | target("**/*.kt") 42 | targetExclude("$buildDir/**/*.kt") 43 | ktlint().editorConfigOverride( 44 | mapOf( 45 | "indent_size" to "2", 46 | "continuation_indent_size" to "2" 47 | ) 48 | ) 49 | licenseHeaderFile(rootProject.file("spotless/copyright.kt")) 50 | trimTrailingWhitespace() 51 | endWithNewline() 52 | } 53 | format("xml") { 54 | target("**/*.xml") 55 | targetExclude("**/build/**/*.xml") 56 | // Look for the first XML tag that isn't a comment ( 17 | 18 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/AnimateBounds.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.animation.core.AnimationVector2D 19 | import androidx.compose.animation.core.FiniteAnimationSpec 20 | import androidx.compose.animation.core.Spring 21 | import androidx.compose.animation.core.VectorConverter 22 | import androidx.compose.animation.core.spring 23 | import androidx.compose.runtime.remember 24 | import androidx.compose.runtime.rememberCoroutineScope 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.composed 27 | import androidx.compose.ui.draw.drawWithContent 28 | import androidx.compose.ui.geometry.Offset 29 | import androidx.compose.ui.graphics.Color 30 | import androidx.compose.ui.graphics.drawscope.Stroke 31 | import androidx.compose.ui.graphics.drawscope.translate 32 | import androidx.compose.ui.layout.Placeable 33 | import androidx.compose.ui.layout.intermediateLayout 34 | import androidx.compose.ui.unit.Constraints 35 | import androidx.compose.ui.unit.IntOffset 36 | import androidx.compose.ui.unit.IntSize 37 | import androidx.compose.ui.unit.round 38 | 39 | /** 40 | * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt 41 | * 42 | * Creates a custom modifier to animate the bounds of the layout within the 43 | * LookaheadLayout, whenever there's a change in the layout. 44 | */ 45 | public fun Modifier.animateBounds( 46 | modifier: Modifier = Modifier, 47 | sizeAnimationSpec: FiniteAnimationSpec = spring( 48 | Spring.DampingRatioNoBouncy, 49 | Spring.StiffnessMediumLow, 50 | ), 51 | positionAnimationSpec: FiniteAnimationSpec = spring( 52 | Spring.DampingRatioNoBouncy, 53 | Spring.StiffnessMediumLow, 54 | ), 55 | debug: Boolean = false, 56 | orbitalScope: (orbitalScope: OrbitalScope) -> OrbitalScope = { it }, 57 | ): Modifier = composed { 58 | val coroutineScope = rememberCoroutineScope() 59 | val outerOffsetAnimation = 60 | remember { DeferredAnimation(coroutineScope, IntOffset.VectorConverter) } 61 | val outerSizeAnimation = remember { DeferredAnimation(coroutineScope, IntSize.VectorConverter) } 62 | 63 | val offsetAnimation = remember { DeferredAnimation(coroutineScope, IntOffset.VectorConverter) } 64 | val sizeAnimation = remember { DeferredAnimation(coroutineScope, IntSize.VectorConverter) } 65 | 66 | // The measure logic in `intermediateLayout` is skipped in the lookahead pass, as 67 | // intermediateLayout is expected to produce intermediate stages of a layout transform. 68 | // When the measure block is invoked after lookahead pass, the lookahead size of the 69 | // child will be accessible as a parameter to the measure block. 70 | this 71 | .drawWithContent { 72 | drawContent() 73 | if (debug) { 74 | val offset = outerOffsetAnimation.target!! - outerOffsetAnimation.value!! 75 | translate( 76 | offset.x.toFloat(), 77 | offset.y.toFloat(), 78 | ) { 79 | drawRect(Color.Black.copy(alpha = 0.5f), style = Stroke(10f)) 80 | } 81 | } 82 | } 83 | .intermediateLayout { measurable, constraints -> 84 | val (w, h) = outerSizeAnimation.updateTarget( 85 | lookaheadSize, 86 | sizeAnimationSpec, 87 | ) 88 | measurable 89 | .measure(constraints) 90 | .run { 91 | layout(w, h) { 92 | val (x, y) = outerOffsetAnimation.updateTargetBasedOnCoordinates( 93 | orbitalScope = OrbitalScope(this@intermediateLayout), 94 | placeableScope = this, 95 | animationSpec = positionAnimationSpec, 96 | ) 97 | place(x, y) 98 | } 99 | } 100 | } 101 | .then(modifier) 102 | .drawWithContent { 103 | drawContent() 104 | if (debug) { 105 | val offset = offsetAnimation.target!! - offsetAnimation.value!! 106 | translate( 107 | offset.x.toFloat(), 108 | offset.y.toFloat(), 109 | ) { 110 | drawRect(Color.Green.copy(alpha = 0.5f), style = Stroke(10f)) 111 | } 112 | } 113 | } 114 | .intermediateLayout { measurable, _ -> 115 | // When layout changes, the lookahead pass will calculate a new final size for the 116 | // child modifier. This lookahead size can be used to animate the size 117 | // change, such that the animation starts from the current size and gradually 118 | // change towards `lookaheadSize`. 119 | val (width, height) = sizeAnimation.updateTarget( 120 | lookaheadSize, 121 | sizeAnimationSpec, 122 | ) 123 | // Creates a fixed set of constraints using the animated size 124 | val animatedConstraints = Constraints.fixed(width, height) 125 | // Measure child/children with animated constraints. 126 | val placeable = measurable.measure(animatedConstraints) 127 | layout(placeable.width, placeable.height) { 128 | val (x, y) = with(orbitalScope(OrbitalScope(this@intermediateLayout))) { 129 | offsetAnimation.updateTargetBasedOnCoordinates( 130 | orbitalScope = OrbitalScope(this@intermediateLayout), 131 | placeableScope = this@layout, 132 | animationSpec = positionAnimationSpec, 133 | ) 134 | } 135 | placeable.place(x, y) 136 | } 137 | } 138 | } 139 | 140 | internal fun DeferredAnimation.updateTargetBasedOnCoordinates( 141 | orbitalScope: OrbitalScope, 142 | placeableScope: Placeable.PlacementScope, 143 | animationSpec: FiniteAnimationSpec, 144 | ): IntOffset { 145 | placeableScope.coordinates?.let { coordinates -> 146 | with(this@updateTargetBasedOnCoordinates) { 147 | val targetOffset = with(orbitalScope.lookaheadScope) { 148 | placeableScope.lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates) 149 | } 150 | val animOffset = updateTarget( 151 | targetOffset.round(), 152 | animationSpec, 153 | ) 154 | val current = with(orbitalScope.lookaheadScope) { 155 | placeableScope.lookaheadScopeCoordinates.localPositionOf( 156 | coordinates, 157 | Offset.Zero, 158 | ).round() 159 | } 160 | return (animOffset - current) 161 | } 162 | } 163 | 164 | return IntOffset.Zero 165 | } 166 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/AnimateMovement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.animation.core.FiniteAnimationSpec 19 | import androidx.compose.animation.core.Spring 20 | import androidx.compose.animation.core.VectorConverter 21 | import androidx.compose.animation.core.spring 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.mutableStateOf 24 | import androidx.compose.runtime.remember 25 | import androidx.compose.runtime.rememberCoroutineScope 26 | import androidx.compose.runtime.setValue 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.composed 29 | import androidx.compose.ui.geometry.Offset 30 | import androidx.compose.ui.layout.findRootCoordinates 31 | import androidx.compose.ui.layout.intermediateLayout 32 | import androidx.compose.ui.layout.onPlaced 33 | import androidx.compose.ui.unit.IntOffset 34 | import androidx.compose.ui.unit.round 35 | 36 | /** 37 | * https://github.com/androidx/androidx/blob/c8d02ae9dc3baa3ad7d974da00ed08e5d00dac74/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt 38 | * 39 | * Creates a custom modifier to animate the local position of the layout within the 40 | * LookaheadLayout, whenever there's a change in the layout. 41 | * 42 | * @param animationSpec An [FiniteAnimationSpec] which has [IntOffset] as a generic type. 43 | */ 44 | public fun Modifier.animateMovement( 45 | orbitalScope: OrbitalScope, 46 | animationSpec: FiniteAnimationSpec = spring( 47 | Spring.DampingRatioNoBouncy, 48 | Spring.StiffnessMediumLow, 49 | ), 50 | ): Modifier = composed { 51 | val coroutineScope = rememberCoroutineScope() 52 | var placementOffset: IntOffset by remember { mutableStateOf(IntOffset.Zero) } 53 | val offsetAnimation = remember { 54 | DeferredAnimation(coroutineScope, IntOffset.VectorConverter) 55 | } 56 | with(orbitalScope) { 57 | this@composed 58 | .onPlaced { layoutCoordinates -> 59 | // This block of code has the LookaheadCoordinates of the LookaheadLayout 60 | // as the first parameter, and the coordinates of this modifier as the second 61 | // parameter. 62 | 63 | // localLookaheadPositionOf returns the *target* position of this 64 | // modifier in the LookaheadLayout's local coordinates. 65 | val targetOffset = layoutCoordinates.findRootCoordinates() 66 | .localLookaheadPositionOf( 67 | layoutCoordinates, 68 | ) 69 | .round() 70 | offsetAnimation.updateTarget(targetOffset, animationSpec) 71 | 72 | // localPositionOf returns the *current* position of this 73 | // modifier in the LookaheadLayout's local coordinates. 74 | placementOffset = layoutCoordinates.findRootCoordinates() 75 | .localPositionOf( 76 | layoutCoordinates, Offset.Zero, 77 | ) 78 | .round() 79 | } 80 | // The measure logic in `intermediateLayout` is skipped in the lookahead pass, as 81 | // intermediateLayout is expected to produce intermediate stages of a layout 82 | // transform. When the measure block is invoked after lookahead pass, the lookahead 83 | // size of the child will be accessible as a parameter to the measure block. 84 | .intermediateLayout { measurable, constraints -> 85 | val placeable = measurable.measure(constraints) 86 | layout(placeable.width, placeable.height) { 87 | // offsetAnimation will animate the target position whenever it changes. 88 | // In order to place the child at the animated position, we need to offset 89 | // the child based on the target and current position in LookaheadLayout. 90 | val (x, y) = offsetAnimation.value?.let { it - placementOffset } 91 | // If offsetAnimation has not been set up yet (i.e. in the first frame), 92 | // skip the animation 93 | ?: (offsetAnimation.target!! - placementOffset) 94 | placeable.place(x, y) 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/AnimateSharedElementTransition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.animation.core.FiniteAnimationSpec 19 | import androidx.compose.animation.core.Spring 20 | import androidx.compose.animation.core.VectorConverter 21 | import androidx.compose.animation.core.spring 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.mutableStateOf 24 | import androidx.compose.runtime.remember 25 | import androidx.compose.runtime.rememberCoroutineScope 26 | import androidx.compose.runtime.setValue 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.composed 29 | import androidx.compose.ui.geometry.Offset 30 | import androidx.compose.ui.layout.findRootCoordinates 31 | import androidx.compose.ui.layout.intermediateLayout 32 | import androidx.compose.ui.layout.onPlaced 33 | import androidx.compose.ui.unit.Constraints 34 | import androidx.compose.ui.unit.IntOffset 35 | import androidx.compose.ui.unit.IntSize 36 | import androidx.compose.ui.unit.round 37 | 38 | /** 39 | * Allows a custom modifier to animate the local position and size of the layout within the 40 | * LookaheadLayout, whenever there's a change in the layout. 41 | * 42 | * @param movementSpec An [FiniteAnimationSpec] which has [IntOffset] as a generic type. 43 | * @param transformSpec An [FiniteAnimationSpec] which has [IntSize] as a generic type. 44 | */ 45 | public fun Modifier.animateSharedElementTransition( 46 | orbitalScope: OrbitalScope, 47 | movementSpec: FiniteAnimationSpec = spring( 48 | Spring.DampingRatioNoBouncy, 49 | Spring.StiffnessMediumLow, 50 | ), 51 | transformSpec: FiniteAnimationSpec = spring( 52 | Spring.DampingRatioNoBouncy, 53 | Spring.StiffnessMediumLow, 54 | ), 55 | ): Modifier = composed { 56 | val coroutineScope = rememberCoroutineScope() 57 | var placementOffset: IntOffset by remember { mutableStateOf(IntOffset.Zero) } 58 | 59 | val offsetAnimation = remember { 60 | DeferredAnimation(coroutineScope, IntOffset.VectorConverter) 61 | } 62 | val sizeAnimation = remember { 63 | DeferredAnimation(coroutineScope, IntSize.VectorConverter) 64 | } 65 | // The measure logic in `intermediateLayout` is skipped in the lookahead pass, as 66 | // intermediateLayout is expected to produce intermediate stages of a layout transform. 67 | // When the measure block is invoked after lookahead pass, the lookahead size of the 68 | // child will be accessible as a parameter to the measure block. 69 | with(orbitalScope) { 70 | this@composed 71 | .intermediateLayout { measurable, constraints -> 72 | val (width, height) = sizeAnimation.value ?: lookaheadSize 73 | measurable 74 | .measure(constraints) 75 | .run { 76 | layout(width, height) { 77 | place(0, 0) 78 | } 79 | } 80 | } 81 | .onPlaced { layoutCoordinates -> 82 | // This block of code has the LookaheadCoordinates of the LookaheadLayout 83 | // as the first parameter, and the coordinates of this modifier as the second 84 | // parameter. 85 | 86 | // localLookaheadPositionOf returns the *target* position of this 87 | // modifier in the LookaheadLayout's local coordinates. 88 | val targetOffset = layoutCoordinates.findRootCoordinates() 89 | .localLookaheadPositionOf( 90 | layoutCoordinates, 91 | ) 92 | .round() 93 | offsetAnimation.updateTarget(targetOffset, movementSpec) 94 | 95 | // localPositionOf returns the *current* position of this 96 | // modifier in the LookaheadLayout's local coordinates. 97 | placementOffset = layoutCoordinates.findRootCoordinates() 98 | .localPositionOf( 99 | layoutCoordinates, Offset.Zero, 100 | ) 101 | .round() 102 | } 103 | .intermediateLayout { measurable, _ -> 104 | // When layout changes, the lookahead pass will calculate a new final size for the 105 | // child modifier. This lookahead size can be used to animate the size 106 | // change, such that the animation starts from the current size and gradually 107 | // change towards `lookaheadSize`. 108 | sizeAnimation.updateTarget(lookaheadSize, transformSpec) 109 | // Reads the animation size if the animation is set up. Otherwise (i.e. first 110 | // frame), use the lookahead size without animation. 111 | val (width, height) = sizeAnimation.value ?: lookaheadSize 112 | // Creates a fixed set of constraints using the animated size 113 | val animatedConstraints = Constraints.fixed(width.coerceAtLeast(0), height.coerceAtLeast(0)) 114 | // Measure child/children with animated constraints. 115 | val placeable = measurable.measure(animatedConstraints) 116 | layout(placeable.width, placeable.height) { 117 | // offsetAnimation will animate the target position whenever it changes. 118 | // In order to place the child at the animated position, we need to offset 119 | // the child based on the target and current position in LookaheadLayout. 120 | val (x, y) = offsetAnimation.value?.let { it - placementOffset } 121 | // If offsetAnimation has not been set up yet (i.e. in the first frame), 122 | // skip the animation 123 | ?: (offsetAnimation.target!! - placementOffset) 124 | placeable.place(x, y) 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/AnimateTransformation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.animation.core.FiniteAnimationSpec 19 | import androidx.compose.animation.core.Spring 20 | import androidx.compose.animation.core.VectorConverter 21 | import androidx.compose.animation.core.spring 22 | import androidx.compose.runtime.remember 23 | import androidx.compose.runtime.rememberCoroutineScope 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.composed 26 | import androidx.compose.ui.layout.intermediateLayout 27 | import androidx.compose.ui.unit.Constraints 28 | import androidx.compose.ui.unit.IntSize 29 | 30 | /** 31 | * https://github.com/androidx/androidx/blob/c8d02ae9dc3baa3ad7d974da00ed08e5d00dac74/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt 32 | * 33 | * Creates a custom modifier to animate the local size of the layout within the 34 | * LookaheadLayout, whenever there's a change in the layout. 35 | * 36 | * @param animationSpec An [FiniteAnimationSpec] which has [IntSize] as a generic type. 37 | */ 38 | public fun Modifier.animateTransformation( 39 | orbitalScope: OrbitalScope, 40 | animationSpec: FiniteAnimationSpec = spring( 41 | Spring.DampingRatioNoBouncy, 42 | Spring.StiffnessMediumLow, 43 | ), 44 | ): Modifier = composed { 45 | val coroutineScope = rememberCoroutineScope() 46 | val sizeAnimation = remember { 47 | DeferredAnimation(coroutineScope, IntSize.VectorConverter) 48 | } 49 | // The measure logic in `intermediateLayout` is skipped in the lookahead pass, as 50 | // intermediateLayout is expected to produce intermediate stages of a layout transform. 51 | // When the measure block is invoked after lookahead pass, the lookahead size of the 52 | // child will be accessible as a parameter to the measure block. 53 | with(orbitalScope) { 54 | this@composed 55 | .intermediateLayout { measurable, _ -> 56 | // When layout changes, the lookahead pass will calculate a new final size for the 57 | // child modifier. This lookahead size can be used to animate the size 58 | // change, such that the animation starts from the current size and gradually 59 | // change towards `lookaheadSize`. 60 | sizeAnimation.updateTarget(lookaheadSize, animationSpec) 61 | // Reads the animation size if the animation is set up. Otherwise (i.e. first 62 | // frame), use the lookahead size without animation. 63 | val (width, height) = sizeAnimation.value ?: lookaheadSize 64 | // Creates a fixed set of constraints using the animated size and ensures the sizes are minimum zero. 65 | val animatedConstraints = Constraints.fixed( 66 | width.coerceAtLeast(0), 67 | height.coerceAtLeast(0), 68 | ) 69 | // Measure child/children with animated constraints. 70 | val placeable = measurable.measure(animatedConstraints) 71 | layout(placeable.width, placeable.height) { 72 | placeable.place(0, 0) 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/DeferredAnimation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.animation.core.Animatable 19 | import androidx.compose.animation.core.AnimationVector 20 | import androidx.compose.animation.core.FiniteAnimationSpec 21 | import androidx.compose.animation.core.TwoWayConverter 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.mutableStateOf 24 | import androidx.compose.runtime.setValue 25 | import kotlinx.coroutines.CoroutineScope 26 | import kotlinx.coroutines.launch 27 | 28 | internal class DeferredAnimation( 29 | private val coroutineScope: CoroutineScope, 30 | private val vectorConverter: TwoWayConverter, 31 | ) { 32 | val value: T? 33 | get() = animatable?.value ?: target 34 | var target: T? by mutableStateOf(null) 35 | private set 36 | private var animatable: Animatable? = null 37 | 38 | internal val isActive: Boolean 39 | get() = target != animatable?.targetValue || animatable?.isRunning == true 40 | 41 | fun updateTarget( 42 | targetValue: T, 43 | animationSpec: FiniteAnimationSpec, 44 | ): T { 45 | target = targetValue 46 | if (target != null && target != animatable?.targetValue) { 47 | animatable?.run { 48 | coroutineScope.launch { 49 | animateTo( 50 | targetValue, 51 | animationSpec, 52 | ) 53 | } 54 | } ?: Animatable(targetValue, vectorConverter).let { 55 | animatable = it 56 | } 57 | } 58 | return animatable?.value ?: targetValue 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/Orbital.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.remember 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.layout.Layout 22 | import androidx.compose.ui.layout.LookaheadScope 23 | import androidx.compose.ui.layout.MeasurePolicy 24 | import kotlin.math.max 25 | 26 | /** 27 | * Orbital is a layout that measures and placements internally, and collocates the measured layouts 28 | * on the [LookaheadScope] continuously. You can allows the [content] to run animations such as 29 | * [animateMovement], [animateTransformation], and [animateSharedElementTransition] on the [OrbitalScope]. 30 | * 31 | * @param modifier [Modifier] used to adjust the layout or drawing content. 32 | * @param measurePolicy The function that defines the measurement and layout. 33 | * @param content A Composable that receives [OrbitalScope]. 34 | */ 35 | @Composable 36 | public fun Orbital( 37 | modifier: Modifier = Modifier, 38 | measurePolicy: MeasurePolicy = internalMeasurePolicy, 39 | content: @Composable OrbitalScope.() -> Unit, 40 | ) { 41 | LookaheadScope { 42 | Layout( 43 | content = { 44 | val orbitalScope = remember { OrbitalScope(this) } 45 | orbitalScope.content() 46 | }, 47 | modifier = modifier, 48 | measurePolicy = measurePolicy, 49 | ) 50 | } 51 | } 52 | 53 | /** 54 | * Orbital is a layout that measures and placements internally, and collocates the measured layouts 55 | * on the [LookaheadScope] continuously. You can allows the [onStartContent] and [onTransformedContent] 56 | * to run animations such as [animateMovement], [animateTransformation], and [animateSharedElementTransition] 57 | * on the [OrbitalScope]. 58 | * 59 | * @param modifier [Modifier] used to adjust the layout or drawing content. 60 | * @param isTransformed The criteria to decide which Composable should be executed. 61 | * @param measurePolicy The function that defines the measurement and layout. 62 | * @param onStartContent A Composable that receives [OrbitalScope]. This will be executed if the [isTransformed] is `false`. 63 | * @param onTransformedContent A Composable that receives [OrbitalScope]. This will be executed if the [isTransformed] is `true`. 64 | */ 65 | @Composable 66 | public fun Orbital( 67 | modifier: Modifier = Modifier, 68 | isTransformed: Boolean, 69 | measurePolicy: MeasurePolicy = internalMeasurePolicy, 70 | onStartContent: @Composable OrbitalScope.() -> Unit, 71 | onTransformedContent: @Composable OrbitalScope.() -> Unit, 72 | ) { 73 | Orbital( 74 | modifier = modifier, 75 | measurePolicy = measurePolicy, 76 | content = { 77 | if (isTransformed) { 78 | onStartContent() 79 | } else { 80 | onTransformedContent() 81 | } 82 | }, 83 | ) 84 | } 85 | 86 | internal val internalMeasurePolicy = MeasurePolicy { measurables, constraints -> 87 | val contentConstraints = constraints.copy(minWidth = 0, minHeight = 0) 88 | val placeables = measurables.map { it.measure(contentConstraints) } 89 | val maxWidth: Int = max(placeables.maxOf { it.width }, constraints.minWidth) 90 | val maxHeight = max(placeables.maxOf { it.height }, constraints.minHeight) 91 | // Position the children. 92 | layout(maxWidth, maxHeight) { 93 | placeables.forEach { 94 | it.place(0, 0) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/OrbitalScope.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.ui.layout.LookaheadScope 19 | 20 | /** 21 | * OrbitalScope is a scope that wraps [LookaheadScope] to apply animations. 22 | */ 23 | public class OrbitalScope internal constructor(internal val lookaheadScope: LookaheadScope) : 24 | LookaheadScope by lookaheadScope 25 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/RememberMovables.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.movableContentWithReceiverOf 20 | import androidx.compose.runtime.remember 21 | 22 | /** 23 | * Remember [movableContentWithReceiverOf] with the content that has [OrbitalScope] as a receiver. 24 | * 25 | * @param content A Composable that has [OrbitalScope] as a receiver. 26 | */ 27 | @Composable 28 | public fun rememberContentWithOrbitalScope( 29 | content: @Composable OrbitalScope.() -> Unit, 30 | ): @Composable OrbitalScope.() -> Unit { 31 | return remember { 32 | movableContentWithReceiverOf(content = content) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /orbital/src/commonMain/kotlin/com/skydoves/orbital/RememberableContent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.skydoves.orbital 17 | 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.movableContentOf 20 | import androidx.compose.runtime.remember 21 | 22 | /** 23 | * Remember a movable Composable content. 24 | * 25 | * @param key1 A key to re-invalidate the remembered content. 26 | * @param content A movable Composable content that will be remembered. 27 | */ 28 | @Composable 29 | public inline fun rememberMovableContentOf( 30 | key1: Any? = null, 31 | crossinline content: @Composable () -> T, 32 | ): @Composable () -> Unit { 33 | return remember(key1 = key1) { 34 | movableContentOf { content.invoke() } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /previews/preview0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/previews/preview0.gif -------------------------------------------------------------------------------- /previews/preview1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/previews/preview1.gif -------------------------------------------------------------------------------- /previews/preview2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/previews/preview2.gif -------------------------------------------------------------------------------- /previews/preview3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/previews/preview3.gif -------------------------------------------------------------------------------- /previews/preview4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/previews/preview4.gif -------------------------------------------------------------------------------- /previews/preview5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skydoves/Orbital/85e41b6f21cb2318982d1578b33ba1dbd161d916/previews/preview5.gif -------------------------------------------------------------------------------- /scripts/publish-module.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.skydoves.orbital.Configuration 2 | 3 | apply(plugin = "com.vanniktech.maven.publish") 4 | 5 | rootProject.extra.apply { 6 | val snapshot = System.getenv("SNAPSHOT").toBoolean() 7 | val libVersion = if (snapshot) { 8 | Configuration.snapshotVersionName 9 | } else { 10 | Configuration.versionName 11 | } 12 | set("libVersion", libVersion) 13 | } 14 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | maven(url = "https://plugins.gradle.org/m2/") 7 | } 8 | } 9 | 10 | dependencyResolutionManagement { 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven(url = "https://plugins.gradle.org/m2/") 15 | maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev") 16 | } 17 | } 18 | rootProject.name = "OrbitalDemo" 19 | include(":app") 20 | include(":orbital") 21 | include(":benchmark-app") 22 | include(":benchmark") 23 | -------------------------------------------------------------------------------- /spotless/copyright.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /spotless/copyright.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2023 skydoves (Jaewoong Eum) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /spotless/copyright.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | --------------------------------------------------------------------------------