Skip to content

Commit e869953

Browse files
committed
Initial commit
0 parents  commit e869953

29 files changed

+898
-0
lines changed

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
/.idea/caches
5+
/.idea/libraries
6+
/.idea/modules.xml
7+
/.idea/workspace.xml
8+
/.idea/navEditor.xml
9+
/.idea/assetWizardSettings.xml
10+
.DS_Store
11+
/build
12+
/captures
13+
.externalNativeBuild
14+
.cxx
15+
local.properties
16+
/.idea/

app/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

app/build.gradle.kts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
plugins {
2+
id("com.android.library")
3+
id("org.jetbrains.kotlin.android")
4+
id("maven-publish")
5+
}
6+
7+
android {
8+
namespace = "ru.chatan.swipebutton"
9+
compileSdk = 33
10+
11+
defaultConfig {
12+
minSdk = 26
13+
14+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15+
}
16+
17+
buildTypes {
18+
release {
19+
isMinifyEnabled = false
20+
proguardFiles(
21+
getDefaultProguardFile("proguard-android-optimize.txt"),
22+
"proguard-rules.pro"
23+
)
24+
}
25+
}
26+
compileOptions {
27+
sourceCompatibility = JavaVersion.VERSION_1_8
28+
targetCompatibility = JavaVersion.VERSION_1_8
29+
}
30+
31+
kotlinOptions {
32+
jvmTarget = "1.8"
33+
}
34+
35+
buildFeatures {
36+
viewBinding = true
37+
}
38+
}
39+
40+
afterEvaluate {
41+
publishing {
42+
publications {
43+
create<MavenPublication>("maven") {
44+
groupId = "ru.chatan.swipebutton"
45+
artifactId = "swipe-button"
46+
version = "1.0.0"
47+
48+
from(components["release"])
49+
}
50+
}
51+
}
52+
}
53+
54+
dependencies {
55+
implementation("androidx.core:core-ktx:1.9.0")
56+
implementation("androidx.appcompat:appcompat:1.6.1")
57+
implementation("com.google.android.material:material:1.9.0")
58+
testImplementation("junit:junit:4.13.2")
59+
androidTestImplementation("androidx.test.ext:junit:1.1.5")
60+
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
61+
implementation("com.google.guava:guava:27.0.1-android")
62+
63+
// Shimmer
64+
implementation("com.facebook.shimmer:shimmer:0.5.0")
65+
66+
// JitPack
67+
implementation("com.github.jitpack:gradle-simple:1.0")
68+
}

app/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package ru.chatan.swipebutton
2+
3+
import androidx.test.platform.app.InstrumentationRegistry
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
6+
import org.junit.Test
7+
import org.junit.runner.RunWith
8+
9+
import org.junit.Assert.*
10+
11+
/**
12+
* Instrumented test, which will execute on an Android device.
13+
*
14+
* See [testing documentation](http://d.android.com/tools/testing).
15+
*/
16+
@RunWith(AndroidJUnit4::class)
17+
class ExampleInstrumentedTest {
18+
@Test
19+
fun useAppContext() {
20+
// Context of the app under test.
21+
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22+
assertEquals("ru.chatan.swipebutton", appContext.packageName)
23+
}
24+
}

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest>
3+
4+
</manifest>
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package ru.chatan.swipebutton
2+
3+
import android.animation.Animator
4+
import android.animation.Animator.AnimatorListener
5+
import android.animation.ObjectAnimator
6+
import android.annotation.SuppressLint
7+
import android.content.Context
8+
import android.content.res.ColorStateList
9+
import android.content.res.TypedArray
10+
import android.util.AttributeSet
11+
import android.util.TypedValue
12+
import android.view.LayoutInflater
13+
import android.view.MotionEvent
14+
import android.view.View
15+
import android.view.ViewGroup
16+
import android.view.animation.LinearInterpolator
17+
import android.widget.RelativeLayout
18+
import androidx.core.content.ContextCompat
19+
import androidx.core.content.res.ResourcesCompat
20+
import ru.chatan.swipebutton.databinding.SwipeButtonLayoutBinding
21+
22+
@SuppressLint("ClickableViewAccessibility")
23+
class SwipeButton(context: Context, attrs: AttributeSet): RelativeLayout(context, attrs) {
24+
25+
private var _binding: SwipeButtonLayoutBinding? = null
26+
private val binding get() = _binding!!
27+
28+
private var animation: ObjectAnimator? = null
29+
private var onSwipeButtonListener: OnSwipeButtonListener? = null
30+
31+
private var defaultText: String = ""
32+
33+
init {
34+
_binding = SwipeButtonLayoutBinding.inflate(LayoutInflater.from(context))
35+
addView(binding.root)
36+
37+
setThumbTouchListener()
38+
binding.shimmerBackground.showShimmer(true)
39+
40+
applyAttrs(context, attrs)
41+
}
42+
43+
private fun applyAttrs(context: Context, attrs: AttributeSet) {
44+
val typedArray = context.obtainStyledAttributes(
45+
attrs,
46+
R.styleable.SwipeButton, 0, 0
47+
)
48+
49+
applyTextAttrs(typedArray, context)
50+
applyDrawableColorAttrs(typedArray, context)
51+
52+
typedArray.recycle()
53+
}
54+
55+
private fun applyDrawableColorAttrs(
56+
typedArray: TypedArray,
57+
context: Context
58+
) {
59+
val drawable =
60+
typedArray.getResourceId(R.styleable.SwipeButton_swipeDrawable, R.drawable.swipe_arrow)
61+
val thumbColor = typedArray.getColor(
62+
R.styleable.SwipeButton_swipeThumbColor,
63+
ContextCompat.getColor(context, R.color.light_blue)
64+
)
65+
val progressColor = typedArray.getColor(
66+
R.styleable.SwipeButton_swipeProgressColor,
67+
ContextCompat.getColor(context, R.color.light_blue)
68+
)
69+
70+
binding.thumb.setImageResource(drawable)
71+
binding.thumb.backgroundTintList = ColorStateList.valueOf(thumbColor)
72+
binding.backgroundProgress.setBackgroundColor(progressColor)
73+
}
74+
75+
private fun applyTextAttrs(
76+
typedArray: TypedArray,
77+
context: Context
78+
) {
79+
defaultText = typedArray.getString(R.styleable.SwipeButton_swipeText).orEmpty()
80+
val font =
81+
typedArray.getResourceId(R.styleable.SwipeButton_swipeFontFamily, R.font.inter_medium)
82+
val color = typedArray.getColor(
83+
R.styleable.SwipeButton_swipeTextColor,
84+
ContextCompat.getColor(context, R.color.light_blue)
85+
)
86+
87+
val textSize = typedArray.getDimensionPixelSize(R.styleable.SwipeButton_swipeTextSize, 16)
88+
binding.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize.toFloat())
89+
binding.textView.typeface = ResourcesCompat.getFont(context, font)
90+
binding.textView.setTextColor(color)
91+
binding.textView.text = defaultText
92+
}
93+
94+
private fun setThumbTouchListener() {
95+
var offsetX = 0F
96+
var endX = -1
97+
var layoutParams: ViewGroup.LayoutParams?
98+
99+
binding.thumb.setOnTouchListener { view, event ->
100+
val textStartX = binding.textView.left
101+
val textEndX = binding.textView.right
102+
if (endX == -1) endX = binding.shimmerBackground.right
103+
104+
when (event.action) {
105+
MotionEvent.ACTION_DOWN -> {
106+
offsetX = event.rawX - view.x
107+
animation?.cancel()
108+
}
109+
110+
MotionEvent.ACTION_UP -> {
111+
if (view.x + view.right < endX) {
112+
animation = ObjectAnimator.ofFloat(view, "translationX", 0f)
113+
animation?.duration = 200
114+
animation?.interpolator = LinearInterpolator()
115+
116+
animation?.addUpdateListener {
117+
layoutParams = binding.backgroundProgress.layoutParams
118+
layoutParams?.width = (it.animatedValue as Float + (view.right / 2) + 15).toInt()
119+
binding.backgroundProgress.layoutParams = layoutParams
120+
}
121+
122+
animation?.addListener(object : AnimatorListener {
123+
override fun onAnimationStart(animation: Animator) {
124+
}
125+
126+
override fun onAnimationEnd(animation: Animator) {
127+
setTextVisibility(1f)
128+
}
129+
130+
override fun onAnimationCancel(animation: Animator) {
131+
}
132+
133+
override fun onAnimationRepeat(animation: Animator) {
134+
}
135+
})
136+
137+
animation?.start()
138+
}
139+
}
140+
141+
MotionEvent.ACTION_MOVE -> {
142+
val newX = event.rawX - offsetX
143+
view.x = newX
144+
145+
if (view.x + view.right >= endX) {
146+
view.x = endX.toFloat() - view.right
147+
148+
binding.thumb.isEnabled = false
149+
binding.thumb.isClickable = false
150+
151+
onSwipeButtonListener?.onSwiped()
152+
}
153+
if (view.x <= 0) view.x = 0F
154+
155+
var rawAlpha = (textStartX - (view.x + view.right))
156+
if (rawAlpha > 100) setTextVisibility(1f)
157+
else if (rawAlpha in 0.0..100.0) setTextVisibility(rawAlpha / 100)
158+
else if (view.x + view.right > textStartX && view.x + view.right < textEndX) setTextVisibility(0f)
159+
else {
160+
rawAlpha = (view.x + view.left) - textEndX
161+
if (rawAlpha > 100) setTextVisibility(1f)
162+
else if (rawAlpha in 0.0..100.0) setTextVisibility(rawAlpha / 100)
163+
}
164+
}
165+
}
166+
167+
layoutParams = binding.backgroundProgress.layoutParams
168+
layoutParams?.width = (view.x + (view.right / 2) + 15).toInt()
169+
binding.backgroundProgress.layoutParams = layoutParams
170+
171+
true
172+
}
173+
}
174+
175+
private fun setTextVisibility(alpha: Float) {
176+
binding.textView.alpha = alpha
177+
}
178+
179+
/**
180+
* Set listener for SwipeButton
181+
*/
182+
fun setListener(onSwipeButtonListener: OnSwipeButtonListener) {
183+
this.onSwipeButtonListener = onSwipeButtonListener
184+
}
185+
186+
/**
187+
* Set SwipeButton text
188+
*/
189+
fun setText(text: String) {
190+
binding.textView.text = text
191+
}
192+
193+
/**
194+
* Set SwipeButton default text
195+
*/
196+
fun setDefaultText(defaultText: String) {
197+
this.defaultText = defaultText
198+
}
199+
200+
/**
201+
* Reload SwipeButton, set 'setDefaultText = true' if you want return defaultText
202+
*/
203+
fun reload(setDefaultText: Boolean = false) {
204+
binding.thumb.isEnabled = true
205+
binding.thumb.isClickable = true
206+
207+
binding.thumb.x = 0F
208+
val layoutParams = binding.backgroundProgress.layoutParams
209+
layoutParams.width = 0
210+
binding.backgroundProgress.layoutParams = layoutParams
211+
212+
if (setDefaultText)
213+
binding.textView.text = defaultText
214+
}
215+
216+
interface OnSwipeButtonListener {
217+
fun onSwiped()
218+
}
219+
220+
override fun onViewRemoved(child: View?) {
221+
super.onViewRemoved(child)
222+
223+
_binding = null
224+
onSwipeButtonListener = null
225+
226+
animation?.cancel()
227+
animation = null
228+
}
229+
230+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<vector android:autoMirrored="true" android:height="24dp"
2+
android:tint="#000000" android:viewportHeight="24"
3+
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
4+
<path android:fillColor="@android:color/white" android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
5+
</vector>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android">
3+
<corners android:radius="1000dp"/>
4+
<solid android:color="#a0dcee"/>
5+
</shape>

0 commit comments

Comments
 (0)