├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── 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 │ │ │ ├── drawable │ │ │ │ ├── circular_border_background.xml │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ ├── ic_user_circle.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── themes.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── image_placeholder.xml │ │ │ │ ├── activity_list.xml │ │ │ │ ├── image_placeholder_circle.xml │ │ │ │ ├── activity_routing.xml │ │ │ │ ├── item_data.xml │ │ │ │ ├── shimmer_item_data.xml │ │ │ │ └── activity_main.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ └── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ ├── java │ │ │ └── az │ │ │ │ └── lahza │ │ │ │ └── shimmereffect │ │ │ │ ├── adapter │ │ │ │ ├── ShimmerAdapter.kt │ │ │ │ └── DataAdapter.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── RoutingActivity.kt │ │ │ │ └── ListActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── az │ │ │ └── lahza │ │ │ └── shimmereffect │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── az │ │ └── lahza │ │ └── shimmereffect │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── .idea ├── .name ├── .gitignore ├── compiler.xml ├── kotlinc.xml ├── vcs.xml ├── migrations.xml ├── misc.xml ├── deploymentTargetSelector.xml ├── gradle.xml └── other.xml ├── shimmer_animation ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── az │ │ │ │ └── lahza │ │ │ │ └── shimmer_animation │ │ │ │ └── shimmer │ │ │ │ ├── builders │ │ │ │ ├── AlphaHighlightBuilder.kt │ │ │ │ ├── ColorHighlightBuilder.kt │ │ │ │ └── Builder.kt │ │ │ │ ├── annotations │ │ │ │ ├── Shape.kt │ │ │ │ └── Direction.kt │ │ │ │ ├── extensions │ │ │ │ └── TypedArrayExtensions.kt │ │ │ │ ├── Shimmer.kt │ │ │ │ ├── ShimmerFrameLayout.kt │ │ │ │ └── ShimmerDrawable.kt │ │ └── res │ │ │ └── values │ │ │ └── attrs.xml │ ├── test │ │ └── java │ │ │ └── az │ │ │ └── lahza │ │ │ └── shimmer_animation │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── az │ │ └── lahza │ │ └── shimmer_animation │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── jitpack.yml ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── examples ├── example shimmer animation.gif └── example shimmer animation base.gif ├── .gitignore ├── settings.gradle.kts ├── LICENCE ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Shimmer Effect -------------------------------------------------------------------------------- /shimmer_animation/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /shimmer_animation/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 3 | before_install: 4 | - ./scripts/prepareJitpackEnvironment.sh -------------------------------------------------------------------------------- /shimmer_animation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/example shimmer animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/examples/example shimmer animation.gif -------------------------------------------------------------------------------- /examples/example shimmer animation base.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/examples/example shimmer animation base.gif -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaminalirustemov/ShimmerAnimation/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circular_border_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Nov 13 04:26:33 AZT 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF000000 4 | #FFFFFFFF 5 | #DDDDDD 6 | #DDDDDD 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/image_placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/test/java/az/lahza/shimmereffect/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmereffect 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /shimmer_animation/src/test/java/az/lahza/shimmer_animation/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Shimmer Effect 3 | Shimmer animation for item description 4 | Shimmer animation for item price 5 | Shimmer animation for item name 6 | Shimmer animation for item photo 7 | User profile circular icon 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/builders/AlphaHighlightBuilder.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer.builders 2 | 3 | /** 4 | * A builder class for creating an alpha highlight shimmer effect. 5 | * 6 | * This builder enables the alpha shimmer effect and allows further customization. 7 | */ 8 | class AlphaHighlightBuilder : Builder() { 9 | 10 | /** 11 | *Return the current instance of AlphaHighlightBuilder for method chaining 12 | */ 13 | override val self: AlphaHighlightBuilder 14 | get() = this 15 | 16 | init { 17 | // Enable the alpha shimmer effect by default 18 | shimmer.alphaShimmer = true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/annotations/Shape.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer.annotations 2 | 3 | import androidx.annotation.IntDef 4 | 5 | /** 6 | * Annotation class for defining the type of shimmer effect shape. 7 | * 8 | * Supported shapes are: 9 | * - LINEAR: A linear shimmer effect. 10 | * - RADIAL: A radial shimmer effect. 11 | */ 12 | @Retention(AnnotationRetention.SOURCE) 13 | @IntDef(Shape.LINEAR, Shape.RADIAL) 14 | annotation class Shape { 15 | 16 | companion object { 17 | // Constant for a linear shimmer effect 18 | const val LINEAR = 0 19 | 20 | // Constant for a radial shimmer effect 21 | const val RADIAL = 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/az/lahza/shimmereffect/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmereffect 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("az.lahza.shimmereffect", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | 20 | // Include JitPack, a repository that allows you to fetch dependencies from GitHub projects. 21 | maven { url = uri("https://jitpack.io") } 22 | } 23 | } 24 | 25 | rootProject.name = "Shimmer Effect" 26 | include(":app") 27 | include(":shimmer_animation") 28 | -------------------------------------------------------------------------------- /shimmer_animation/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/annotations/Direction.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer.annotations 2 | 3 | import androidx.annotation.IntDef 4 | 5 | /** 6 | * Annotation for defining the direction of shimmer animation. 7 | * The direction can be one of the following: 8 | * - LEFT_TO_RIGHT 9 | * - TOP_TO_BOTTOM 10 | * - RIGHT_TO_LEFT 11 | * - BOTTOM_TO_TOP 12 | */ 13 | @Retention(AnnotationRetention.SOURCE) 14 | @IntDef( 15 | Direction.LEFT_TO_RIGHT, 16 | Direction.TOP_TO_BOTTOM, 17 | Direction.RIGHT_TO_LEFT, 18 | Direction.BOTTOM_TO_TOP 19 | ) 20 | annotation class Direction { 21 | 22 | companion object { 23 | const val LEFT_TO_RIGHT = 0 24 | const val TOP_TO_BOTTOM = 1 25 | const val RIGHT_TO_LEFT = 2 26 | const val BOTTOM_TO_TOP = 3 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /shimmer_animation/src/androidTest/java/az/lahza/shimmer_animation/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("az.lahza.shimmer_animation.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/image_placeholder_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_user_circle.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/az/lahza/shimmereffect/adapter/ShimmerAdapter.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmereffect.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import az.lahza.shimmereffect.databinding.ShimmerItemDataBinding 8 | 9 | class ShimmerAdapter(private val itemCount: Int) : 10 | RecyclerView.Adapter() { 11 | 12 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerViewHolder { 13 | val view = ShimmerItemDataBinding.inflate( 14 | LayoutInflater.from(parent.context), 15 | parent, 16 | false 17 | ) 18 | return ShimmerViewHolder(view.root) 19 | } 20 | 21 | override fun onBindViewHolder(holder: ShimmerViewHolder, position: Int) {} 22 | 23 | override fun getItemCount(): Int = itemCount 24 | 25 | inner class ShimmerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/az/lahza/shimmereffect/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmereffect 2 | 3 | import android.os.Bundle 4 | import androidx.activity.enableEdgeToEdge 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.core.view.ViewCompat 7 | import androidx.core.view.WindowInsetsCompat 8 | import az.lahza.shimmereffect.databinding.ActivityMainBinding 9 | 10 | class MainActivity : AppCompatActivity() { 11 | 12 | private lateinit var binding: ActivityMainBinding 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | enableEdgeToEdge() 16 | super.onCreate(savedInstanceState) 17 | binding = ActivityMainBinding.inflate(layoutInflater) 18 | setContentView(binding.root) 19 | 20 | ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> 21 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) 22 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) 23 | insets 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Zaminali Rustamov. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/az/lahza/shimmereffect/RoutingActivity.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmereffect 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.activity.enableEdgeToEdge 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.core.view.ViewCompat 8 | import androidx.core.view.WindowInsetsCompat 9 | import az.lahza.shimmereffect.databinding.ActivityRoutingBinding 10 | 11 | class RoutingActivity : AppCompatActivity() { 12 | 13 | private lateinit var binding: ActivityRoutingBinding 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | enableEdgeToEdge() 17 | super.onCreate(savedInstanceState) 18 | binding = ActivityRoutingBinding.inflate(layoutInflater) 19 | setContentView(binding.root) 20 | 21 | ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> 22 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) 23 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) 24 | insets 25 | } 26 | 27 | binding.btnMain.setOnClickListener { 28 | startActivity(Intent(this, MainActivity::class.java)) 29 | } 30 | 31 | binding.btnList.setOnClickListener { 32 | startActivity(Intent(this, ListActivity::class.java)) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | 25 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.5.1" 3 | kotlin = "1.9.0" 4 | coreKtx = "1.15.0" 5 | junit = "4.13.2" 6 | junitVersion = "1.2.1" 7 | espressoCore = "3.6.1" 8 | appcompat = "1.7.0" 9 | material = "1.12.0" 10 | activity = "1.9.3" 11 | constraintlayout = "2.2.0" 12 | shimmer_animation = "1.0.2" 13 | 14 | [libraries] 15 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 16 | junit = { group = "junit", name = "junit", version.ref = "junit" } 17 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } 18 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } 19 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } 20 | material = { group = "com.google.android.material", name = "material", version.ref = "material" } 21 | androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } 22 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } 23 | shimmer_animation = { module = "com.github.zaminalirustamov:Shimmer_Animation", version.ref = "shimmer_animation" } 24 | 25 | [plugins] 26 | android-application = { id = "com.android.application", version.ref = "agp" } 27 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 28 | android-library = { id = "com.android.library", version.ref = "agp" } 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_routing.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 22 | 23 | 26 | 27 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "az.lahza.shimmereffect" 8 | compileSdk = 35 9 | 10 | defaultConfig { 11 | applicationId = "az.lahza.shimmereffect" 12 | minSdk = 24 13 | targetSdk = 35 14 | versionCode = 1 15 | versionName = "1.0" 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | isMinifyEnabled = false 23 | proguardFiles( 24 | getDefaultProguardFile("proguard-android-optimize.txt"), 25 | "proguard-rules.pro" 26 | ) 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility = JavaVersion.VERSION_1_8 31 | targetCompatibility = JavaVersion.VERSION_1_8 32 | } 33 | kotlinOptions { 34 | jvmTarget = "1.8" 35 | } 36 | buildFeatures { 37 | viewBinding = true 38 | } 39 | } 40 | 41 | dependencies { 42 | // AndroidX libraries 43 | implementation(libs.androidx.core.ktx) 44 | implementation(libs.androidx.appcompat) 45 | implementation(libs.material) 46 | implementation(libs.androidx.activity) 47 | implementation(libs.androidx.constraintlayout) 48 | 49 | // Testing dependencies 50 | testImplementation(libs.junit) 51 | androidTestImplementation(libs.androidx.junit) 52 | androidTestImplementation(libs.androidx.espresso.core) 53 | 54 | // Shimmer animation library 55 | implementation(libs.shimmer.animation) 56 | } -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/extensions/TypedArrayExtensions.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer.extensions 2 | 3 | import android.content.res.TypedArray 4 | 5 | /** 6 | * Extension function to retrieve a boolean value from a TypedArray. 7 | * Returns null if the attribute does not exist. 8 | * 9 | * @param index The index of the attribute. 10 | * @return The boolean value or null if not present. 11 | */ 12 | fun TypedArray.getBooleanOrNull(index: Int): Boolean? = 13 | if (hasValue(index)) getBoolean(index, false) else null 14 | 15 | /** 16 | * Extension function to retrieve a float value from a TypedArray. 17 | * Returns null if the attribute does not exist. 18 | * 19 | * @param index The index of the attribute. 20 | * @return The float value or null if not present. 21 | */ 22 | fun TypedArray.getFloatOrNull(index: Int): Float? = 23 | if (hasValue(index)) getFloat(index, 0f) else null 24 | 25 | /** 26 | * Extension function to retrieve an integer value from a TypedArray. 27 | * Returns null if the attribute does not exist. 28 | * 29 | * @param index The index of the attribute. 30 | * @return The integer value or null if not present. 31 | */ 32 | fun TypedArray.getIntOrNull(index: Int): Int? = 33 | if (hasValue(index)) getInt(index, 0) else null 34 | 35 | /** 36 | * Extension function to retrieve a color value from a TypedArray. 37 | * Returns null if the attribute does not exist. 38 | * 39 | * @param index The index of the attribute. 40 | * @return The color value or null if not present. 41 | */ 42 | fun TypedArray.getColorOrNull(index: Int): Int? = 43 | if (hasValue(index)) getColor(index, 0) else null 44 | -------------------------------------------------------------------------------- /app/src/main/java/az/lahza/shimmereffect/adapter/DataAdapter.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmereffect.adapter 2 | 3 | import android.graphics.Color 4 | import android.graphics.drawable.ColorDrawable 5 | import android.graphics.drawable.Drawable 6 | import android.view.LayoutInflater 7 | import android.view.ViewGroup 8 | import androidx.recyclerview.widget.RecyclerView 9 | import az.lahza.shimmereffect.databinding.ItemDataBinding 10 | 11 | class DataAdapter(private val dataList: List) : 12 | RecyclerView.Adapter() { 13 | 14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataViewHolder { 15 | val binding = ItemDataBinding.inflate(LayoutInflater.from(parent.context), parent, false) 16 | return DataViewHolder(binding) 17 | } 18 | 19 | override fun onBindViewHolder(holder: DataViewHolder, position: Int) { 20 | holder.bind(dataList[position]) 21 | } 22 | 23 | override fun getItemCount(): Int = dataList.size 24 | 25 | inner class DataViewHolder(private var binding: ItemDataBinding) : 26 | RecyclerView.ViewHolder(binding.root) { 27 | fun bind(dataList: String) { 28 | binding.ivPhoto.setImageDrawable(getRandomDrawable()) 29 | binding.tvFirst.text = "Name $dataList" 30 | binding.tvSecond.text = "Date $dataList" 31 | binding.tvThird.text = "Phone $dataList" 32 | } 33 | } 34 | 35 | private fun getRandomDrawable(): Drawable { 36 | return listOf( 37 | ColorDrawable(Color.parseColor("#740938")), 38 | ColorDrawable(Color.parseColor("#AF1740")), 39 | ColorDrawable(Color.parseColor("#CC2B52")), 40 | ColorDrawable(Color.parseColor("#DE7C7D")), 41 | ColorDrawable(Color.parseColor("#A64D79")) 42 | ).random() 43 | } 44 | } -------------------------------------------------------------------------------- /shimmer_animation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | `maven-publish` 5 | } 6 | 7 | android { 8 | namespace = "az.lahza.shimmer_animation" 9 | compileSdk = 35 10 | 11 | defaultConfig { 12 | minSdk = 24 13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles("consumer-rules.pro") 15 | } 16 | 17 | buildTypes { 18 | release { 19 | isMinifyEnabled = false 20 | proguardFiles( 21 | getDefaultProguardFile("proguard-android-optimize.txt"), 22 | "proguard-rules.pro" 23 | ) 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility = JavaVersion.VERSION_1_8 28 | targetCompatibility = JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = "1.8" 32 | } 33 | } 34 | 35 | dependencies { 36 | // AndroidX libraries 37 | implementation(libs.androidx.core.ktx) // Android KTX for Kotlin extensions 38 | implementation(libs.androidx.appcompat) // AppCompat for backward compatibility 39 | implementation(libs.material) // Material Design components 40 | 41 | // Testing dependencies 42 | testImplementation(libs.junit) // JUnit for unit tests 43 | androidTestImplementation(libs.androidx.junit) // JUnit for Android tests 44 | androidTestImplementation(libs.androidx.espresso.core) // Espresso for UI tests 45 | } 46 | 47 | 48 | afterEvaluate{ 49 | publishing { 50 | publications { 51 | create("maven") { 52 | groupId = "com.github.zaminalirustamov" 53 | artifactId = "shimmer_animation" 54 | version = "1.0.2" 55 | from(components["release"]) 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/az/lahza/shimmereffect/ListActivity.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmereffect 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.os.Looper 6 | import androidx.activity.enableEdgeToEdge 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.core.view.ViewCompat 9 | import androidx.core.view.WindowInsetsCompat 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import az.lahza.shimmereffect.adapter.DataAdapter 12 | import az.lahza.shimmereffect.adapter.ShimmerAdapter 13 | import az.lahza.shimmereffect.databinding.ActivityListBinding 14 | 15 | class ListActivity : AppCompatActivity() { 16 | 17 | private lateinit var binding: ActivityListBinding 18 | 19 | private lateinit var dataAdapter: DataAdapter 20 | private lateinit var shimmerAdapter: ShimmerAdapter 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | enableEdgeToEdge() 24 | super.onCreate(savedInstanceState) 25 | binding = ActivityListBinding.inflate(layoutInflater) 26 | setContentView(binding.root) 27 | 28 | ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> 29 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) 30 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) 31 | insets 32 | } 33 | binding.rvList.layoutManager = LinearLayoutManager(this) 34 | showShimmer() 35 | showActualData() 36 | } 37 | 38 | private fun showShimmer() { 39 | shimmerAdapter = ShimmerAdapter(10) // Show 10 shimmer items as a placeholder 40 | binding.rvList.adapter = shimmerAdapter 41 | } 42 | 43 | private fun showActualData() { 44 | // Simulate data loading with a delay 45 | val data = List(20) { "Item ${it + 1}" } // Generating dummy data 46 | Handler(Looper.getMainLooper()).postDelayed({ 47 | dataAdapter = DataAdapter(data) 48 | binding.rvList.adapter = dataAdapter // Replace shimmer adapter with the data adapter 49 | }, DELAY_TIME) 50 | } 51 | 52 | companion object { 53 | const val DELAY_TIME = 3000L // 3 seconds 54 | } 55 | } -------------------------------------------------------------------------------- /shimmer_animation/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/builders/ColorHighlightBuilder.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer.builders 2 | 3 | import android.content.res.TypedArray 4 | import androidx.annotation.ColorInt 5 | import az.lahza.shimmer_animation.R 6 | import az.lahza.shimmer_animation.shimmer.extensions.getColorOrNull 7 | 8 | /** 9 | * Builder class for configuring color highlight settings for shimmer animation. 10 | * This builder allows setting both the base color and highlight color of the shimmer effect. 11 | */ 12 | class ColorHighlightBuilder : Builder() { 13 | 14 | /** 15 | * Returns the current instance of ColorHighlightBuilder for method chaining. 16 | */ 17 | override val self: ColorHighlightBuilder get() = this 18 | 19 | init { 20 | // Disable alpha shimmer by default 21 | shimmer.alphaShimmer = false 22 | } 23 | 24 | /** 25 | * Sets the highlight color for the shimmer effect. 26 | * 27 | * @param color The color to be set as the highlight color. 28 | */ 29 | private fun setHighlightColor(@ColorInt color: Int) = apply { 30 | shimmer.highlightColor = color 31 | } 32 | 33 | /** 34 | * Sets the base color for the shimmer effect. 35 | * This method modifies the base color while keeping the alpha channel intact. 36 | * 37 | * @param color The color to be set as the base color. 38 | */ 39 | private fun setBaseColor(@ColorInt color: Int) = apply { 40 | shimmer.baseColor = (shimmer.baseColor and -0x1000000) or (color and 0x00FFFFFF) 41 | } 42 | 43 | /** 44 | * Loads attributes from a TypedArray and applies the color values. 45 | * 46 | * @param typedArray The TypedArray containing the attribute values. 47 | * @return The builder instance for method chaining. 48 | */ 49 | override fun loadAttributes(typedArray: TypedArray): ColorHighlightBuilder { 50 | super.loadAttributes(typedArray) 51 | 52 | // Get the base color and highlight color from the attributes and apply them 53 | typedArray.getColorOrNull(R.styleable.ShimmerFrameLayout_shimmer_base_color)?.let { setBaseColor(it) } 54 | typedArray.getColorOrNull(R.styleable.ShimmerFrameLayout_shimmer_highlight_color)?.let { setHighlightColor(it) } 55 | 56 | return this 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 30 | 31 | 41 | 42 | 43 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/shimmer_item_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 22 | 23 | 24 | 33 | 34 | 35 | 45 | 46 | 47 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/Shimmer.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer 2 | 3 | import android.animation.ValueAnimator 4 | import android.graphics.Color 5 | import androidx.annotation.ColorInt 6 | import az.lahza.shimmer_animation.shimmer.annotations.Direction 7 | import az.lahza.shimmer_animation.shimmer.annotations.Shape 8 | import kotlin.math.roundToInt 9 | 10 | /** 11 | * A class that defines the shimmer animation properties and behavior. 12 | */ 13 | class Shimmer { 14 | 15 | // ============================ 16 | // Shimmer Configuration 17 | // ============================ 18 | 19 | /** Direction of the shimmer animation */ 20 | @Direction var direction = Direction.LEFT_TO_RIGHT 21 | 22 | /** Highlight color of the shimmer effect */ 23 | @ColorInt var highlightColor = Color.WHITE 24 | 25 | /** Base color of the shimmer effect */ 26 | @ColorInt var baseColor = 0x4cffffff 27 | 28 | /** Shape of the shimmer animation */ 29 | @Shape var shape = Shape.LINEAR 30 | 31 | /** Fixed width of the shimmer (0 for dynamic) */ 32 | private var fixedWidth = 0 33 | 34 | /** Fixed height of the shimmer (0 for dynamic) */ 35 | private var fixedHeight = 0 36 | 37 | /** Width ratio relative to the view's width */ 38 | var widthRatio = 1f 39 | 40 | /** Height ratio relative to the view's height */ 41 | var heightRatio = 1f 42 | 43 | /** Intensity of the shimmer effect */ 44 | var intensity = 0f 45 | 46 | /** Dropoff effect for shimmer (controls fadeout) */ 47 | var dropoff = 0.5f 48 | 49 | /** Tilt angle for shimmer movement */ 50 | var tilt = 20f 51 | 52 | /** Whether to clip the shimmer to the children views */ 53 | var clipToChildren = true 54 | 55 | /** Whether the shimmer effect should auto-start */ 56 | var autoStart = true 57 | 58 | /** Whether the shimmer effect includes alpha shimmer */ 59 | var alphaShimmer = true 60 | 61 | /** Number of repetitions for the shimmer effect */ 62 | var repeatCount = ValueAnimator.INFINITE 63 | 64 | /** The repeat mode of the shimmer animation */ 65 | var repeatMode = ValueAnimator.RESTART 66 | 67 | /** Duration of the shimmer animation */ 68 | var animationDuration = 1400L 69 | 70 | /** Delay before the shimmer effect starts */ 71 | var repeatDelay: Long = 0 72 | 73 | /** Delay before the shimmer effect begins animating */ 74 | var startDelay: Long = 0 75 | 76 | // ============================ 77 | // Shimmer Effect Arrays 78 | // ============================ 79 | 80 | /** Array of shimmer effect positions */ 81 | val positions = FloatArray(COMPONENT_COUNT) 82 | 83 | /** Array of shimmer effect colors */ 84 | val colors = IntArray(COMPONENT_COUNT) 85 | 86 | // ============================ 87 | // Width and Height Calculations 88 | // ============================ 89 | 90 | /** 91 | * Calculates the shimmer width based on fixedWidth or widthRatio. 92 | */ 93 | fun width(width: Int) = if (fixedWidth > 0) fixedWidth else (widthRatio * width).roundToInt() 94 | 95 | /** 96 | * Calculates the shimmer height based on fixedHeight or heightRatio. 97 | */ 98 | fun height(height: Int) = if (fixedHeight > 0) fixedHeight else (heightRatio * height).roundToInt() 99 | 100 | // ============================ 101 | // Update Shimmer Colors 102 | // ============================ 103 | 104 | /** 105 | * Updates the shimmer colors based on the shape of the shimmer effect. 106 | */ 107 | fun updateColors() { 108 | colors.apply { 109 | val colorSequence = when (shape) { 110 | Shape.LINEAR -> listOf(baseColor, highlightColor, highlightColor, baseColor) 111 | Shape.RADIAL -> listOf(highlightColor, highlightColor, baseColor, baseColor) 112 | else -> listOf(baseColor, highlightColor, highlightColor, baseColor) 113 | } 114 | colorSequence.forEachIndexed { index, color -> this[index] = color } 115 | } 116 | } 117 | 118 | // ============================ 119 | // Update Shimmer Positions 120 | // ============================ 121 | 122 | /** 123 | * Updates the shimmer positions based on intensity and dropOff. 124 | */ 125 | fun updatePositions() { 126 | val halfIntensity = (1f - intensity - dropoff) / 2f 127 | val halfIntensityEdge = (1f - intensity - 0.001f) / 2f 128 | val halfEndEdge = (1f + intensity + 0.001f) / 2f 129 | val halfEnd = (1f + intensity + dropoff) / 2f 130 | 131 | // Ensure values are within valid bounds (0f to 1f) 132 | val startPosition = halfIntensity.coerceAtLeast(0f) 133 | val startEdgePosition = halfIntensityEdge.coerceAtLeast(0f) 134 | val endEdgePosition = halfEndEdge.coerceAtMost(1f) 135 | val endPosition = halfEnd.coerceAtMost(1f) 136 | 137 | // Update the shimmer gradient positions 138 | positions.apply { 139 | this[0] = startPosition 140 | this[1] = startEdgePosition 141 | this[2] = endEdgePosition 142 | this[3] = endPosition 143 | } 144 | } 145 | 146 | companion object { 147 | /** The number of components in the shimmer effect */ 148 | private const val COMPONENT_COUNT = 4 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Shimmer Animation 4 | - ![kotlin](https://img.shields.io/badge/Platforms-Kotlin_Compatible-lightblue?style=for-the-badge&logo=kotlin) 5 | - ![kotlin](https://img.shields.io/badge/Made_With-Kotlin-0095D9?style=for-the-badge&logo=kotlin) 6 | - ![android_7](https://img.shields.io/badge/Android-7.0_Nougat-green?style=for-the-badge) 7 | - ![min_sdk](https://img.shields.io/badge/minSdk-24-orange?style=for-the-badge) 8 | 9 |

A Shimmer Animation library for Kotlin.

10 |

11 | 12 | 13 |

14 | 15 | 16 | ## 📦 Features 17 | 18 | - 🌈 **Customizable Direction** 19 | Tailor the shimmer animation’s direction to fit your design needs: 20 | - **LEFT_TO_RIGHT**: Shimmer moves from left to right 21 | - **TOP_TO_BOTTOM**: Shimmer moves from top to bottom 22 | - **RIGHT_TO_LEFT**: Shimmer moves from right to left 23 | - **BOTTOM_TO_TOP**: Shimmer moves from bottom to top 24 | 25 | - 🛠 **Customizable Shape** 26 | Choose the shimmer effect that best suits your UI: 27 | - **LINEAR**: A simple, linear shimmer effect 28 | - **RADIAL**: A circular, radial shimmer effect 29 | 30 | - 🔥 **Customizable Intensity** 31 | Adjust the shimmer intensity to create the desired visual impact that matches your app’s style. 32 | 33 | - ✨ **Customizable Fadeout** 34 | Smoothly control the fadeout effect of the shimmer animation for seamless transitions. 35 | 36 | - 🔁 **Customizable Repeat Count** 37 | Define how many times the shimmer animation will repeat, giving you full control over the effect duration. 38 | 39 | - ⏳ **Customizable Animation Duration** 40 | Set the duration of each shimmer cycle, allowing you to control how long the effect lasts. 41 | 42 | - 🕒 **Customizable Repeat Delay** 43 | Manage the time interval between consecutive shimmer cycles for a more natural animation flow. 44 | 45 | - 🚀 **Customizable Start Delay** 46 | Set a delay before the shimmer animation begins to create a more dynamic timing sequence. 47 | 48 | ## 🛠 Installation 49 | **Step 1.** Add it in your root setting.gradle at the end of repositories: 50 | ```kotlin 51 | dependencyResolutionManagement { 52 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 53 | repositories { 54 | mavenCentral() 55 | 56 | // Include JitPack, a repository that allows you to fetch dependencies from GitHub projects. 57 | maven { url 'https://jitpack.io' } 58 | } 59 | } 60 | ``` 61 | **Step 2.** Add the dependency 62 | 63 | ```kotlin 64 | // This dependencies block is for the build.gradle 65 | dependencies { 66 | implementation 'com.github.zaminalirustamov:Shimmer_Animation:1.0.2' 67 | } 68 | ``` 69 | ## 🚀 Usage 70 | ### Example 71 | 72 | ```xml 73 | 74 | 82 | 83 | 86 | 87 | 91 | 92 | 93 | 94 | ``` 95 | 96 | ```xml 97 | 98 | 106 | 107 | 110 | 111 | 116 | 117 | 123 | 124 | 130 | 131 | 137 | 138 | 139 | 140 | 141 | ``` 142 | 143 | For more examples, check out the code 144 | 145 | ## 🤝 Contribution 146 | I highly appreciate and welcome any issue reports, feature requests, pull requests, or GitHub stars you may provide. 147 | 148 | Buy Me A Coffee 149 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/ShimmerFrameLayout.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.graphics.drawable.Drawable 7 | import android.util.AttributeSet 8 | import android.view.View 9 | import android.widget.FrameLayout 10 | import az.lahza.shimmer_animation.R 11 | import az.lahza.shimmer_animation.shimmer.builders.AlphaHighlightBuilder 12 | import az.lahza.shimmer_animation.shimmer.builders.ColorHighlightBuilder 13 | 14 | /** 15 | * A custom FrameLayout that displays a shimmer effect over its content. 16 | * The shimmer effect can be customized using XML attributes. 17 | */ 18 | class ShimmerFrameLayout : FrameLayout { 19 | 20 | // Paint object for content rendering 21 | private val mContentPaint = Paint() 22 | 23 | // Shimmer drawable that applies the shimmer effect 24 | private val mShimmerDrawable: ShimmerDrawable = ShimmerDrawable() 25 | 26 | // Flag to track if shimmer is visible 27 | private var isShimmerVisible = true 28 | 29 | // Flag to track if shimmer should be stopped because of visibility change 30 | private var mStoppedShimmerBecauseVisibility = false 31 | 32 | constructor(context: Context) : super(context) { 33 | init(context, null) 34 | } 35 | 36 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 37 | init(context, attrs) 38 | } 39 | 40 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 41 | context, 42 | attrs, 43 | defStyleAttr 44 | ) { 45 | init(context, attrs) 46 | } 47 | 48 | /** 49 | * Initializes the shimmer effect based on XML attributes. 50 | * If no attributes are provided, the default AlphaHighlightBuilder is used. 51 | */ 52 | private fun init(context: Context, attrs: AttributeSet?) { 53 | setWillNotDraw(false) // Allow drawing the shimmer effect 54 | mShimmerDrawable.callback = this // Set the callback for drawing the shimmer effect 55 | 56 | // Set default shimmer if no attributes are provided 57 | if (attrs == null) { 58 | setShimmer(AlphaHighlightBuilder().build()) 59 | return 60 | } 61 | 62 | // Load shimmer attributes from XML 63 | val typedArray = context.obtainStyledAttributes( 64 | /* set = */ attrs, 65 | /* attrs = */ R.styleable.ShimmerFrameLayout, 66 | /* defStyleAttr = */ 0, 67 | /* defStyleRes = */ 0 68 | ) 69 | try { 70 | val shimmerBuilder = 71 | if (typedArray.hasValue(R.styleable.ShimmerFrameLayout_shimmer_colored) 72 | && typedArray.getBoolean(R.styleable.ShimmerFrameLayout_shimmer_colored, false) 73 | ) ColorHighlightBuilder() else AlphaHighlightBuilder() 74 | 75 | // Set shimmer properties from XML attributes 76 | setShimmer(shimmerBuilder.loadAttributes(typedArray)!!.build()) 77 | } finally { 78 | typedArray.recycle() 79 | } 80 | } 81 | 82 | /** 83 | * Sets the shimmer effect to be displayed. 84 | * If the shimmer requires clipping to children, set hardware acceleration. 85 | */ 86 | private fun setShimmer(shimmer: Shimmer?): ShimmerFrameLayout { 87 | mShimmerDrawable.shimmer = shimmer 88 | if (shimmer != null && shimmer.clipToChildren) { 89 | setLayerType(LAYER_TYPE_HARDWARE, mContentPaint) 90 | } else { 91 | setLayerType(LAYER_TYPE_NONE, null) 92 | } 93 | return this 94 | } 95 | 96 | /** 97 | * Stops the shimmer animation. 98 | */ 99 | private fun stopShimmer() { 100 | mStoppedShimmerBecauseVisibility = false 101 | mShimmerDrawable.stopShimmer() 102 | } 103 | 104 | /** 105 | * Checks if the shimmer animation has started. 106 | */ 107 | private val isShimmerStarted: Boolean 108 | get() = mShimmerDrawable.isShimmerStarted 109 | 110 | /** 111 | * Called when the layout bounds change. Updates the shimmer bounds accordingly. 112 | */ 113 | public override fun onLayout( 114 | changed: Boolean, 115 | left: Int, 116 | top: Int, 117 | right: Int, 118 | bottom: Int 119 | ) { 120 | super.onLayout(changed, left, top, right, bottom) 121 | val width = width 122 | val height = height 123 | mShimmerDrawable.setBounds(0, 0, width, height) 124 | } 125 | 126 | /** 127 | * Handles visibility changes. Stops the shimmer if visibility is not VISIBLE, 128 | * and restarts it if visibility is restored. 129 | */ 130 | override fun onVisibilityChanged(changedView: View, visibility: Int) { 131 | super.onVisibilityChanged(changedView, visibility) 132 | 133 | if (visibility != VISIBLE) { 134 | if (isShimmerStarted) { 135 | stopShimmer() 136 | mStoppedShimmerBecauseVisibility = true 137 | } 138 | } else if (mStoppedShimmerBecauseVisibility) { 139 | mShimmerDrawable.maybeStartShimmer() 140 | mStoppedShimmerBecauseVisibility = false 141 | } 142 | } 143 | 144 | /** 145 | * Called when the view is attached to the window. Starts the shimmer animation if needed. 146 | */ 147 | public override fun onAttachedToWindow() { 148 | super.onAttachedToWindow() 149 | mShimmerDrawable.maybeStartShimmer() 150 | } 151 | 152 | /** 153 | * Called when the view is detached from the window. Stops the shimmer animation. 154 | */ 155 | public override fun onDetachedFromWindow() { 156 | super.onDetachedFromWindow() 157 | stopShimmer() 158 | } 159 | 160 | /** 161 | * Draws the content of the layout along with the shimmer effect. 162 | */ 163 | public override fun dispatchDraw(canvas: Canvas) { 164 | super.dispatchDraw(canvas) 165 | if (isShimmerVisible) { 166 | mShimmerDrawable.draw(canvas) 167 | } 168 | } 169 | 170 | /** 171 | * Verifies if the drawable is the shimmer drawable. 172 | */ 173 | override fun verifyDrawable(who: Drawable) = 174 | super.verifyDrawable(who) || who === mShimmerDrawable 175 | } 176 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/builders/Builder.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer.builders 2 | 3 | import android.content.Context 4 | import android.content.res.TypedArray 5 | import android.util.AttributeSet 6 | import androidx.annotation.ColorInt 7 | import androidx.annotation.FloatRange 8 | import az.lahza.shimmer_animation.R 9 | import az.lahza.shimmer_animation.shimmer.Shimmer 10 | import az.lahza.shimmer_animation.shimmer.annotations.Direction 11 | import az.lahza.shimmer_animation.shimmer.annotations.Shape 12 | import az.lahza.shimmer_animation.shimmer.extensions.getBooleanOrNull 13 | import az.lahza.shimmer_animation.shimmer.extensions.getFloatOrNull 14 | import az.lahza.shimmer_animation.shimmer.extensions.getIntOrNull 15 | 16 | abstract class Builder?> { 17 | 18 | protected val shimmer = Shimmer() 19 | 20 | protected abstract val self: T 21 | 22 | /** 23 | * Builds the final Shimmer instance with applied attributes. 24 | */ 25 | fun build() = shimmer.apply { 26 | updateColors() 27 | updatePositions() 28 | } 29 | 30 | /** 31 | * Loads custom attributes for a view from XML. 32 | * 33 | * This method retrieves the custom attributes defined in the XML layout 34 | * and applies them to the shimmer effect configuration. 35 | * 36 | * @param context The context used to obtain styled attributes. 37 | * @param attrs The attribute set from the XML layout. 38 | * @return The builder instance for method chaining. 39 | */ 40 | fun loadAttributes(context: Context, attrs: AttributeSet?): T { 41 | context.obtainStyledAttributes( 42 | attrs, R.styleable.ShimmerFrameLayout 43 | ).use { typedArray -> 44 | loadAttributes(typedArray) 45 | } 46 | return self 47 | } 48 | 49 | /** 50 | * Loads custom attributes from a TypedArray and applies them to the shimmer object. 51 | * 52 | * @param typedArray The TypedArray containing the custom attributes. 53 | * @return The builder instance for method chaining. 54 | */ 55 | open fun loadAttributes(typedArray: TypedArray): T { 56 | typedArray.apply { 57 | getBooleanOrNull(R.styleable.ShimmerFrameLayout_shimmer_clip_to_children)?.let { shimmer.clipToChildren = it } 58 | getBooleanOrNull(R.styleable.ShimmerFrameLayout_shimmer_auto_start)?.let { shimmer.autoStart = it } 59 | getFloatOrNull(R.styleable.ShimmerFrameLayout_shimmer_base_alpha)?.let { setBaseAlpha(it) } 60 | getFloatOrNull(R.styleable.ShimmerFrameLayout_shimmer_highlight_alpha)?.let { setHighlightAlpha(it) } 61 | getFloatOrNull(R.styleable.ShimmerFrameLayout_shimmer_intensity)?.let { setIntensity(it) } 62 | getFloatOrNull(R.styleable.ShimmerFrameLayout_shimmer_width_ratio)?.let { setWidthRatio(it) } 63 | getFloatOrNull(R.styleable.ShimmerFrameLayout_shimmer_height_ratio)?.let { setHeightRatio(it) } 64 | getFloatOrNull(R.styleable.ShimmerFrameLayout_shimmer_dropoff)?.let { setDropOff(it) } 65 | getFloatOrNull(R.styleable.ShimmerFrameLayout_shimmer_tilt)?.let { shimmer.tilt = it } 66 | getIntOrNull(R.styleable.ShimmerFrameLayout_shimmer_direction)?.let { setDirection(it) } 67 | getIntOrNull(R.styleable.ShimmerFrameLayout_shimmer_shape)?.let { setShape(it) } 68 | getIntOrNull(R.styleable.ShimmerFrameLayout_shimmer_duration)?.let { shimmer.animationDuration = it.toLong() } 69 | getIntOrNull(R.styleable.ShimmerFrameLayout_shimmer_repeat_count)?.let { shimmer.repeatCount = it } 70 | getIntOrNull(R.styleable.ShimmerFrameLayout_shimmer_repeat_delay)?.let { shimmer.repeatDelay = it.toLong() } 71 | getIntOrNull(R.styleable.ShimmerFrameLayout_shimmer_start_delay)?.let { shimmer.startDelay = it.toLong() } 72 | } 73 | return self 74 | } 75 | 76 | // =========================== 77 | // Setter Methods for Shimmer Properties 78 | // =========================== 79 | 80 | /** 81 | * Sets the base alpha value for the shimmer effect. 82 | */ 83 | private fun setBaseAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) = apply { 84 | shimmer.baseColor = adjustAlpha(shimmer.baseColor, alpha) 85 | } 86 | 87 | /** 88 | * Sets the highlight alpha value for the shimmer effect. 89 | */ 90 | private fun setHighlightAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) = apply { 91 | shimmer.highlightColor = adjustAlpha(shimmer.highlightColor, alpha) 92 | } 93 | 94 | /** 95 | * Adjusts the alpha value of a color by the given factor. 96 | * The factor should be between 0.0 and 1.0. 97 | */ 98 | private fun adjustAlpha(@ColorInt color: Int, factor: Float): Int { 99 | val alpha = (255 * factor).toInt() 100 | return (color and 0x00FFFFFF) or (alpha shl 24) 101 | } 102 | 103 | /** 104 | * Sets the intensity for the shimmer effect. 105 | * Intensity controls how strong the shimmer effect appears. 106 | * 107 | * @param intensity The intensity value, typically between 0.0 and 1.0. 108 | */ 109 | private fun setIntensity(intensity: Float) = apply { 110 | shimmer.intensity = intensity 111 | } 112 | 113 | /** 114 | * Sets the width ratio for the shimmer effect. 115 | * Width ratio controls how wide the shimmer effect appears relative to the view's width. 116 | * 117 | * @param widthRatio The width ratio, typically between 0.0 and 1.0. 118 | */ 119 | private fun setWidthRatio(widthRatio: Float) = apply { 120 | shimmer.widthRatio = widthRatio 121 | } 122 | 123 | /** 124 | * Sets the height ratio for the shimmer effect. 125 | * Height ratio controls how tall the shimmer effect appears relative to the view's height. 126 | * 127 | * @param heightRatio The height ratio, typically between 0.0 and 1.0. 128 | */ 129 | private fun setHeightRatio(heightRatio: Float) = apply { 130 | shimmer.heightRatio = heightRatio 131 | } 132 | 133 | /** 134 | * Sets the dropOff value for the shimmer effect. 135 | * DropOff controls how quickly the shimmer effect fades off. 136 | * 137 | * @param dropOff The dropOff value, typically between 0.0 and 1.0. 138 | */ 139 | private fun setDropOff(dropOff: Float) = apply { 140 | shimmer.dropoff = dropOff 141 | } 142 | 143 | /** 144 | * Sets the direction of the shimmer effect. 145 | * Direction defines in which direction the shimmer animation occurs. 146 | * 147 | * @param direction One of the predefined direction constants from the [Direction] annotation. 148 | */ 149 | private fun setDirection(@Direction direction: Int) = apply { 150 | shimmer.direction = direction 151 | } 152 | 153 | /** 154 | * Sets the shape of the shimmer effect. 155 | * Shape defines the pattern or geometry of the shimmer effect. 156 | * 157 | * @param shape One of the predefined shape constants from the [Shape] annotation. 158 | */ 159 | private fun setShape(@Shape shape: Int) = apply { 160 | shimmer.shape = shape 161 | } 162 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 19 | 20 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 42 | 43 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | 64 | 65 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 85 | 86 | 89 | 90 | 94 | 95 | 96 | 97 | 98 | 99 | 106 | 107 | 110 | 111 | 115 | 116 | 117 | 118 | 119 | 120 | 128 | 129 | 132 | 133 | 137 | 138 | 139 | 140 | 141 | 142 | 150 | 151 | 154 | 155 | 160 | 161 | 167 | 168 | 174 | 175 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /shimmer_animation/src/main/java/az/lahza/shimmer_animation/shimmer/ShimmerDrawable.kt: -------------------------------------------------------------------------------- 1 | package az.lahza.shimmer_animation.shimmer 2 | 3 | import android.animation.ValueAnimator 4 | import android.animation.ValueAnimator.AnimatorUpdateListener 5 | import android.graphics.Canvas 6 | import android.graphics.ColorFilter 7 | import android.graphics.LinearGradient 8 | import android.graphics.Matrix 9 | import android.graphics.Paint 10 | import android.graphics.PixelFormat 11 | import android.graphics.PorterDuff 12 | import android.graphics.PorterDuffXfermode 13 | import android.graphics.RadialGradient 14 | import android.graphics.Rect 15 | import android.graphics.Shader 16 | import android.graphics.drawable.Drawable 17 | import android.view.animation.LinearInterpolator 18 | import az.lahza.shimmer_animation.shimmer.annotations.Direction 19 | import az.lahza.shimmer_animation.shimmer.annotations.Shape 20 | import kotlin.math.sqrt 21 | import kotlin.math.tan 22 | 23 | /** 24 | * A drawable used to display a shimmer effect on a canvas. 25 | * 26 | * This drawable applies a shimmer effect over a view by animating a gradient 27 | * across the surface. The shimmer can be configured with various properties 28 | * such as direction, shape, tilt, intensity, and more. 29 | */ 30 | class ShimmerDrawable : Drawable() { 31 | 32 | // Animator listener to trigger a redraw on each animation frame 33 | private val mUpdateListener = AnimatorUpdateListener { invalidateSelf() } 34 | 35 | // Paint object used to draw the shimmer 36 | private val mShimmerPaint = Paint().apply { isAntiAlias = true } 37 | 38 | // Rect object to define the bounds for drawing 39 | private val mDrawRect = Rect() 40 | 41 | // Matrix for manipulating the shader 42 | private val mShaderMatrix = Matrix() 43 | 44 | // ValueAnimator for controlling the shimmer animation 45 | private var mValueAnimator: ValueAnimator? = null 46 | 47 | // Used for static progress if animation is not running 48 | private var mStaticAnimationProgress = -1f 49 | 50 | // Holds the shimmer configuration 51 | private var mShimmer: Shimmer? = null 52 | 53 | /** 54 | * The shimmer configuration that defines the behavior of the shimmer effect. 55 | * Setting this property triggers the shimmer update process. 56 | */ 57 | var shimmer: Shimmer? 58 | get() = mShimmer 59 | set(value) { 60 | mShimmer = value 61 | mShimmer?.let { 62 | mShimmerPaint.xfermode = PorterDuffXfermode( 63 | if (it.alphaShimmer) PorterDuff.Mode.DST_IN else PorterDuff.Mode.SRC_IN 64 | ) 65 | updateShader() 66 | updateValueAnimator() 67 | invalidateSelf() 68 | } 69 | } 70 | 71 | /** 72 | * Indicates whether the shimmer animation has started. 73 | */ 74 | val isShimmerStarted: Boolean 75 | get() = mValueAnimator?.isStarted == true 76 | 77 | /** 78 | * Stops the shimmer animation if it is currently running. 79 | */ 80 | fun stopShimmer() { 81 | if (mValueAnimator != null && isShimmerStarted) { 82 | mValueAnimator!!.cancel() 83 | } 84 | } 85 | 86 | /** 87 | * Called when the bounds of the drawable change. 88 | * This updates the drawing rectangle and shader, and potentially starts the shimmer animation. 89 | */ 90 | public override fun onBoundsChange(bounds: Rect) { 91 | super.onBoundsChange(bounds) 92 | mDrawRect.set(bounds) 93 | updateShader() 94 | maybeStartShimmer() 95 | } 96 | 97 | /** 98 | * Draws the shimmer effect on the canvas. The shimmer is animated by applying a gradient 99 | * shader and translating it according to the animation progress. 100 | * 101 | * @param canvas The canvas on which to draw the shimmer effect. 102 | */ 103 | override fun draw(canvas: Canvas) { 104 | val shimmer = mShimmer ?: return 105 | val shader = mShimmerPaint.shader ?: return 106 | 107 | // Calculate the tilt factor based on the shimmer tilt angle 108 | val tiltTan = tan(Math.toRadians(shimmer.tilt.toDouble())).toFloat() 109 | 110 | // Calculate translation values based on the shimmer tilt and bounds 111 | val translateHeight = mDrawRect.height() + tiltTan * mDrawRect.width() 112 | val translateWidth = mDrawRect.width() + tiltTan * mDrawRect.height() 113 | 114 | // Determine the animation progress (either static or from the animator) 115 | val animatedValue = 116 | mStaticAnimationProgress.takeIf { it >= 0f } ?: (mValueAnimator?.animatedValue as? Float 117 | ?: 0f) 118 | 119 | // Calculate the translation offsets based on the shimmer direction 120 | val (dx, dy) = calculateTranslation( 121 | shimmer.direction, translateWidth, translateHeight, animatedValue 122 | ) 123 | 124 | // Reset and update the shader matrix for applying translation and rotation 125 | mShaderMatrix.reset() 126 | mShaderMatrix.setRotate(shimmer.tilt, mDrawRect.width() / 2f, mDrawRect.height() / 2f) 127 | mShaderMatrix.preTranslate(dx, dy) 128 | 129 | // Apply the transformation to the shader and draw the shimmer effect 130 | shader.setLocalMatrix(mShaderMatrix) 131 | canvas.drawRect(mDrawRect, mShimmerPaint) 132 | } 133 | 134 | 135 | /** 136 | * Calculates the translation offsets (dx, dy) based on the shimmer direction. 137 | * 138 | * @param direction The direction of the shimmer effect. 139 | * @param translateWidth The width translation value. 140 | * @param translateHeight The height translation value. 141 | * @param animatedValue The current progress of the animation. 142 | * @return A pair of translation values (dx, dy). 143 | */ 144 | private fun calculateTranslation( 145 | direction: Int, translateWidth: Float, translateHeight: Float, animatedValue: Float 146 | ) = when (direction) { 147 | Direction.LEFT_TO_RIGHT -> offset(-translateWidth, translateWidth, animatedValue) to 0f 148 | Direction.RIGHT_TO_LEFT -> offset(translateWidth, -translateWidth, animatedValue) to 0f 149 | Direction.TOP_TO_BOTTOM -> 0f to offset(-translateHeight, translateHeight, animatedValue) 150 | Direction.BOTTOM_TO_TOP -> 0f to offset(translateHeight, -translateHeight, animatedValue) 151 | else -> offset(-translateWidth, translateWidth, animatedValue) to 0f 152 | } 153 | 154 | 155 | override fun setAlpha(alpha: Int) {} 156 | 157 | override fun setColorFilter(colorFilter: ColorFilter?) {} 158 | 159 | @Deprecated("Deprecated in Java") 160 | override fun getOpacity(): Int { 161 | return if (mShimmer?.clipToChildren == true || mShimmer?.alphaShimmer == true) { 162 | PixelFormat.TRANSLUCENT 163 | } else { 164 | PixelFormat.OPAQUE 165 | } 166 | } 167 | 168 | /** 169 | * Adjusts the translation offset based on the animation progress. 170 | * 171 | * @param start The starting translation value. 172 | * @param end The ending translation value. 173 | * @param percent The progress of the animation (0f to 1f). 174 | * @return The calculated offset. 175 | */ 176 | private fun offset(start: Float, end: Float, percent: Float) = start + (end - start) * percent 177 | 178 | /** 179 | * Updates the ValueAnimator for the shimmer animation based on the current shimmer properties. 180 | * Cancels the existing animator (if any) and creates a new one with the correct properties. 181 | */ 182 | private fun updateValueAnimator() { 183 | // Ensure shimmer is not null 184 | mShimmer?.let { shimmer -> 185 | // Cancel and reset the existing animator, if it exists 186 | mValueAnimator?.cancel() 187 | mValueAnimator?.removeAllUpdateListeners() 188 | 189 | // Create a new ValueAnimator with the correct properties 190 | mValueAnimator = ValueAnimator.ofFloat( 191 | 0f, 1f + (shimmer.repeatDelay / shimmer.animationDuration).toFloat() 192 | ).apply { 193 | // Set the interpolator for the animation 194 | interpolator = LinearInterpolator() 195 | 196 | // Set repeat mode, count, and duration based on shimmer properties 197 | repeatMode = shimmer.repeatMode 198 | startDelay = shimmer.startDelay 199 | repeatCount = shimmer.repeatCount 200 | duration = shimmer.animationDuration + shimmer.repeatDelay 201 | 202 | // Add an update listener to trigger invalidation 203 | addUpdateListener(mUpdateListener) 204 | 205 | // Start the animation 206 | start() 207 | } 208 | } 209 | } 210 | 211 | /** 212 | * Starts the shimmer animation if it is not already started and the conditions are met. 213 | */ 214 | fun maybeStartShimmer() { 215 | // Ensure the shimmer and animator are valid, and autoStart is true 216 | if (mValueAnimator?.isStarted == true || mShimmer?.autoStart == false || callback == null) return 217 | 218 | // Start the shimmer animation if the conditions are satisfied 219 | mValueAnimator?.start() 220 | } 221 | 222 | 223 | /** 224 | * Updates the shader used for drawing the shimmer effect. 225 | * This method creates the shader based on the shimmer properties. 226 | */ 227 | private fun updateShader() { 228 | // Early return if shimmer is null or bounds are invalid 229 | val shimmer = mShimmer ?: return 230 | val bounds = bounds 231 | if (bounds.width() == 0 || bounds.height() == 0) return 232 | 233 | // Calculate the width and height for the shimmer effect 234 | val width = shimmer.width(bounds.width()) 235 | val height = shimmer.height(bounds.height()) 236 | 237 | // Create the appropriate shader based on shimmer properties 238 | val shader = createShader(shimmer, width, height) 239 | 240 | // Apply the shader to the paint object 241 | mShimmerPaint.shader = shader 242 | } 243 | 244 | /** 245 | * Creates the shader for the shimmer effect based on the current shimmer properties. 246 | * 247 | * @param width The width of the shimmer effect. 248 | * @param height The height of the shimmer effect. 249 | * @return The shader to be used for the shimmer effect. 250 | */ 251 | private fun createShader(shimmer: Shimmer, width: Int, height: Int): Shader { 252 | return when (shimmer.shape) { 253 | Shape.LINEAR -> createLinearGradient(shimmer, width, height) 254 | Shape.RADIAL -> createRadialGradient(shimmer, width, height) 255 | else -> createLinearGradient(shimmer, width, height) // Default to linear gradient 256 | } 257 | } 258 | 259 | /** 260 | * Creates a linear gradient shader for the shimmer effect based on the shimmer direction. 261 | * 262 | * @param shimmer The shimmer configuration containing direction, colors, and positions. 263 | * @param width The width of the shimmer effect (view width). 264 | * @param height The height of the shimmer effect (view height). 265 | * @return The linear gradient shader based on the shimmer's direction and properties. 266 | */ 267 | private fun createLinearGradient(shimmer: Shimmer, width: Int, height: Int): LinearGradient { 268 | // Determine whether the shimmer direction is vertical or horizontal 269 | val isVertical = 270 | shimmer.direction == Direction.TOP_TO_BOTTOM || shimmer.direction == Direction.BOTTOM_TO_TOP 271 | 272 | // Calculate the end coordinates for the gradient based on the direction 273 | val endX = if (isVertical) 0 else width 274 | val endY = if (isVertical) height else 0 275 | 276 | // Return the linear gradient shader with the calculated values 277 | return LinearGradient( 278 | 0f, // Start at the top-left corner (0,0) 279 | 0f, // Start at the top-left corner (0,0) 280 | endX.toFloat(), endY.toFloat(), shimmer.colors, // Color stops 281 | shimmer.positions, // Gradient positions 282 | Shader.TileMode.CLAMP // Prevents the gradient from repeating 283 | ) 284 | } 285 | 286 | 287 | /** 288 | * Creates a radial gradient shader for the shimmer effect. 289 | * 290 | * @param shimmer The shimmer configuration. 291 | * @param width The width of the shimmer effect. 292 | * @param height The height of the shimmer effect. 293 | * @return The radial gradient shader. 294 | */ 295 | private fun createRadialGradient(shimmer: Shimmer, width: Int, height: Int) = 296 | RadialGradient( 297 | width / 2f, 298 | height / 2f, 299 | (width.coerceAtLeast(height) / sqrt(2.0)).toFloat(), 300 | shimmer.colors, 301 | shimmer.positions, 302 | Shader.TileMode.CLAMP 303 | ) 304 | } -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 328 | 329 | --------------------------------------------------------------------------------