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'