Skip to content

Commit 88edb94

Browse files
authored
Merge pull request #9 from JDevZone/dev_new
custom interpolator support added, new fall down animation added and …
2 parents 6ae5ec1 + 856af65 commit 88edb94

File tree

7 files changed

+158
-43
lines changed

7 files changed

+158
-43
lines changed

app/src/main/java/com/devzone/ctv_sample/MainActivity.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.devzone.ctv_sample
22

33
import android.os.Bundle
44
import android.view.View
5+
import android.view.animation.*
56
import androidx.appcompat.app.AppCompatActivity
67
import com.devzone.checkabletextview.CheckedListener
78
import kotlinx.android.synthetic.main.activity_main.*
@@ -11,14 +12,29 @@ class MainActivity : AppCompatActivity(), CheckedListener {
1112
override fun onCreate(savedInstanceState: Bundle?) {
1213
super.onCreate(savedInstanceState)
1314
setContentView(R.layout.activity_main)
14-
checkedTV.setOnCheckChangeListener(this@MainActivity)
15-
checkedSecondTV.setOnCheckChangeListener(this@MainActivity)
15+
16+
checkedTV.setOnCheckChangeListener { view, isChecked -> stateTV.text = if (isChecked) "Checked" else "Unchecked" }
17+
checkedSecondTV.setOnCheckChangeListener(::checkHandler) // function as parameter
18+
checkedThirdTV.setOnCheckChangeListener(this@MainActivity)
19+
20+
21+
checkedTV.setAnimInterpolator(AnticipateOvershootInterpolator()) // setting custom interpolator
22+
checkedSecondTV.setAnimInterpolator(LinearInterpolator())
23+
checkedThirdTV.setAnimInterpolator(BounceInterpolator())
24+
25+
checkedThirdTV.setAnimDuration(1000)
1626
}
1727

18-
override fun onCheckChange(view: View, isChecked: Boolean) {
28+
private fun checkHandler(view: View, isChecked: Boolean) {
1929
when (view.id) {
20-
R.id.checkedTV -> stateTV.text = if (isChecked) "Checked" else "Unchecked"
2130
R.id.checkedSecondTV -> stateSecondTV.text = if (isChecked) "Checked" else "Unchecked"
2231
}
2332
}
33+
34+
override fun onCheckChange(view: View, isChecked: Boolean) { //lagacy type listener callback
35+
when (view.id) {
36+
R.id.checkedThirdTV -> stateThirdTV.text = if (isChecked) "Checked" else "Unchecked"
37+
}
38+
}
39+
2440
}

app/src/main/res/layout/activity_main.xml

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
app:ctv_TextStyle="@style/TextAppearance.General"
1616
app:ctv_IconTint="@color/colorPrimary"
1717
app:ctv_IconChecked="true"
18+
android:background="#e8e8e8"
1819
app:ctv_Text="@string/app_name"
19-
app:ctv_AnimDuration="2000"
20+
app:ctv_AnimDuration="1250"
2021
app:ctv_AnimType="translate"
2122
android:layout_width="match_parent"
2223
android:layout_height="wrap_content"/>
@@ -38,10 +39,9 @@
3839
android:background="#e8e8e8"
3940
android:id="@+id/checkedSecondTV"
4041
app:ctv_TextStyle="@style/TextAppearance.General"
41-
app:ctv_IconTint="@color/colorAccent"
42+
4243
app:ctv_IconChecked="true"
4344
app:ctv_AnimType="scale"
44-
app:ctv_AnimDuration="2000"
4545
app:ctv_Icon="@drawable/ic_cancel_custom_vector"
4646
app:ctv_Text="@string/app_name"
4747
android:layout_width="match_parent"
@@ -60,4 +60,31 @@
6060
android:layout_height="wrap_content"/>
6161

6262

63+
<com.devzone.checkabletextview.CheckableTextView
64+
android:layout_marginTop="20dp"
65+
android:background="#e8e8e8"
66+
android:id="@+id/checkedThirdTV"
67+
app:ctv_TextStyle="@style/TextAppearance.General"
68+
app:ctv_IconTint="@color/colorOrange"
69+
app:ctv_IconChecked="true"
70+
app:ctv_AnimType="fall_down"
71+
app:ctv_Text="@string/app_name"
72+
android:layout_width="match_parent"
73+
android:layout_height="wrap_content"/>
74+
75+
76+
<androidx.appcompat.widget.AppCompatTextView
77+
android:id="@+id/stateThirdTV"
78+
android:text="Checked"
79+
android:padding="5dp"
80+
android:layout_marginTop="5dp"
81+
android:layout_gravity="center_horizontal"
82+
style="@style/TextAppearance.General"
83+
android:gravity="center"
84+
android:layout_width="wrap_content"
85+
android:layout_height="wrap_content"/>
86+
87+
88+
89+
6390
</LinearLayout>

app/src/main/res/values/colors.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
<color name="colorPrimary">#008577</color>
44
<color name="colorPrimaryDark">#00574B</color>
55
<color name="colorAccent">#D81B60</color>
6+
<color name="colorOrange">#FF5722</color>
67
</resources>

checkabletextview/src/main/java/com/devzone/checkabletextview/CheckableTextView.kt

Lines changed: 106 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package com.devzone.checkabletextview
22

3+
import android.animation.TimeInterpolator
34
import android.content.Context
45
import android.content.res.TypedArray
6+
import android.graphics.Color
57
import android.graphics.drawable.ColorDrawable
68
import android.os.Build
79
import android.util.AttributeSet
810
import android.util.TypedValue
911
import android.view.Gravity
1012
import android.view.LayoutInflater
1113
import android.view.View
14+
import android.view.ViewPropertyAnimator
15+
import android.view.animation.LinearInterpolator
1216
import android.widget.RelativeLayout
1317
import androidx.annotation.*
1418
import androidx.appcompat.app.AppCompatDelegate
@@ -18,21 +22,37 @@ import kotlinx.android.synthetic.main.layout_checkable_text.view.*
1822

1923
class CheckableTextView : RelativeLayout {
2024

21-
2225
companion object {
2326
const val SCALE = 0
2427
const val TRANSLATE = 1
28+
const val FALL_DOWN = 2
2529
}
2630

27-
private val defaultAnimDuration: Long = 250
28-
private var isChecked: Boolean = true
29-
private var listener: CheckedListener? = null
30-
private val defaultCheckIcon = R.drawable.ic_check_circle_vector
31+
// default values
32+
private val defaultResValue: Int = 0
33+
private val defaultAnimDuration: Long = 300
34+
private val defaultAnimateStyle: Int = SCALE
35+
private val defaultCheckState: Boolean = true
3136
private val defaultTextColor = android.R.color.black
32-
private val defaultIconTintColor = android.R.color.transparent
37+
private val defaultCheckIcon = R.drawable.ic_check_circle_vector
38+
39+
//initialise with default values
3340
private var checkIcon = defaultCheckIcon
34-
private var animateStyle = SCALE
41+
private var animateStyle = defaultAnimateStyle
42+
private var isChecked: Boolean = defaultCheckState
3543
private var animDuration: Long = defaultAnimDuration
44+
private var animInterpolator: TimeInterpolator = LinearInterpolator()
45+
46+
47+
// check change listeners
48+
private var listener: CheckedListener? = null //Legacy type callback listener using interface (Both java & kotlin)
49+
50+
/**
51+
* [Function],[Function2] (for two variables)
52+
* kotlin.jvm.functions.Function2<View, Boolean, kotlin.Unit>() can be used with java code but requires Kotlin setup in project
53+
*/
54+
private var listenerNew: ((v: View, isChecked: Boolean) -> Unit)? =
55+
null //New type introduced by kotlin (Function2 , function as parameter)
3656

3757
constructor(context: Context) : super(context) {
3858
init(context, null)
@@ -57,22 +77,22 @@ class CheckableTextView : RelativeLayout {
5777
LayoutInflater.from(context).inflate(
5878
R.layout.layout_checkable_text,
5979
this, true)
60-
attributeSet.let {
61-
val array: TypedArray = context.obtainStyledAttributes(attributeSet, R.styleable.CheckableTextView)
80+
attributeSet?.let {
81+
val array: TypedArray = context.obtainStyledAttributes(it, R.styleable.CheckableTextView)
6282
if (array.length() > 0) {
6383
val iconTint = array.getColor(
6484
R.styleable.CheckableTextView_ctv_IconTint,
65-
ContextCompat.getColor(context, defaultIconTintColor)
85+
Color.parseColor(getThemeAccentColor(context))
6686
)
6787
val textColor = array.getColor(
6888
R.styleable.CheckableTextView_ctv_TextColor,
6989
ContextCompat.getColor(context, defaultTextColor)
7090
)
7191
val text = array.getString(R.styleable.CheckableTextView_ctv_Text)
72-
isChecked = array.getBoolean(R.styleable.CheckableTextView_ctv_IconChecked, false)
73-
val textSize = array.getDimensionPixelSize(R.styleable.CheckableTextView_ctv_TextSize, 0)
74-
val textStyle = array.getResourceId(R.styleable.CheckableTextView_ctv_TextStyle, 0)
75-
checkIcon = array.getResourceId(R.styleable.CheckableTextView_ctv_Icon, 0)
92+
isChecked = array.getBoolean(R.styleable.CheckableTextView_ctv_IconChecked, defaultCheckState)
93+
val textSize = array.getDimensionPixelSize(R.styleable.CheckableTextView_ctv_TextSize, defaultResValue)
94+
val textStyle = array.getResourceId(R.styleable.CheckableTextView_ctv_TextStyle, defaultResValue)
95+
checkIcon = array.getResourceId(R.styleable.CheckableTextView_ctv_Icon, defaultResValue)
7696
val gravity = array.getInt(R.styleable.CheckableTextView_ctv_TextGravity, Gravity.CENTER)
7797
animateStyle = array.getInt(R.styleable.CheckableTextView_ctv_AnimType, SCALE)
7898
val animDuration =
@@ -89,9 +109,9 @@ class CheckableTextView : RelativeLayout {
89109

90110
if (isValidRes(textSize))
91111
checkedTextTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize.toFloat())
112+
92113
if (isValidRes(iconTint))
93114
checkedIV.setColorFilter(iconTint)
94-
95115
}
96116
array.recycle()
97117
}
@@ -121,27 +141,48 @@ class CheckableTextView : RelativeLayout {
121141

122142
private fun animateView(view: View, show: Boolean) {
123143
view.clearAnimation()
144+
val animator = when (animateStyle) {
145+
SCALE -> getScaleAnimator(view, show)
146+
TRANSLATE -> getTranslateAnimator(view, show)
147+
FALL_DOWN -> getFallDownAnimator(view, show)
148+
else -> getScaleAnimator(view, show)
149+
}
150+
animator.setInterpolator(animInterpolator).start()
151+
}
124152

125-
when (animateStyle) {
126-
SCALE -> {
127-
view.translationX = 0f
128-
val scale = if (show) 1f else 0f
129-
val rotation = if (show) 0f else -360f
130-
view.animate().setStartDelay(20).scaleX(scale).scaleY(scale).rotation(rotation)
131-
.setDuration(animDuration)
132-
.start()
133-
}
134-
TRANSLATE -> {
135-
view.scaleX = 1f
136-
view.scaleY = 1f
137-
val translate = if (show) 0f else (view.width.toFloat() + view.width / 2)
138-
val rotation = if (show) 0f else 360f
139-
view.animate().setStartDelay(20).translationX(translate).rotation(rotation).setDuration(animDuration)
140-
.start()
141-
}
153+
private fun getScaleAnimator(view: View, show: Boolean): ViewPropertyAnimator {
154+
//resetting view to initial state for this animation (if In case user sets new animation on the fly)
155+
view.translationX = 0f
156+
view.translationY = 0f
142157

143-
}
158+
val scale = if (show) 1f else 0f
159+
val rotation = if (show) 0f else -360f
160+
return view.animate().setStartDelay(20).scaleX(scale).scaleY(scale).rotation(rotation)
161+
.setDuration(animDuration)
162+
}
163+
164+
private fun getTranslateAnimator(view: View, show: Boolean): ViewPropertyAnimator {
165+
view.scaleX = 1f
166+
view.scaleY = 1f
167+
view.translationY = 0f
168+
169+
val translate = if (show) 0f else (view.width.toFloat() + view.width / 2)
170+
val rotation = if (show) 0f else 360f
171+
return view.animate().setStartDelay(20).translationX(translate).rotation(rotation)
172+
.setDuration(animDuration)
173+
174+
}
175+
176+
private fun getFallDownAnimator(view: View, show: Boolean): ViewPropertyAnimator {
177+
view.scaleX = 1f
178+
view.scaleY = 1f
179+
view.rotation = 0f
144180

181+
val trValue = (view.height.toFloat() + view.height / 2)
182+
if (show) view.translationY = -trValue
183+
val translate = if (show) 0f else trValue
184+
return view.animate().setStartDelay(20).translationY(translate)
185+
.setDuration(animDuration)
145186
}
146187

147188
private fun validateCheckIcon(context: Context) {
@@ -157,11 +198,12 @@ class CheckableTextView : RelativeLayout {
157198
}
158199

159200

160-
private fun isValidRes(res: Int) = res != 0
201+
private fun isValidRes(res: Int) = res != defaultResValue
161202
private fun emptyNullCheck(text: String?) = text != null && !text.isBlank();
162203

163204
private fun notifyListener(isChecked: Boolean) {
164205
listener?.onCheckChange(this, isChecked)
206+
listenerNew?.invoke(this, isChecked)
165207
}
166208

167209

@@ -171,6 +213,22 @@ class CheckableTextView : RelativeLayout {
171213
return outValue.resourceId
172214
}
173215

216+
private fun getThemeAccentColor(context: Context): String {
217+
try {
218+
val colorAttr: Int
219+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
220+
colorAttr = android.R.attr.colorAccent
221+
} else {
222+
//Get colorAccent defined for AppCompat
223+
colorAttr = context.resources.getIdentifier("colorAccent", "attr", context.packageName)
224+
}
225+
val outValue = TypedValue()
226+
context.theme.resolveAttribute(colorAttr, outValue, true)
227+
return String.format("#%06X", 0xFFFFFF and outValue.data)
228+
} catch (e: Exception) {
229+
return "#00FFFFFF"
230+
}
231+
}
174232

175233
/*-------------------------------------------------public functions------------------------------------------------------------------------------------------*/
176234

@@ -191,7 +249,14 @@ class CheckableTextView : RelativeLayout {
191249
}
192250

193251
fun setOnCheckChangeListener(listener: CheckedListener) {
194-
this.listener = listener
252+
this.listener = listener //only one type listener will invoke
253+
this.listenerNew = null
254+
}
255+
256+
257+
fun setOnCheckChangeListener(listenerNew: (view: View, isChecked: Boolean) -> Unit) {
258+
this.listener = null
259+
this.listenerNew = listenerNew
195260
}
196261

197262
fun setChecked(isChecked: Boolean, shouldNotifyListeners: Boolean=false) {
@@ -262,12 +327,13 @@ class CheckableTextView : RelativeLayout {
262327
}
263328

264329
/**
265-
* @param animType should be [SCALE] OR [TRANSLATE]
330+
* @param animType should be [SCALE],[TRANSLATE],[FALL_DOWN]
266331
*/
267332
fun setAnimStyle(animType: Int) {
268333
animateStyle = when (animType) {
269334
SCALE -> SCALE
270335
TRANSLATE -> TRANSLATE
336+
FALL_DOWN -> FALL_DOWN
271337
else -> SCALE
272338
}
273339
}
@@ -277,4 +343,8 @@ class CheckableTextView : RelativeLayout {
277343
animDuration = duration
278344
}
279345

346+
fun setAnimInterpolator(interpolator: TimeInterpolator) {
347+
animInterpolator = interpolator
348+
}
349+
280350
}

checkabletextview/src/main/res/values/attrs.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<attr name="ctv_AnimType" format="enum">
1414
<enum name="scale" value="0"/>
1515
<enum name="translate" value="1"/>
16+
<enum name="fall_down" value="2"/>
1617
</attr>
1718
<attr name="ctv_TextGravity">
1819
<flag name="bottom" value="80" />

fall_down.gif

344 KB
Loading

overshoot.gif

164 KB
Loading

0 commit comments

Comments
 (0)