diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1ae289a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+/.gradle/5.4.1/javaCompile/taskHistory.bin
+
+/keystore.properties
+/.idea
+/buildSrc/buildSrc.iml
+/app/build
+/app/.idea
+gradlew.bat
+gradlew
+/app/gradle
+/app/local.properties
+misc.xml
+/buildSrc/build
+/vcs.xml
+/app/release
+/app/staging
+/buildConfigKeys.properties
+/app/schemas
+*.jks
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4ac06bd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Mindinventory
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..49392b3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,89 @@
+ShimmerTextView
+====
+
+ShimmerTextView is a simple library to integrate shimmer effect in your TextView.
+
+![image](art/ShimmerTextView.gif)
+![image](art/ShimmerTextViewOffer.gif)
+
+# Key features
+
+* Set a base color in ShimmerTextView.
+* Set a highlight color in ShimmerTextView.
+* Set animation duration for shimmer effect(in millisecond).
+* Set animation direction(left_to_right, top_to_bottom, right_to_left, bottom_to_top).
+* Set ShimmerTextView shape(Linear/Radial)
+
+# Usage
+
+**Dependencies**
+> Insert gradle dependency here.
+
+**Implementation**
+
+* Step 1 : Use custom ShimmerTextView in XML.
+
+
+
+* Step 2 : Use all attributes dynamically in your.
+
+ class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ val textView = findViewById(R.id.textView)
+ textView.setBaseColor(ContextCompat.getColor(this, R.color.dark_red))
+ .setHighLightColor(ContextCompat.getColor(this, R.color.orange))
+ .setDirection(Shimmer.Direction.LEFT_TO_RIGHT)
+ .build()
+ textView.startShimmer()
+ }
+ }
+
+**XML Properties**
+
+| Properties | Description |
+|------------------------|-------------------------------------------|
+|shimmer_base_color |Set base color of ShimmerTextView |
+|Shimmer_highlight_color |Set highlight color of shimmer animation |
+|shimmer_colored |Set it to true for colored ShimmerTextView |
+|shimmer_duration |Set duration for animation |
+|shimmer_direction |Set animation direction(left_to_right,top_to_bottom, right_to_left, bottom_to_top)|
+
+That's it ๐ and you're good to go ๐
+
+### Guideline to report an issue/feature request
+---------
+It would be very helpful for us, if the reporter can share the below things to understand the root cause of the issue.
+
+* Library version.
+* Code snippet.
+* Logs if applicable.
+* Screenshot/video with steps to reproduce the issue.
+
+### LICENCE
+----------------
+ShimmerTextView is [MIT-licensed.](https://git.mindinventory.com/mi-android/android-libs/shimmertextview/-/blob/master/LICENSE)
+
+### Let us know!
+---------
+If you use open-source libraries in your project, please make sure to credit us and Give a star to [www.mindinventory.com](https://www.mindinventory.com/)
+
+Please feel free to use this component and let us know if you are interested to building Apps or Designing Products.
diff --git a/ShimmerTextView/.gitignore b/ShimmerTextView/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/ShimmerTextView/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/ShimmerTextView/build.gradle b/ShimmerTextView/build.gradle
new file mode 100644
index 0000000..f81bee3
--- /dev/null
+++ b/ShimmerTextView/build.gradle
@@ -0,0 +1,40 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ minSdk 22
+ targetSdk 32
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.8.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.material:material:1.6.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/ShimmerTextView/consumer-rules.pro b/ShimmerTextView/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/ShimmerTextView/proguard-rules.pro b/ShimmerTextView/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/ShimmerTextView/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/ShimmerTextView/src/androidTest/java/com/app/shimmertextview/ExampleInstrumentedTest.kt b/ShimmerTextView/src/androidTest/java/com/app/shimmertextview/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..6f7ba7a
--- /dev/null
+++ b/ShimmerTextView/src/androidTest/java/com/app/shimmertextview/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.app.shimmertextview
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.app.shimmertextview.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/ShimmerTextView/src/main/AndroidManifest.xml b/ShimmerTextView/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9ce32ff
--- /dev/null
+++ b/ShimmerTextView/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/ShimmerTextView/src/main/java/com/app/shimmertextview/Shimmer.kt b/ShimmerTextView/src/main/java/com/app/shimmertextview/Shimmer.kt
new file mode 100644
index 0000000..cd02cad
--- /dev/null
+++ b/ShimmerTextView/src/main/java/com/app/shimmertextview/Shimmer.kt
@@ -0,0 +1,450 @@
+package com.app.shimmertextview
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.TypedArray
+import android.graphics.Color
+import android.graphics.RectF
+import android.util.AttributeSet
+import androidx.annotation.ColorInt
+import androidx.annotation.FloatRange
+import androidx.annotation.IntDef
+import androidx.annotation.Px
+import kotlin.math.roundToInt
+import kotlin.math.sin
+
+
+class Shimmer {
+ companion object {
+ private const val COMPONENT_COUNT = 4
+ }
+
+ val positions: FloatArray = FloatArray(COMPONENT_COUNT)
+ val colors: IntArray = IntArray(COMPONENT_COUNT)
+ private val bounds = RectF()
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(Shape.LINEAR, Shape.RADIAL)
+ annotation class Shape {
+ companion object {
+ const val LINEAR = 0
+ const val RADIAL = 1
+ }
+ }
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(Direction.LEFT_TO_RIGHT,
+ Direction.TOP_TO_BOTTOM,
+ Direction.RIGHT_TO_LEFT,
+ Direction.BOTTOM_TO_TOP)
+ annotation class Direction {
+ companion object {
+ const val LEFT_TO_RIGHT = 0
+ const val TOP_TO_BOTTOM = 1
+ const val RIGHT_TO_LEFT = 2
+ const val BOTTOM_TO_TOP = 3
+ }
+ }
+
+ @Direction
+ var direction = Direction.LEFT_TO_RIGHT
+
+ @ColorInt
+ var highLightColor = Color.WHITE
+
+ @ColorInt
+ var baseColor = 0x4Cffffff
+
+ @Shape
+ var shape = Shape.LINEAR
+ var fixedWidth = 0
+ var fixedHeight = 0
+
+ var widthRatio = 1F
+ var heightRatio = 1F
+ var intensity = 0F
+ var dropOff = 0.5F
+ var tilt = 20F
+ var isColored = false
+
+ var clipToChildren = true
+ var autoStart = true
+ var alphaShimmer = true
+
+ var repeatCount = ValueAnimator.INFINITE
+ var repeatMode = ValueAnimator.RESTART
+ var animationDuration = 1000L
+ var repeatDelay: Long = 0
+ var startDelay: Long = 0
+
+ fun width(width: Int): Int {
+ return if (fixedWidth > 0) fixedWidth else (widthRatio * width).roundToInt()
+ }
+
+ fun height(height: Int): Int {
+ return if (fixedHeight > 0) fixedHeight else (heightRatio * height).roundToInt()
+ }
+
+ private fun updateColor() {
+ when (shape) {
+ Shape.LINEAR -> {
+ colors[0] = baseColor
+ colors[1] = highLightColor
+ colors[2] = highLightColor
+ colors[3] = baseColor
+ }
+ Shape.RADIAL -> {
+ colors[0] = highLightColor
+ colors[1] = highLightColor
+ colors[2] = baseColor
+ colors[3] = baseColor
+ }
+ }
+ }
+
+ private fun updatePosition() {
+ when (shape) {
+ Shape.LINEAR -> {
+ positions[0] = ((1F - intensity - dropOff) / 2F).coerceAtLeast(0F)
+ positions[1] = ((1F - intensity - 0.001F) / 2F).coerceAtLeast(0F)
+ positions[2] = ((1F + intensity + 0.001F) / 2F).coerceAtMost(1F)
+ positions[3] = ((1F + intensity + dropOff) / 2F).coerceAtMost(1F)
+ }
+ Shape.RADIAL -> {
+ positions[0] = 0F
+ positions[1] = intensity.coerceAtMost(1F)
+ positions[2] = (intensity + dropOff).coerceAtMost(1F)
+ positions[3] = 1F
+ }
+ }
+ }
+
+ private fun updateBounds(viewWidth: Int, viewHeight: Int) {
+ val magnitude = viewWidth.coerceAtLeast(viewHeight)
+ val rad = Math.PI / 2F - Math.toRadians((tilt % 90F).toDouble())
+ val hyp = magnitude / sin(rad)
+ val padding = 3 * ((hyp - magnitude) / 2F).roundToInt()
+ bounds.set(-padding.toFloat(),
+ -padding.toFloat(),
+ (width(viewWidth) + padding).toFloat(),
+ (height(viewHeight) + padding).toFloat())
+ }
+
+ abstract class Builder> {
+ val shimmer = Shimmer()
+
+ protected abstract fun getThis(): T
+
+ fun consumeAttributes(context: Context, attrs: AttributeSet): T {
+ val typedArray =
+ context.obtainStyledAttributes(attrs, R.styleable.ShimmerTextView, 0, 0)
+ return consumeAttributes(typedArray)
+ }
+
+ open fun consumeAttributes(typedArray: TypedArray): T {
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_clip_to_children)) {
+ setClipToChildren(typedArray.getBoolean(R.styleable.ShimmerTextView_shimmer_clip_to_children,
+ shimmer.clipToChildren))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_auto_start)) {
+ setAutoStart(typedArray.getBoolean(R.styleable.ShimmerTextView_shimmer_auto_start,
+ shimmer.autoStart))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_base_alpha)) {
+ setBaseAlpha(typedArray.getFloat(R.styleable.ShimmerTextView_shimmer_base_alpha,
+ 0.3F))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_highlight_alpha)) {
+ setHighlightAlpha(typedArray.getFloat(R.styleable.ShimmerTextView_shimmer_highlight_alpha,
+ 1F))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_duration)) {
+ setDuration(typedArray.getInt(R.styleable.ShimmerTextView_shimmer_duration,
+ shimmer.animationDuration.toInt()).toLong())
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_repeat_count)) {
+ setRepeatCount(typedArray.getInt(R.styleable.ShimmerTextView_shimmer_repeat_count,
+ shimmer.repeatCount))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_repeat_delay)) {
+ setRepeatDelay(typedArray.getInt(R.styleable.ShimmerTextView_shimmer_repeat_delay,
+ shimmer.repeatDelay.toInt()).toLong())
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_repeat_mode)) {
+ setRepeatMode(typedArray.getInt(R.styleable.ShimmerTextView_shimmer_repeat_mode,
+ shimmer.repeatMode))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_start_delay)) {
+ setStartDelay(typedArray.getInt(R.styleable.ShimmerTextView_shimmer_start_delay,
+ shimmer.startDelay.toInt()).toLong())
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_direction)) {
+ when (typedArray.getInt(R.styleable.ShimmerTextView_shimmer_direction,
+ shimmer.direction)) {
+ Direction.LEFT_TO_RIGHT -> {
+ setDirection(Direction.LEFT_TO_RIGHT)
+ }
+ Direction.TOP_TO_BOTTOM -> {
+ setDirection(Direction.TOP_TO_BOTTOM)
+ }
+ Direction.RIGHT_TO_LEFT -> {
+ setDirection(Direction.RIGHT_TO_LEFT)
+ }
+ Direction.BOTTOM_TO_TOP -> {
+ setDirection(Direction.BOTTOM_TO_TOP)
+ }
+ }
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_shape)) {
+ when (typedArray.getInt(R.styleable.ShimmerTextView_shimmer_shape, shimmer.shape)) {
+ Shape.LINEAR -> {
+ setShape(Shape.LINEAR)
+ }
+ Shape.RADIAL -> {
+ setShape(Shape.RADIAL)
+ }
+ }
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_dropoff)) {
+ setDropOff(typedArray.getFloat(R.styleable.ShimmerTextView_shimmer_dropoff,
+ shimmer.dropOff))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_fixed_width)) {
+ setFixedWidth(typedArray.getDimensionPixelSize(R.styleable.ShimmerTextView_shimmer_fixed_width,
+ shimmer.fixedWidth))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_fixed_height)) {
+ setFixedHeight(typedArray.getDimensionPixelSize(R.styleable.ShimmerTextView_shimmer_fixed_height,
+ shimmer.fixedHeight))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_intensity)) {
+ setIntensity(typedArray.getFloat(R.styleable.ShimmerTextView_shimmer_intensity,
+ shimmer.intensity))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_width_ratio)) {
+ setWidthRatio(typedArray.getFloat(R.styleable.ShimmerTextView_shimmer_width_ratio,
+ shimmer.widthRatio))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_height_ratio)) {
+ setHeightRatio(typedArray.getFloat(R.styleable.ShimmerTextView_shimmer_height_ratio,
+ shimmer.heightRatio))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_tilt)) {
+ setTilt(typedArray.getFloat(R.styleable.ShimmerTextView_shimmer_tilt, shimmer.tilt))
+ }
+
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_colored)) {
+ setColored(
+ typedArray.getBoolean(R.styleable.ShimmerTextView_shimmer_colored, shimmer.isColored)
+ )
+ }
+
+ return getThis()
+ }
+
+ fun copyFrom(other: Shimmer): T {
+ setClipToChildren(other.clipToChildren)
+ setAutoStart(other.autoStart)
+ setRepeatCount(other.repeatCount)
+ setDirection(other.direction)
+ setShape(other.shape)
+ setTilt(other.tilt)
+ setIntensity(other.intensity)
+ setFixedWidth(other.fixedWidth)
+ setFixedHeight(other.fixedHeight)
+ setWidthRatio(other.widthRatio)
+ setHeightRatio(other.heightRatio)
+ setDropOff(other.dropOff)
+ setRepeatMode(other.repeatMode)
+ setRepeatDelay(other.repeatDelay)
+ setStartDelay(other.startDelay)
+ setDuration(other.animationDuration)
+ shimmer.baseColor = other.baseColor
+ shimmer.highLightColor = other.highLightColor
+ return getThis()
+ }
+
+ fun setClipToChildren(status: Boolean): T {
+ shimmer.clipToChildren = status
+ return getThis()
+ }
+
+ fun setAutoStart(status: Boolean): T {
+ shimmer.autoStart = status
+ return getThis()
+ }
+
+ fun setBaseAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float): T {
+ val intAlpha: Int = (clamp(0F, 1F, alpha) * 255F).toInt()
+ shimmer.baseColor = intAlpha shl 24 or (shimmer.baseColor and 0x00FFFFFF)
+ return getThis()
+ }
+
+ fun clamp(min: Float, max: Float, value: Float): Float {
+ return max.coerceAtMost(min.coerceAtLeast(value))
+ }
+
+ fun setHighlightAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float): T {
+ val intAlpha = (clamp(0F, 1F, alpha) * 255F).toInt()
+ shimmer.highLightColor = intAlpha shl 24 or (shimmer.highLightColor and 0x00FFFFFF)
+ return getThis()
+ }
+
+ fun setDuration(millis: Long): T {
+ if (millis < 0) {
+ throw IllegalArgumentException("Given a negative duration: $millis")
+ }
+ shimmer.animationDuration = millis
+ return getThis()
+ }
+
+ fun setRepeatCount(repeatCount: Int): T {
+ shimmer.repeatCount = repeatCount
+ return getThis()
+ }
+
+ fun setRepeatDelay(millis: Long): T {
+ if (millis < 0) {
+ throw IllegalArgumentException("Given a negative repeat delay")
+ }
+ shimmer.repeatDelay = millis
+ return getThis()
+ }
+
+ fun setStartDelay(millis: Long): T {
+ if (millis < 0) {
+ throw IllegalArgumentException("Given a negative start delay: $millis")
+ }
+ shimmer.startDelay = millis
+ return getThis()
+ }
+
+ fun setRepeatMode(mode: Int): T {
+ shimmer.repeatMode = mode
+ return getThis()
+ }
+
+ fun setDirection(@Direction direction: Int): T {
+ shimmer.direction = direction
+ return getThis()
+ }
+
+ fun setShape(@Shape shape: Int): T {
+ shimmer.shape = shape
+ return getThis()
+ }
+
+ fun setDropOff(dropOff: Float): T {
+ if (dropOff < 0F) {
+ throw IllegalArgumentException("Given invalid drop off value: $dropOff")
+ }
+ shimmer.dropOff = dropOff
+ return getThis()
+ }
+
+ fun setFixedWidth(@Px fixedWidth: Int): T {
+ if (fixedWidth < 0) {
+ throw IllegalArgumentException("Given invalid width: $fixedWidth")
+ }
+ shimmer.fixedWidth = fixedWidth
+ return getThis()
+ }
+
+ fun setFixedHeight(@Px fixedHeight: Int): T {
+ if (fixedHeight < 0) {
+ throw IllegalArgumentException("Given invalid height: $fixedHeight")
+ }
+ shimmer.fixedHeight = fixedHeight
+ return getThis()
+ }
+
+ fun setIntensity(intensity: Float): T {
+ if (intensity < 0F) {
+ throw IllegalArgumentException("Given invalid intensity value: $intensity")
+ }
+ shimmer.intensity = intensity
+ return getThis()
+ }
+
+ fun setWidthRatio(widthRatio: Float): T {
+ if (widthRatio < 0F) {
+ throw IllegalArgumentException("Given invalid width ratio: $widthRatio")
+ }
+ shimmer.widthRatio = widthRatio
+ return getThis()
+ }
+
+ fun setHeightRatio(heightRatio: Float): T {
+ if (heightRatio < 0F) {
+ throw IllegalArgumentException("Given invalid height ratio: $heightRatio")
+ }
+ shimmer.heightRatio = heightRatio
+ return getThis()
+ }
+
+ fun setTilt(tilt: Float): T {
+ shimmer.tilt = tilt
+ return getThis()
+ }
+
+ fun setColored(isColored: Boolean): T {
+ shimmer.isColored = isColored
+ return getThis()
+ }
+
+ fun build(): Shimmer {
+ shimmer.updateColor()
+ shimmer.updatePosition()
+ return shimmer
+ }
+ }
+
+ class AlphaHighlightBuilder : Builder() {
+ override fun getThis(): AlphaHighlightBuilder {
+ return this
+ }
+
+ init {
+ shimmer.alphaShimmer = true
+ }
+ }
+
+ class ColorHighlightBuilder : Builder() {
+ /** Sets the highlight color for the shimmer. */
+ fun setHighlightColor(@ColorInt color: Int): ColorHighlightBuilder {
+ shimmer.highLightColor = color
+ return getThis()
+ }
+
+ /** Sets the base color for the shimmer. */
+ fun setBaseColor(@ColorInt color: Int): ColorHighlightBuilder {
+ shimmer.baseColor = color
+ return getThis()
+ }
+
+ override fun consumeAttributes(typedArray: TypedArray): ColorHighlightBuilder {
+ super.consumeAttributes(typedArray)
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_base_color)) {
+ setBaseColor(
+ typedArray.getColor(R.styleable.ShimmerTextView_shimmer_base_color,
+ shimmer.baseColor))
+ }
+ if (typedArray.hasValue(R.styleable.ShimmerTextView_shimmer_highlight_color)) {
+ setHighlightColor(
+ typedArray.getColor(
+ R.styleable.ShimmerTextView_shimmer_highlight_color,
+ shimmer.highLightColor))
+ }
+ return getThis()
+ }
+
+ override fun getThis(): ColorHighlightBuilder {
+ return this
+ }
+
+ init {
+ shimmer.alphaShimmer = false
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/ShimmerTextView/src/main/java/com/app/shimmertextview/ShimmerDrawable.kt b/ShimmerTextView/src/main/java/com/app/shimmertextview/ShimmerDrawable.kt
new file mode 100644
index 0000000..44e9535
--- /dev/null
+++ b/ShimmerTextView/src/main/java/com/app/shimmertextview/ShimmerDrawable.kt
@@ -0,0 +1,226 @@
+package com.app.shimmertextview
+
+import android.animation.ValueAnimator
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.view.animation.LinearInterpolator
+import androidx.annotation.FloatRange
+import androidx.annotation.Nullable
+import androidx.annotation.Px
+import kotlin.math.sqrt
+import kotlin.math.tan
+
+class ShimmerDrawable : Drawable() {
+
+ private val shimmerPaint = Paint()
+ private val rect = Rect()
+ private val shaderMatrix = Matrix()
+
+ @Nullable
+ private var valueAnimator: ValueAnimator? = null
+ private var staticAnimationProgress = -1F
+
+ private var shimmer: Shimmer? = null
+
+ init {
+ shimmerPaint.isAntiAlias = true
+ }
+
+ private val valueAnimatorUpdateListener = ValueAnimator.AnimatorUpdateListener {
+ invalidateSelf()
+ }
+
+ fun setShimmer(@Nullable shimmer: Shimmer) {
+ this.shimmer = shimmer
+ if (this.shimmer != null) {
+ shimmerPaint.xfermode =
+ PorterDuffXfermode(if (shimmer.alphaShimmer) PorterDuff.Mode.DST_IN else PorterDuff.Mode.SRC_IN)
+ }
+ updateShader()
+ updateValueAnimator()
+ invalidateSelf()
+ }
+
+
+ @Nullable
+ fun getShimmer(): Shimmer? {
+ return shimmer
+ }
+
+ fun startShimmer() {
+ if (valueAnimator != null && !isShimmerStarted() && callback != null) {
+ valueAnimator?.start()
+ }
+ }
+
+ fun stopShimmer() {
+ if (valueAnimator != null && isShimmerStarted()) {
+ valueAnimator?.cancel()
+ }
+ }
+
+ fun isShimmerStarted(): Boolean {
+ return valueAnimator != null && valueAnimator?.isStarted == true
+ }
+
+ fun isShimmerRunning(): Boolean {
+ return valueAnimator != null && valueAnimator?.isRunning == true
+ }
+
+ override fun onBoundsChange(bounds: Rect?) {
+ super.onBoundsChange(bounds)
+ bounds?.let {
+ rect.set(it)
+ updateShader()
+ maybeStartShimmer()
+ }
+ }
+
+ fun maybeStartShimmer() {
+ valueAnimator?.let { animator ->
+ if (!animator.isStarted && shimmer != null && shimmer?.autoStart == true && callback != null) {
+ animator.start()
+ }
+ }
+ }
+
+ fun setStaticAnimationProgress(value: Float) {
+ if (value.compareTo(staticAnimationProgress) == 0 || (value < 0F && staticAnimationProgress < 0F)) {
+ return
+ }
+ staticAnimationProgress = value.coerceAtMost(1F)
+ invalidateSelf()
+ }
+
+ fun clearStaticAnimationProgress() {
+ setStaticAnimationProgress(-1F)
+ }
+
+ override fun draw(canvas: Canvas) {
+ if (shimmer == null || shimmerPaint.shader == null) {
+ return
+ }
+
+ val tiltTan: Float = tan(Math.toRadians(shimmer?.tilt?.toDouble() ?: 0.0)).toFloat()
+ val translateHeight = rect.height() + tiltTan * rect.width()
+ val translateWidth = rect.width() + tiltTan * rect.height()
+ var dx = 0F
+ var dy = 0F
+ var animatedValue = 0F
+
+ animatedValue = if (staticAnimationProgress < 0F) {
+ if (valueAnimator != null) valueAnimator?.animatedValue as Float else 0F
+ } else {
+ staticAnimationProgress
+ }
+
+ when (shimmer?.direction) {
+ Shimmer.Direction.LEFT_TO_RIGHT -> {
+ dx = offset(-translateWidth, translateWidth, animatedValue)
+ dy = 0F
+ }
+ Shimmer.Direction.RIGHT_TO_LEFT -> {
+ dx = offset(translateWidth, -translateWidth, animatedValue)
+ dy = 0f
+ }
+ Shimmer.Direction.TOP_TO_BOTTOM -> {
+ dx = 0F
+ dy = offset(-translateHeight, translateHeight, animatedValue)
+ }
+ Shimmer.Direction.BOTTOM_TO_TOP -> {
+ dx = 0F
+ dy = offset(translateHeight, -translateHeight, animatedValue)
+ }
+ }
+
+ shaderMatrix.reset()
+ shaderMatrix.setRotate(shimmer?.tilt ?: 0F, rect.width() / 2F, rect.height() / 2F)
+ shaderMatrix.preTranslate(dx, dy)
+ shimmerPaint.shader.setLocalMatrix(shaderMatrix)
+ canvas.drawRect(rect, shimmerPaint)
+ }
+
+ private fun offset(start: Float, end: Float, percent: Float) = start + (end - start) * percent
+
+ override fun setAlpha(p0: Int) {
+ }
+
+ override fun setColorFilter(p0: ColorFilter?) {
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun getOpacity(): Int {
+ return if (shimmer != null && (shimmer?.clipToChildren == true || shimmer?.alphaShimmer == true)) PixelFormat.TRANSLUCENT else PixelFormat.OPAQUE
+ }
+
+ private fun updateValueAnimator() {
+ shimmer?.let { shimmer ->
+ val started: Boolean
+
+ if (valueAnimator != null) {
+ started = valueAnimator?.isStarted == true
+ valueAnimator?.cancel()
+ valueAnimator?.removeAllUpdateListeners()
+ } else {
+ started = false
+ }
+
+
+ valueAnimator =
+ ValueAnimator.ofFloat(0F, 1F + (shimmer.repeatDelay / shimmer.animationDuration))
+ valueAnimator?.interpolator = LinearInterpolator()
+ valueAnimator?.repeatMode = shimmer.repeatMode
+ valueAnimator?.startDelay = shimmer.startDelay
+ valueAnimator?.repeatCount = shimmer.repeatCount
+ valueAnimator?.duration = shimmer.animationDuration + shimmer.repeatDelay
+ valueAnimator?.addUpdateListener(valueAnimatorUpdateListener)
+ if (started) {
+ valueAnimator?.start()
+ }
+ }
+ }
+
+ private fun updateShader() {
+ val bounds = bounds
+ val boundsWidth = bounds.width()
+ val boundsHeight = bounds.height()
+
+ if (boundsWidth == 0 || boundsHeight == 0 || shimmer == null) {
+ return
+ }
+
+ val width = shimmer?.width(boundsWidth)
+ val height = shimmer?.height(boundsHeight)
+
+ var shader: Shader? = null
+
+ when (shimmer?.shape) {
+ Shimmer.Shape.LINEAR -> {
+ val vertical =
+ shimmer?.direction == Shimmer.Direction.TOP_TO_BOTTOM || shimmer?.direction == Shimmer.Direction.BOTTOM_TO_TOP
+ val endX = if (vertical) 0 else width
+ val endY = if (vertical) height else 0
+
+ shader = LinearGradient(
+ 0F,
+ 0F,
+ endX?.toFloat() ?: 0F,
+ endY?.toFloat() ?: 0F,
+ shimmer?.colors ?: intArrayOf(),
+ shimmer?.positions ?: floatArrayOf(),
+ Shader.TileMode.CLAMP)
+ }
+ Shimmer.Shape.RADIAL -> {
+ val radius = ((width
+ ?: 0).coerceAtLeast(height ?: 0) / sqrt(2.0))
+ shader = RadialGradient(width?.div(2F) ?: 0F,
+ height?.div(2F) ?: 0F,
+ radius.toFloat(),
+ shimmer?.colors ?: intArrayOf(),
+ shimmer?.positions ?: floatArrayOf(),
+ Shader.TileMode.CLAMP)
+ }
+ }
+ shimmerPaint.shader = shader
+ }
+}
\ No newline at end of file
diff --git a/ShimmerTextView/src/main/java/com/app/shimmertextview/ShimmerTextView.kt b/ShimmerTextView/src/main/java/com/app/shimmertextview/ShimmerTextView.kt
new file mode 100644
index 0000000..fcab0f5
--- /dev/null
+++ b/ShimmerTextView/src/main/java/com/app/shimmertextview/ShimmerTextView.kt
@@ -0,0 +1,282 @@
+package com.app.shimmertextview
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.FloatRange
+import androidx.annotation.Nullable
+import androidx.annotation.Px
+import androidx.appcompat.widget.AppCompatTextView
+
+class ShimmerTextView : AppCompatTextView {
+
+ private val paint: Paint = Paint()
+ private val shimmerDrawable: ShimmerDrawable = ShimmerDrawable()
+
+ private var showShimmer = true
+ private var stoppedShimmerBecauseVisibility = true
+
+ private var shimmerBuilder: Shimmer.Builder<*>? = null
+
+ constructor(context: Context) : super(context) {
+ init(context, null)
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ init(context, attrs)
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
+ context,
+ attrs,
+ defStyleAttr
+ ) {
+ init(context, attrs)
+ }
+
+ private fun init(context: Context?, @Nullable attrs: AttributeSet?) {
+ setWillNotDraw(false)
+ shimmerDrawable.callback = this
+
+ if (attrs == null) {
+ setShimmer(Shimmer.AlphaHighlightBuilder().build())
+ return
+ }
+
+ val typedArray: TypedArray? =
+ context?.obtainStyledAttributes(attrs, R.styleable.ShimmerTextView, 0, 0)
+ try {
+ shimmerBuilder =
+ if (typedArray?.hasValue(R.styleable.ShimmerTextView_shimmer_colored) == true && typedArray.getBoolean(
+ R.styleable.ShimmerTextView_shimmer_colored,
+ false)
+ ) Shimmer.ColorHighlightBuilder() else Shimmer.AlphaHighlightBuilder()
+ typedArray?.let {
+ setShimmer(shimmerBuilder?.consumeAttributes(it)?.build())
+ }
+ } finally {
+ typedArray?.recycle()
+ }
+ }
+
+ private fun setShimmer(@Nullable shimmer: Shimmer?): ShimmerTextView {
+ shimmer?.let {
+ shimmerDrawable.setShimmer(it)
+
+ if (it.clipToChildren) {
+ setLayerType(LAYER_TYPE_HARDWARE, paint)
+ } else {
+ setLayerType(LAYER_TYPE_NONE, null)
+ }
+ }
+
+ return this
+ }
+
+ fun getShimmer(): Shimmer? {
+ return shimmerDrawable.getShimmer()
+ }
+
+ fun startShimmer() {
+ shimmerDrawable.startShimmer()
+ }
+
+ fun stopShimmer() {
+ stoppedShimmerBecauseVisibility = false
+ shimmerDrawable.stopShimmer()
+ }
+
+ fun isShimmerStarted(): Boolean {
+ return shimmerDrawable.isShimmerStarted()
+ }
+
+ fun showShimmer(startShimmer: Boolean) {
+ showShimmer = true
+ if (startShimmer) {
+ startShimmer()
+ }
+ invalidate()
+ }
+
+ fun hideShimmer() {
+ stopShimmer()
+ showShimmer = false
+ invalidate()
+ }
+
+ fun isShimmerVisible(): Boolean {
+ return showShimmer
+ }
+
+ fun isShimmerRunning(): Boolean {
+ return shimmerDrawable.isShimmerRunning()
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ shimmerDrawable.setBounds(0, 0, width, height)
+ }
+
+ override fun onVisibilityChanged(changedView: View, visibility: Int) {
+ super.onVisibilityChanged(changedView, visibility)
+ if (shimmerDrawable == null) {
+ return
+ }
+
+ if (visibility != View.VISIBLE) {
+ if (isShimmerStarted()) {
+ stopShimmer()
+ stoppedShimmerBecauseVisibility = true
+ }
+ } else if (stoppedShimmerBecauseVisibility) {
+ shimmerDrawable.maybeStartShimmer()
+ stoppedShimmerBecauseVisibility = false
+ }
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ shimmerDrawable.maybeStartShimmer()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ stopShimmer()
+ }
+
+ override fun dispatchDraw(canvas: Canvas?) {
+ super.dispatchDraw(canvas)
+ if (showShimmer) {
+ canvas?.let { shimmerDrawable.draw(it) }
+ }
+ }
+
+ override fun verifyDrawable(who: Drawable): Boolean {
+ return super.verifyDrawable(who) || who == shimmerDrawable
+ }
+
+ fun setStaticAnimationProgress(value: Float) {
+ shimmerDrawable.setStaticAnimationProgress(value)
+ }
+
+ fun clearStaticAnimationProgress() {
+ shimmerDrawable.clearStaticAnimationProgress()
+ }
+
+ fun setBaseColor(baseColor: Int): ShimmerTextView {
+ if (shimmerBuilder is Shimmer.ColorHighlightBuilder) {
+ (shimmerBuilder as Shimmer.ColorHighlightBuilder).setBaseColor(baseColor)
+ }
+ return this
+ }
+
+ fun setHighLightColor(highlightColor: Int): ShimmerTextView {
+ if (shimmerBuilder is Shimmer.ColorHighlightBuilder) {
+ (shimmerBuilder as Shimmer.ColorHighlightBuilder).setHighlightColor(highlightColor)
+ }
+ return this
+ }
+
+ fun setClipToChildren(status: Boolean): ShimmerTextView {
+ shimmerBuilder?.setClipToChildren(status)
+ return this
+ }
+
+ fun setAutoStart(status: Boolean): ShimmerTextView {
+ shimmerBuilder?.setAutoStart(status)
+ return this
+ }
+
+ fun setBaseAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float): ShimmerTextView {
+ shimmerBuilder?.setBaseAlpha(alpha)
+ return this
+ }
+
+ fun setHighlightAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float): ShimmerTextView {
+ shimmerBuilder?.setHighlightAlpha(alpha)
+ return this
+ }
+
+ fun setDuration(millis: Long): ShimmerTextView {
+ shimmerBuilder?.setDuration(millis)
+ return this
+ }
+
+ fun setRepeatCount(repeatCount: Int): ShimmerTextView {
+ shimmerBuilder?.setRepeatCount(repeatCount)
+ return this
+ }
+
+ fun setRepeatDelay(millis: Long): ShimmerTextView {
+ shimmerBuilder?.setRepeatDelay(millis)
+ return this
+ }
+
+ fun setStartDelay(millis: Long): ShimmerTextView {
+ shimmerBuilder?.setStartDelay(millis)
+ return this
+ }
+
+ fun setRepeatMode(mode: Int): ShimmerTextView {
+ shimmerBuilder?.setRepeatMode(mode)
+ return this
+ }
+
+ fun setDirection(@Shimmer.Direction direction: Int): ShimmerTextView {
+ shimmerBuilder?.setDirection(direction)
+ return this
+ }
+
+ fun setShape(@Shimmer.Shape shape: Int): ShimmerTextView {
+ shimmerBuilder?.setShape(shape)
+ return this
+ }
+
+ fun setDropOff(dropOff: Float): ShimmerTextView {
+ shimmerBuilder?.setDropOff(dropOff)
+ return this
+ }
+
+ fun setFixedWidth(@Px fixedWidth: Int): ShimmerTextView {
+ shimmerBuilder?.setFixedWidth(fixedWidth)
+ return this
+ }
+
+ fun setFixedHeight(@Px fixedHeight: Int): ShimmerTextView {
+ shimmerBuilder?.setFixedHeight(fixedHeight)
+ return this
+ }
+
+ fun setIntensity(intensity: Float): ShimmerTextView {
+ shimmerBuilder?.setIntensity(intensity)
+ return this
+ }
+
+ fun setWidthRatio(widthRatio: Float): ShimmerTextView {
+ shimmerBuilder?.setWidthRatio(widthRatio)
+ return this
+ }
+
+ fun setHeightRatio(heightRatio: Float): ShimmerTextView {
+ shimmerBuilder?.setHeightRatio(heightRatio)
+ return this
+ }
+
+ fun setTilt(tilt: Float): ShimmerTextView {
+ shimmerBuilder?.setTilt(tilt)
+ return this
+ }
+
+ fun setColored(isColored: Boolean): ShimmerTextView {
+ shimmerBuilder?.setColored(isColored)
+ return this
+ }
+
+ fun build() {
+ shimmerBuilder?.build()
+ }
+}
\ No newline at end of file
diff --git a/ShimmerTextView/src/main/res/values/attrs.xml b/ShimmerTextView/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..76fd3d1
--- /dev/null
+++ b/ShimmerTextView/src/main/res/values/attrs.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ShimmerTextView/src/test/java/com/app/shimmertextview/ExampleUnitTest.kt b/ShimmerTextView/src/test/java/com/app/shimmertextview/ExampleUnitTest.kt
new file mode 100644
index 0000000..f9a5711
--- /dev/null
+++ b/ShimmerTextView/src/test/java/com/app/shimmertextview/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.app.shimmertextview
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..cc68eb0
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ applicationId "com.app.shimmertextview"
+ minSdk 23
+ targetSdk 32
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.material:material:1.6.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation project(path: ':ShimmerTextView')
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/app/shimmertextview/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/app/shimmertextview/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..1612787
--- /dev/null
+++ b/app/src/androidTest/java/com/app/shimmertextview/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.app.shimmertextview
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.app.shimmertextview", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..06ccd45
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/app/shimmertextview/MainActivity.kt b/app/src/main/java/com/app/shimmertextview/MainActivity.kt
new file mode 100644
index 0000000..6d1751a
--- /dev/null
+++ b/app/src/main/java/com/app/shimmertextview/MainActivity.kt
@@ -0,0 +1,61 @@
+package com.app.shimmertextview
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.AppCompatButton
+import androidx.core.content.ContextCompat
+
+
+class MainActivity : AppCompatActivity() {
+
+ lateinit var tv1: ShimmerTextView
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window.decorView.systemUiVisibility =
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ setWindowFlag(this, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, false)
+ window.statusBarColor = Color.TRANSPARENT
+ window.navigationBarColor = Color.TRANSPARENT
+
+ setContentView(R.layout.activity_main)
+
+ tv1 = findViewById(R.id.tv1)
+ val tv2 = findViewById(R.id.tv2)
+ val tv3 = findViewById(R.id.tv3)
+ val tv4 = findViewById(R.id.tv4)
+ val btnNext = findViewById(R.id.btnNext)
+ tv1.setBaseColor(ContextCompat.getColor(this, R.color.dark_red))
+ .setHighLightColor(ContextCompat.getColor(this, R.color.orange))
+ .setDirection(Shimmer.Direction.LEFT_TO_RIGHT)
+ .build()
+ tv1.startShimmer()
+ tv2.startShimmer()
+ tv3.startShimmer()
+ tv4.startShimmer()
+
+ btnNext.setOnClickListener {
+ startActivity(Intent(this, OfferActivity::class.java))
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ tv1.stopShimmer()
+ }
+
+ private fun setWindowFlag(activity: Activity, bits: Int, on: Boolean) {
+ val win = activity.window
+ val winParams = win.attributes
+ if (on) {
+ winParams.flags = winParams.flags or bits
+ } else {
+ winParams.flags = winParams.flags and bits.inv()
+ }
+ win.attributes = winParams
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/app/shimmertextview/OfferActivity.kt b/app/src/main/java/com/app/shimmertextview/OfferActivity.kt
new file mode 100644
index 0000000..76ee409
--- /dev/null
+++ b/app/src/main/java/com/app/shimmertextview/OfferActivity.kt
@@ -0,0 +1,46 @@
+package com.app.shimmertextview
+
+import android.app.Activity
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+
+class OfferActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window.decorView.systemUiVisibility =
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ setWindowFlag(this, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, false)
+ window.statusBarColor = Color.TRANSPARENT
+ window.navigationBarColor = Color.TRANSPARENT
+
+ setContentView(R.layout.activity_offer)
+
+ val tvOffer1 = findViewById(R.id.tvOffer1)
+ val tvOffer2 = findViewById(R.id.tvOffer2)
+ val tvOffer3 = findViewById(R.id.tvOffer3)
+ val tvOffer5 = findViewById(R.id.tvOffer5)
+ val tvOffer6 = findViewById(R.id.tvOffer6)
+ val tvOffer7 = findViewById(R.id.tvOffer7)
+ tvOffer1.startShimmer()
+ tvOffer2.startShimmer()
+ tvOffer3.startShimmer()
+ tvOffer5.startShimmer()
+ tvOffer6.startShimmer()
+ tvOffer7.startShimmer()
+ }
+
+ private fun setWindowFlag(activity: Activity, bits: Int, on: Boolean) {
+ val win = activity.window
+ val winParams = win.attributes
+ if (on) {
+ winParams.flags = winParams.flags or bits
+ } else {
+ winParams.flags = winParams.flags and bits.inv()
+ }
+ win.attributes = winParams
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background.xml b/app/src/main/res/drawable/background.xml
new file mode 100644
index 0000000..7c72562
--- /dev/null
+++ b/app/src/main/res/drawable/background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_bottom_to_top.xml b/app/src/main/res/drawable/ic_bottom_to_top.xml
new file mode 100644
index 0000000..d0b30cb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bottom_to_top.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_left_to_right.xml b/app/src/main/res/drawable/ic_left_to_right.xml
new file mode 100644
index 0000000..cc3583f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_left_to_right.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml
new file mode 100644
index 0000000..a4aadba
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_right_to_left.xml b/app/src/main/res/drawable/ic_right_to_left.xml
new file mode 100644
index 0000000..5e5d496
--- /dev/null
+++ b/app/src/main/res/drawable/ic_right_to_left.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_top_to_bottom.xml b/app/src/main/res/drawable/ic_top_to_bottom.xml
new file mode 100644
index 0000000..8b1db82
--- /dev/null
+++ b/app/src/main/res/drawable/ic_top_to_bottom.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_menu_button_bg.xml b/app/src/main/res/drawable/shape_menu_button_bg.xml
new file mode 100644
index 0000000..c27bd83
--- /dev/null
+++ b/app/src/main/res/drawable/shape_menu_button_bg.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/font/poppins_bold.ttf b/app/src/main/res/font/poppins_bold.ttf
new file mode 100644
index 0000000..00559ee
Binary files /dev/null and b/app/src/main/res/font/poppins_bold.ttf differ
diff --git a/app/src/main/res/font/poppins_regular.ttf b/app/src/main/res/font/poppins_regular.ttf
new file mode 100644
index 0000000..9f0c71b
Binary files /dev/null and b/app/src/main/res/font/poppins_regular.ttf differ
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..d43ab4d
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,350 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_offer.xml b/app/src/main/res/layout/activity_offer.xml
new file mode 100644
index 0000000..16ebed8
--- /dev/null
+++ b/app/src/main/res/layout/activity_offer.xml
@@ -0,0 +1,328 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/custom_tool_bar.xml b/app/src/main/res/layout/custom_tool_bar.xml
new file mode 100644
index 0000000..eddfb8b
--- /dev/null
+++ b/app/src/main/res/layout/custom_tool_bar.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..2909648
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #000000
+ #FFFFFF
+ #F4D03F
+ #f96b8f
+ #db0050
+ #e14782
+ #eeecee
+ #ECF0F1
+
+ #1B4F72
+ #B7950B
+ #873600
+ #186A3B
+ #1C2833
+ #4A235A
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b1e65c5
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+ ShimmerTextView
+ Mindinventory
+ Save up to 5$
+ * Minimum order value 20$
+ FantasticFriday
+ Buy 1 Get 1 Free
+ Buy any 4 Sandwiches
+ Get 1 Sandwich Free!
+ 50% OFF Up to 3$
+ Free Cheesy Garlic Bread
+
+ Buy nine coffees and get one free, purchase every beer on the list and get 10% off beers for the year.
+ Apply HappyFriday and get 1 Free Coffee
+ 10% Discount on Beverages
+ Discount salty =pretzels thatโll inspire someone to buy beers.
+ Thursday happy hour
+ Get Free Delivery on 2 Order
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..30ec6ef
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,28 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/app/shimmertextview/ExampleUnitTest.kt b/app/src/test/java/com/app/shimmertextview/ExampleUnitTest.kt
new file mode 100644
index 0000000..f9a5711
--- /dev/null
+++ b/app/src/test/java/com/app/shimmertextview/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.app.shimmertextview
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/art/ShimmerTextView.gif b/art/ShimmerTextView.gif
new file mode 100644
index 0000000..6e22af2
Binary files /dev/null and b/art/ShimmerTextView.gif differ
diff --git a/art/ShimmerTextViewOffer.gif b/art/ShimmerTextViewOffer.gif
new file mode 100644
index 0000000..2f68f40
Binary files /dev/null and b/art/ShimmerTextViewOffer.gif differ
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..62d0e4c
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,10 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.2.0' apply false
+ id 'com.android.library' version '7.2.0' apply false
+ id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..cd0519b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..be2826a
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Jun 06 10:58:53 IST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..fc1157a
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "ShimmerTextView"
+include ':app'
+include ':ShimmerTextView'