Skip to content

Commit

Permalink
Merge pull request #13 from ozgurg/3.0
Browse files Browse the repository at this point in the history
release: v3.0.0
  • Loading branch information
ozgurg authored Aug 6, 2022
2 parents 5438f5d + 71254e4 commit 149179b
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 410 deletions.
50 changes: 30 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ allprojects {
}
```

If the above doesn't work, try the following:
If the above doesn't work for you, try the following:

```gradle
// settings.gradle
Expand All @@ -39,7 +39,7 @@ dependencyResolutionManagement {
```gradle
// App level build.gradle
dependencies {
implementation "com.github.ozgurg:ToggleIconView:2.3.0"
implementation "com.github.ozgurg:ToggleIconView:3.0.0"
}
```

Expand All @@ -55,31 +55,43 @@ dependencies {
## API

ToggleIconView, uses `AnimatedVectorDrawableCompat` under the hood and extends `AppCompatImageView`. So you can do
anything you can with `AppCompatImageView`.
anything you can with `AppCompatImageView` like tinting and other stuff. That's why I called it "collection library".

### Attributes

| Attribute | Description | Type | Default |
|---------------|------------------------------------|-----------|---------|
| `app:checked` | Sets the initial state of the icon | `boolean` | `false` |
| Attribute | Description | Type | Default |
|-----------------------------------|------------------------------------------------------------|-----------|---------|
| `app:checked` | Sets the initial state of the icon | `Boolean` | `false` |
| `app:checkedContentDescription` | Sets the initial checked content description of the icon | `String` | `null` |
| `app:uncheckedContentDescription` | Sets the initial unchecked content description of the icon | `String` | `null` |
| `app:checkedTooltipText` | Sets the initial checked tooltip text of the icon | `String` | `null` |
| `app:uncheckedTooltipText` | Sets the initial unchecked tooltip text of the icon | `String` | `null` |

### Methods

| Method | Description | Return |
|--------------------------------|-------------------------------------------------------------|-----------|
| `toggle()` | Toggles between the checked and unchecked state of the icon | `void` |
| `isChecked()` | Returns whether the icon is checked | `boolean` |
| `setChecked(checked: Boolean)` | Sets the checked state of the icon | `void` |
| Method | Description | Return |
|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------|-----------|
| `toggle()` | Toggles between the checked and unchecked state of the icon | - |
| `.isChecked` <br /> `isChecked()` | Returns the checked state of the icon | `Boolean` |
| `.isChecked = Boolean` <br /> `setChecked(isChecked: Boolean)` | Sets the checked state of the icon | - |
| `.checkedContentDescription` <br /> `getCheckedContentDescription()` | Returns the checked content description of the icon | `String?` |
| `.checkedContentDescription = String?` <br /> `setCheckedContentDescription(value: String?)` | Sets the checked content description of the icon | - |
| `.uncheckedContentDescription` <br /> `getUncheckedContentDescription()` | Returns the unchecked content description of the icon | `String?` |
| `.uncheckedContentDescription = String?` <br /> `setUncheckedContentDescription(value: String?)` | Sets the unchecked content description of the icon | - |
| `.checkedTooltipText` <br /> `getCheckedTooltipText()` | Returns the checked tooltip text of the icon | `String?` |
| `.checkedTooltipText = String?` <br /> `setCheckedTooltipText(value: String?)` | Sets the checked tooltip text of the icon | - |
| `.uncheckedTooltipText` <br /> `getUncheckedTooltipText()` | Returns the unchecked tooltip text of the icon | `String?` |
| `.uncheckedTooltipText = String?` <br /> `setUncheckedTooltipText(value: String?)` | Sets the unchecked tooltip text of the icon | - |

### Events

| Event | Description |
|------------------------------------------------------------------------|-----------------------------------------------------|
| `onCheckedChanged(toggleIconView: ToggleIconView, isChecked: Boolean)` | Triggers when the checked state of the icon changed |
| Event | Description |
|--------------------------------------------------------------|-----------------------------------------------------|
| `onCheckedChanged(view: ToggleIconView, isChecked: Boolean)` | Triggers when the checked state of the icon changed |

## Built-in icons

_All icons have the same duration and interpolator, but I manually capture their previews; so timings may look different._
_All icons have the same duration (`@android:integer/config_shortAnimTime`) and interpolator (`@android:interpolator/fast_out_slow_in`), but I manually capture their previews; so timings may look different._

| Preview | Package |
|-----------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
Expand All @@ -90,19 +102,17 @@ _All icons have the same duration and interpolator, but I manually capture their
| ![](https://raw.githubusercontent.com/ozgurg/ToggleIconView/master/.github/preview/sharp/FlashOnOff.gif) | [Sharp] FlashOnOff<br />`og.android.lib.toggleiconview.sharp.FlashOnOff` |
| ![](https://raw.githubusercontent.com/ozgurg/ToggleIconView/master/.github/preview/sharp/AirplaneModeOnOff.gif) | [Sharp] AirplaneModeOnOff<br />`og.android.lib.toggleiconview.sharp.AirplaneModeOnOff` |

## How I create my own icon?
## How do you create your custom icon?

### 1) Create an icon

First, you need to create an `AnimatedVectorDrawable` icon.

I highly recommend using [Shape Shifter](https://shapeshifter.design/).
First, you need to create an `AnimatedVectorDrawable` icon. I highly recommend using [Shape Shifter](https://shapeshifter.design/).

After creating and exporting your icon, add the icon to your project's `drawable` folder.

### 2) Implement the icon

Just extend `ToggleIconView` class and set checked and unchecked icon you want to use.
Extend `ToggleIconView` class and set checked and unchecked icon you created.

``` kotlin
package [PACKAGE_NAME]
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id "com.android.application" version "7.2.1" apply false
id "com.android.library" version "7.2.1" apply false
id "com.android.application" version "7.2.2" apply false
id "com.android.library" version "7.2.2" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}

Expand Down
2 changes: 1 addition & 1 deletion lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ afterEvaluate {

groupId = "com.github.ozgurg"
artifactId = "toggle-icon-view"
version = "2.3.0"
version = "3.0.0"
}
}
}
Expand Down
152 changes: 116 additions & 36 deletions lib/src/main/java/og/android/lib/toggleiconview/ToggleIconView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,82 +11,162 @@ abstract class ToggleIconView @JvmOverloads constructor(
@DrawableRes checkedDrawableResId: Int,
@DrawableRes uncheckedDrawableResId: Int,
) : AppCompatImageView(context, attrs, defStyleAttr) {
private lateinit var checkedDrawable: AnimatedVectorDrawableCompat
private lateinit var uncheckedDrawable: AnimatedVectorDrawableCompat
private lateinit var mCheckedDrawable: AnimatedVectorDrawableCompat
private lateinit var mUncheckedDrawable: AnimatedVectorDrawableCompat

private var isChecked: Boolean = false
private var onCheckedChangeListener: ((toggleIconView: ToggleIconView, isChecked: Boolean) -> Unit)? = null
private var mCheckedContentDescription: String? = null
private var mUncheckedContentDescription: String? = null

private var mCheckedTooltipText: String? = null
private var mUncheckedTooltipText: String? = null

private var mIsChecked: Boolean = false

private var mOnCheckedChangeListener: ((view: ToggleIconView, isChecked: Boolean) -> Unit)? = null

init {
setCheckedDrawable(checkedDrawableResId)
setUncheckedDrawable(uncheckedDrawableResId)
createAndSetCheckedDrawable(checkedDrawableResId)
createAndSetUncheckedDrawable(uncheckedDrawableResId)
handleAttributes(attrs, defStyleAttr)
}

private fun setCheckedDrawable(@DrawableRes drawableResId: Int) {
checkedDrawable = AnimatedVectorDrawableCompat.create(context, drawableResId)!!
private fun createAndSetCheckedDrawable(@DrawableRes checkedDrawableResId: Int) {
mCheckedDrawable = AnimatedVectorDrawableCompat.create(context, checkedDrawableResId)!!
}

private fun setUncheckedDrawable(@DrawableRes drawableResId: Int) {
uncheckedDrawable = AnimatedVectorDrawableCompat.create(context, drawableResId)!!
private fun createAndSetUncheckedDrawable(@DrawableRes uncheckedDrawableResId: Int) {
mUncheckedDrawable = AnimatedVectorDrawableCompat.create(context, uncheckedDrawableResId)!!
}

private fun setAndAnimateCheckedDrawable() {
setImageDrawable(checkedDrawable)
checkedDrawable.start()
setImageDrawable(mCheckedDrawable)
mCheckedDrawable.start()
}

private fun setAndAnimateUncheckedDrawable() {
setImageDrawable(uncheckedDrawable)
uncheckedDrawable.start()
setImageDrawable(mUncheckedDrawable)
mUncheckedDrawable.start()
}

private fun setCheckStateAndAnimateDrawable(isChecked: Boolean) {
setAndAnimateDrawableByCheckState(isChecked)
mIsChecked = isChecked
}

private fun setAndAnimateDrawableByCheckState(checked: Boolean) {
if (checked) {
private fun setAndAnimateDrawableByCheckState(isChecked: Boolean) {
if (isChecked) {
setAndAnimateCheckedDrawable()
} else {
setAndAnimateUncheckedDrawable()
}
}

private fun handleCheckState(checked: Boolean) {
setAndAnimateDrawableByCheckState(checked)
isChecked = checked
private fun setContentDescriptionByCheckState(isChecked: Boolean) {
contentDescription = if (isChecked) {
mCheckedContentDescription
} else {
mUncheckedContentDescription
}
}

private fun setTooltipTextByCheckState(isChecked: Boolean) {
tooltipText = if (isChecked) {
mCheckedTooltipText
} else {
mUncheckedTooltipText
}
}

private fun handleAttributes(attrs: AttributeSet? = null, defStyleAttr: Int = 0) {
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ToggleIconView, defStyleAttr, 0)
val typedArray = context.theme.obtainStyledAttributes(
attrs,
R.styleable.ToggleIconView,
defStyleAttr,
0
)

try {
// app:checked
val checked = typedArray.getBoolean(R.styleable.ToggleIconView_checked, isChecked)
val checked = typedArray.getBoolean(R.styleable.ToggleIconView_checked, mIsChecked)

// app:checkedContentDescription
val checkedContentDescription = typedArray.getString(R.styleable.ToggleIconView_checkedContentDescription)
mCheckedContentDescription = checkedContentDescription

// app:uncheckedContentDescription
val uncheckedContentDescription = typedArray.getString(R.styleable.ToggleIconView_uncheckedContentDescription)
mUncheckedContentDescription = uncheckedContentDescription

// app:checkedTooltipText
val checkedTooltipText = typedArray.getString(R.styleable.ToggleIconView_checkedTooltipText)
mCheckedTooltipText = checkedTooltipText

// app:uncheckedTooltipText
val uncheckedTooltipText = typedArray.getString(R.styleable.ToggleIconView_uncheckedTooltipText)
mUncheckedTooltipText = uncheckedTooltipText

handleCheckState(checked)
handleCheckStateChange(checked)
} finally {
typedArray.recycle()
}
}

fun toggle() {
setChecked(!isChecked)
private fun handleCheckStateChange(isChecked: Boolean) {
setCheckStateAndAnimateDrawable(isChecked)
setContentDescriptionByCheckState(isChecked)
setTooltipTextByCheckState(isChecked)
}

fun isChecked(): Boolean {
return isChecked
private fun invokeOnCheckedChangeListener(isChecked: Boolean) {
mOnCheckedChangeListener?.invoke(this, isChecked)
}

fun setChecked(checked: Boolean) {
// We won't update the status if the status is the same as the current status
// This is to prevent the animation from restarting when the state is set again
if (checked == isChecked) {
return
}
private fun isStateSame(previousState: Boolean, currentState: Boolean): Boolean {
return previousState == currentState
}

handleCheckState(checked)
onCheckedChangeListener?.invoke(this, checked)
fun toggle() {
isChecked = !isChecked
}

open fun setOnCheckedChangeListener(listener: (toggleIconView: ToggleIconView, isChecked: Boolean) -> Unit) {
onCheckedChangeListener = listener
var checkedContentDescription: String?
get() = mCheckedContentDescription
set(value) {
mCheckedContentDescription = value
}

var uncheckedContentDescription: String?
get() = mUncheckedContentDescription
set(value) {
mUncheckedContentDescription = value
}

var checkedTooltipText: String?
get() = mCheckedTooltipText
set(value) {
mCheckedTooltipText = value
}

var uncheckedTooltipText: String?
get() = mUncheckedTooltipText
set(value) {
mUncheckedTooltipText = value
}

var isChecked: Boolean
get() = mIsChecked
set(isChecked) {
// We do not update the state if the state is the same as the current state
// to prevent the animation from restarting when the state is set again
if (isStateSame(isChecked, mIsChecked)) {
return
}

handleCheckStateChange(isChecked)
invokeOnCheckedChangeListener(isChecked)
}

open fun setOnCheckedChangeListener(listener: (view: ToggleIconView, isChecked: Boolean) -> Unit) {
mOnCheckedChangeListener = listener
}
}
4 changes: 4 additions & 0 deletions lib/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
<resources>
<declare-styleable name="ToggleIconView">
<attr name="checked" format="boolean" />
<attr name="checkedContentDescription" format="string" />
<attr name="uncheckedContentDescription" format="string" />
<attr name="checkedTooltipText" format="string" />
<attr name="uncheckedTooltipText" format="string" />
</declare-styleable>
</resources>
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package og.android.sample.toggleiconview

import android.os.Bundle
import android.util.Log
import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import og.android.lib.toggleiconview.ToggleIconView

Expand Down Expand Up @@ -47,12 +47,18 @@ class MainActivity : AppCompatActivity() {
toggleIconView.toggle()
}

toggleIconView.setOnCheckedChangeListener { _: ToggleIconView, isChecked: Boolean ->
Toast.makeText(
this,
"onCheckedChanged: $isChecked",
Toast.LENGTH_SHORT
).show()
toggleIconView.setOnLongClickListener {
// Just to use its setter
toggleIconView.isChecked = !toggleIconView.isChecked
false
}

toggleIconView.setOnCheckedChangeListener { view: ToggleIconView, _: Boolean ->
val value = "[${view::class.qualifiedName.toString()}:onCheckedChanged]\n" +
"isChecked: ${toggleIconView.isChecked}\n" +
"tooltipText: ${toggleIconView.tooltipText}\n" +
"contentDescription: ${toggleIconView.contentDescription}"
Log.d("TOGGLEICONVIEW_SAMPLE", value)
}
}
}
Expand Down
Loading

0 comments on commit 149179b

Please sign in to comment.