Skip to content

Commit fef0ed4

Browse files
committed
default check state disabled bug fixes and animate first time option added
further improvements
1 parent 856af65 commit fef0ed4

7 files changed

Lines changed: 187 additions & 117 deletions

File tree

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ package com.devzone.ctv_sample
22

33
import android.os.Bundle
44
import android.view.View
5-
import android.view.animation.*
5+
import android.view.animation.AnticipateOvershootInterpolator
6+
import android.view.animation.BounceInterpolator
7+
import android.view.animation.LinearInterpolator
68
import androidx.appcompat.app.AppCompatActivity
9+
import com.devzone.checkabletextview.CheckableTextView
10+
import com.devzone.checkabletextview.CheckableTextView.Companion.SCALE
11+
import com.devzone.checkabletextview.CheckableTextView.Companion.TRANSLATE
712
import com.devzone.checkabletextview.CheckedListener
813
import kotlinx.android.synthetic.main.activity_main.*
914

@@ -13,25 +18,27 @@ class MainActivity : AppCompatActivity(), CheckedListener {
1318
super.onCreate(savedInstanceState)
1419
setContentView(R.layout.activity_main)
1520

16-
checkedTV.setOnCheckChangeListener { view, isChecked -> stateTV.text = if (isChecked) "Checked" else "Unchecked" }
21+
checkedTV.setOnCheckChangeListener { view, isChecked -> stateTV.text = if (isChecked) "Checked" else "Unchecked"}
1722
checkedSecondTV.setOnCheckChangeListener(::checkHandler) // function as parameter
1823
checkedThirdTV.setOnCheckChangeListener(this@MainActivity)
1924

2025

21-
checkedTV.setAnimInterpolator(AnticipateOvershootInterpolator()) // setting custom interpolator
22-
checkedSecondTV.setAnimInterpolator(LinearInterpolator())
23-
checkedThirdTV.setAnimInterpolator(BounceInterpolator())
26+
checkedTV.animInterpolator = AnticipateOvershootInterpolator() // setting custom interpolator
27+
checkedSecondTV.animInterpolator = LinearInterpolator()
28+
checkedThirdTV.animInterpolator = BounceInterpolator()
2429

2530
checkedThirdTV.setAnimDuration(1000)
2631
}
2732

2833
private fun checkHandler(view: View, isChecked: Boolean) {
34+
2935
when (view.id) {
3036
R.id.checkedSecondTV -> stateSecondTV.text = if (isChecked) "Checked" else "Unchecked"
3137
}
3238
}
3339

3440
override fun onCheckChange(view: View, isChecked: Boolean) { //lagacy type listener callback
41+
checkedSecondTV.setAnimStyle(if(isChecked)TRANSLATE else SCALE)
3542
when (view.id) {
3643
R.id.checkedThirdTV -> stateThirdTV.text = if (isChecked) "Checked" else "Unchecked"
3744
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
android:id="@+id/checkedTV"
1515
app:ctv_TextStyle="@style/TextAppearance.General"
1616
app:ctv_IconTint="@color/colorPrimary"
17-
app:ctv_IconChecked="true"
17+
app:ctv_IconChecked="false"
1818
android:background="#e8e8e8"
1919
app:ctv_Text="@string/app_name"
2020
app:ctv_AnimDuration="1250"
@@ -39,8 +39,7 @@
3939
android:background="#e8e8e8"
4040
android:id="@+id/checkedSecondTV"
4141
app:ctv_TextStyle="@style/TextAppearance.General"
42-
43-
app:ctv_IconChecked="true"
42+
app:ctv_IconChecked="false"
4443
app:ctv_AnimType="scale"
4544
app:ctv_Icon="@drawable/ic_cancel_custom_vector"
4645
app:ctv_Text="@string/app_name"
@@ -66,7 +65,7 @@
6665
android:id="@+id/checkedThirdTV"
6766
app:ctv_TextStyle="@style/TextAppearance.General"
6867
app:ctv_IconTint="@color/colorOrange"
69-
app:ctv_IconChecked="true"
68+
app:ctv_IconChecked="false"
7069
app:ctv_AnimType="fall_down"
7170
app:ctv_Text="@string/app_name"
7271
android:layout_width="match_parent"

checkabletextview/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
apply plugin: 'com.android.library'
2-
apply plugin: 'kotlin-android-extensions'
32
apply plugin: 'kotlin-android'
3+
apply plugin: 'kotlin-android-extensions'
44
apply plugin: 'com.github.dcendents.android-maven'
55

66
group='com.github.JDevZone'

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

Lines changed: 50 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,50 @@ import android.util.TypedValue
1111
import android.view.Gravity
1212
import android.view.LayoutInflater
1313
import android.view.View
14-
import android.view.ViewPropertyAnimator
14+
import android.view.ViewTreeObserver.OnGlobalLayoutListener
1515
import android.view.animation.LinearInterpolator
1616
import android.widget.RelativeLayout
1717
import androidx.annotation.*
1818
import androidx.appcompat.app.AppCompatDelegate
1919
import 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
2024
import kotlinx.android.synthetic.main.layout_checkable_text.view.*
2125

22-
2326
class 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

Comments
 (0)