@@ -11,38 +11,50 @@ import android.util.TypedValue
1111import android.view.Gravity
1212import android.view.LayoutInflater
1313import android.view.View
14- import android.view.ViewPropertyAnimator
14+ import android.view.ViewTreeObserver.OnGlobalLayoutListener
1515import android.view.animation.LinearInterpolator
1616import android.widget.RelativeLayout
1717import androidx.annotation.*
1818import androidx.appcompat.app.AppCompatDelegate
1919import androidx.core.content.ContextCompat
20+ import com.devzone.checkabletextview.utils.AnimatorFactory.Companion.getAnimator
21+ import com.devzone.checkabletextview.utils.ViewUtils.Companion.getRippleDrawable
22+ import com.devzone.checkabletextview.utils.ViewUtils.Companion.getThemeAccentColor
23+ import com.devzone.checkabletextview.utils.ViewUtils.Companion.resetView
2024import kotlinx.android.synthetic.main.layout_checkable_text.view.*
2125
22-
2326class CheckableTextView : RelativeLayout {
2427
2528 companion object {
2629 const val SCALE = 0
2730 const val TRANSLATE = 1
2831 const val FALL_DOWN = 2
32+ // -------------------------------------------------------------------------------//
33+ const val MIN_VALUE = 0f // (initial values for scale, translate etc.)
34+ const val MAX_SCALE = 1f // (max values for scale)
35+ const val MAX_ROTATION = 360f // (max values for rotation)
2936 }
3037
31- // default values
38+
39+ private val defaultAnimFirstTime = true
3240 private val defaultResValue: Int = 0
3341 private val defaultAnimDuration: Long = 300
34- private val defaultAnimateStyle: Int = SCALE
35- private val defaultCheckState: Boolean = true
42+ private val defaultAnimateStyle = SCALE
43+ private val defaultCheckState = true
3644 private val defaultTextColor = android.R .color.black
3745 private val defaultCheckIcon = R .drawable.ic_check_circle_vector
3846
3947 // initialise with default values
4048 private var checkIcon = defaultCheckIcon
41- private var animateStyle = defaultAnimateStyle
42- private var isChecked: Boolean = defaultCheckState
43- private var animDuration: Long = defaultAnimDuration
44- private var animInterpolator: TimeInterpolator = LinearInterpolator ()
49+ var animInterpolator: TimeInterpolator = LinearInterpolator ()
50+ var animateFirstTime = defaultAnimFirstTime
4551
52+ var isChecked = defaultCheckState
53+ private set
54+ var animDuration = defaultAnimDuration
55+ private set
56+ var animateStyle = defaultAnimateStyle
57+ private set
4658
4759 // check change listeners
4860 private var listener: CheckedListener ? = null // Legacy type callback listener using interface (Both java & kotlin)
@@ -73,12 +85,29 @@ class CheckableTextView : RelativeLayout {
7385
7486 private fun init (context : Context , attributeSet : AttributeSet ? ) {
7587 AppCompatDelegate .setCompatVectorFromResourcesEnabled(true )
76-
77- LayoutInflater .from(context).inflate(
88+ val root = LayoutInflater .from(context).inflate(
7889 R .layout.layout_checkable_text,
7990 this , true )
80- attributeSet?.let {
81- val array: TypedArray = context.obtainStyledAttributes(it, R .styleable.CheckableTextView )
91+
92+ initValuesFromAttrs(attributeSet, context)
93+
94+ root.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
95+ override fun onGlobalLayout () {
96+ root.viewTreeObserver.removeOnGlobalLayoutListener(this )
97+ if (animateFirstTime) {
98+ resetView(animateStyle, checkedIV, ! isChecked)
99+ animateView(checkedIV, isChecked)
100+ } else
101+ resetView(animateStyle, checkedIV, isChecked)
102+
103+ }
104+ })
105+ rootRL.setOnClickListener(clickListener())
106+ }
107+
108+ private fun initValuesFromAttrs (attributeSet : AttributeSet ? , context : Context ) {
109+ attributeSet?.apply {
110+ val array: TypedArray = context.obtainStyledAttributes(this , R .styleable.CheckableTextView )
82111 if (array.length() > 0 ) {
83112 val iconTint = array.getColor(
84113 R .styleable.CheckableTextView_ctv_IconTint ,
@@ -89,6 +118,8 @@ class CheckableTextView : RelativeLayout {
89118 ContextCompat .getColor(context, defaultTextColor)
90119 )
91120 val text = array.getString(R .styleable.CheckableTextView_ctv_Text )
121+ animateFirstTime =
122+ array.getBoolean(R .styleable.CheckableTextView_ctv_AnimFirstTime , defaultAnimFirstTime)
92123 isChecked = array.getBoolean(R .styleable.CheckableTextView_ctv_IconChecked , defaultCheckState)
93124 val textSize = array.getDimensionPixelSize(R .styleable.CheckableTextView_ctv_TextSize , defaultResValue)
94125 val textStyle = array.getResourceId(R .styleable.CheckableTextView_ctv_TextStyle , defaultResValue)
@@ -115,9 +146,6 @@ class CheckableTextView : RelativeLayout {
115146 }
116147 array.recycle()
117148 }
118-
119- animateView(checkedIV, isChecked)
120- rootRL.setOnClickListener(clickListener())
121149 }
122150
123151 private fun clickListener (): (v: View ) -> Unit {
@@ -141,50 +169,10 @@ class CheckableTextView : RelativeLayout {
141169
142170 private fun animateView (view : View , show : Boolean ) {
143171 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- }
172+ val animator = getAnimator(animateStyle, view, show, animDuration)
150173 animator.setInterpolator(animInterpolator).start()
151174 }
152175
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
157-
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
180-
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)
186- }
187-
188176 private fun validateCheckIcon (context : Context ) {
189177 if (isValidRes(checkIcon)) {
190178 val drawableIcon = ContextCompat .getDrawable(context, checkIcon)
@@ -197,43 +185,16 @@ class CheckableTextView : RelativeLayout {
197185 } else checkIcon = defaultCheckIcon
198186 }
199187
200-
201188 private fun isValidRes (res : Int ) = res != defaultResValue
202- private fun emptyNullCheck (text : String? ) = text != null && ! text.isBlank();
189+ private fun emptyNullCheck (text : String? ) = text != null && ! text.isBlank()
203190
204191 private fun notifyListener (isChecked : Boolean ) {
205192 listener?.onCheckChange(this , isChecked)
206193 listenerNew?.invoke(this , isChecked)
207194 }
208195
209-
210- private fun getRippleDrawable (): Int {
211- val outValue = TypedValue ()
212- context.theme.resolveAttribute(android.R .attr.selectableItemBackground, outValue, true )
213- return outValue.resourceId
214- }
215-
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- }
232-
233196 /* -------------------------------------------------public functions------------------------------------------------------------------------------------------*/
234197
235-
236-
237198 /* *
238199 * Change [CheckableTextView] click state
239200 * @param isClickable = pass true for enable clicks and false for disable clicks.
@@ -242,7 +203,7 @@ class CheckableTextView : RelativeLayout {
242203 fun setClickEnabled (isClickable : Boolean ) {
243204 // 0.5 second delay added to ongoing ripple animation to complete (if any)
244205 rootRL.postDelayed(
245- { rootRL.setBackgroundResource(if (isClickable) getRippleDrawable() else android.R .color.transparent) },
206+ { rootRL.setBackgroundResource(if (isClickable) getRippleDrawable(context ) else android.R .color.transparent) },
246207 500
247208 )
248209 rootRL.setOnClickListener(if (isClickable) clickListener() else null )
@@ -253,27 +214,18 @@ class CheckableTextView : RelativeLayout {
253214 this .listenerNew = null
254215 }
255216
256-
257217 fun setOnCheckChangeListener (listenerNew : (view: View , isChecked: Boolean ) -> Unit ) {
258218 this .listener = null
259219 this .listenerNew = listenerNew
260220 }
261221
262- fun setChecked (isChecked : Boolean , shouldNotifyListeners : Boolean = false) {
222+ fun setChecked (isChecked : Boolean , shouldNotifyListeners : Boolean = false) {
263223 this .isChecked = isChecked
264224 animateView(checkedIV, isChecked)
265225 if (shouldNotifyListeners)
266226 notifyListener(isChecked)
267227 }
268228
269-
270- fun isChecked (): Boolean {
271- return this .isChecked
272- }
273-
274-
275- // //---------------------------setters------------------------------------------------------------------------------------------////
276-
277229 fun setIconTint (@ColorRes resId : Int ) {
278230 if (isValidRes(resId)) {
279231 val color = ContextCompat .getColor(context, resId)
@@ -330,21 +282,12 @@ class CheckableTextView : RelativeLayout {
330282 * @param animType should be [SCALE],[TRANSLATE],[FALL_DOWN]
331283 */
332284 fun setAnimStyle (animType : Int ) {
333- animateStyle = when (animType) {
334- SCALE -> SCALE
335- TRANSLATE -> TRANSLATE
336- FALL_DOWN -> FALL_DOWN
337- else -> SCALE
338- }
285+ animateStyle = if (animType in SCALE .. FALL_DOWN ) animType else animateStyle
339286 }
340287
341288 fun setAnimDuration (duration : Long ) {
342- if (duration.toInt() == 0 || duration < 0 ) return
289+ if (duration == 0L || duration < 0 ) return
343290 animDuration = duration
344291 }
345292
346- fun setAnimInterpolator (interpolator : TimeInterpolator ) {
347- animInterpolator = interpolator
348- }
349-
350293}
0 commit comments