11package com.devzone.checkabletextview
22
3+ import android.animation.TimeInterpolator
34import android.content.Context
45import android.content.res.TypedArray
6+ import android.graphics.Color
57import android.graphics.drawable.ColorDrawable
68import android.os.Build
79import android.util.AttributeSet
810import android.util.TypedValue
911import android.view.Gravity
1012import android.view.LayoutInflater
1113import android.view.View
14+ import android.view.ViewPropertyAnimator
15+ import android.view.animation.LinearInterpolator
1216import android.widget.RelativeLayout
1317import androidx.annotation.*
1418import androidx.appcompat.app.AppCompatDelegate
@@ -18,21 +22,37 @@ import kotlinx.android.synthetic.main.layout_checkable_text.view.*
1822
1923class 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}
0 commit comments