Skip to content

Commit 8a9e2f2

Browse files
committed
Support remove states programmatically #6
1 parent 97074f4 commit 8a9e2f2

File tree

7 files changed

+334
-156
lines changed

7 files changed

+334
-156
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
## `2.0.0` (22/03/21)
44

5-
- Update dependencies
5+
- Support remove states programmatically #6
6+
- Support nested scroll when used inside a RecyclerView or ViewPager #8
67
- Migrate to Kotlin #9
8+
- Update dependencies
79

810
**Breaking change:**
911
- Minimum API requirements: API >= 21 (Android 5.0 - LOLLIPOP)

README.md

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Android library that provides a multi state switch view.
66

77
## Usage
88

9+
Take a look at [the sample app](https://github.com/davidmigloz/multi-state-switch/tree/master/sample) to see a live example of the capabilities of the library.
10+
911
#### Step 1
1012

1113
Add the JitPack repository to your `build.gradle ` file:
@@ -41,19 +43,79 @@ Use `MultiStateSwitch` view in your layout:
4143
... />
4244
```
4345

44-
TODO
46+
Add states:
4547

46-
#### Attributes
48+
```kotlin
49+
switch.addStatesFromStrings(listOf("One", "Two", "Three"))
50+
```
4751

48-
TODO
52+
Use `StateStyle` to customize the style of the states:
4953

50-
#### API
54+
```kotlin
55+
switch.addStateFromString(
56+
"Cold",
57+
StateStyle.Builder()
58+
.withSelectedBackgroundColor(Color.BLUE)
59+
.withDisabledTextColor(Color.WHITE)
60+
.build()
61+
)
62+
```
5163

52-
TODO
64+
Use `State` object if you need to display different text depending on the state's state:
5365

54-
#### Callback
66+
```kotlin
67+
switch.addState(
68+
State(
69+
text = "ON",
70+
selectedText = "OFF",
71+
disabledText = "OFF"
72+
)
73+
)
74+
```
5575

56-
TODO
76+
#### Attributes
77+
78+
- `app:multistateswitch_background_color=[color|reference]`
79+
- `app:multistateswitch_text_color=[color|reference]`
80+
- `app:multistateswitch_text_size=[dimension|reference]`
81+
- `app:multistateswitch_selected_state_index=[integer|reference]`
82+
- `app:multistateswitch_selected_background_color=[color|reference]`
83+
- `app:multistateswitch_selected_text_color=[color|reference]`
84+
- `app:multistateswitch_selected_text_size=[dimension|reference]`
85+
- `app:multistateswitch_disabled_state_enabled=[boolean|reference]`
86+
- `app:multistateswitch_disabled_state_index=[integer|reference]`
87+
- `app:multistateswitch_disabled_background_color=[color|reference]`
88+
- `app:multistateswitch_disabled_text_color=[color|reference]`
89+
- `app:multistateswitch_disabled_text_size=[dimension|reference]`
90+
- `app:multistateswitch_max_number_states=[integer|reference]`
91+
92+
#### API
93+
94+
- `addState(state: State, stateStyle: StateStyle? = null)`: adds state to the switch.
95+
- `addStates(states: List<State>, stateStyles: List<StateStyle>? = null)`: adds states to the switch and the displaying styles. If you provide styles, you have to provide them for every state.
96+
- `addStateFromString(stateText: String, stateStyle: StateStyle? = null)`: adds state to the switch directly from a string. The text will be used for normal, selected and disabled state.
97+
- `addStatesFromStrings(statesTexts: List<String>, stateStyles: List<StateStyle>? = null)`: adds states to the switch directly from a string and the displaying styles. If you provide styles, you have to provide them for every state. The texts will be used for normal, selected and disabled states.
98+
- `replaceState(stateIndex: Int, state: State, stateStyle: StateStyle? = null)`: replaces state.
99+
- `replaceStateFromString(stateIndex: Int, stateText: String)`: replaces state directly from a string. The text will be used for normal, selected and disabled states.
100+
- `removeState(stateIndex: Int)`: removes an state.
101+
- `selectState(index: Int, notifyStateListeners: Boolean = true)`: selects state in given index. If `notifyStateListeners` is `true` all the listeners will be notified about the new selected state.
102+
- `getNumberStates(): Int`: returns number of states of the switch.
103+
- `setMaxNumberStates(maxNumberStates: Int)`: Sets the max number of states. If you try to add a new state but the number of states is already `maxNumberStates` the state will be ignored. By default is `-1` which means that there is no restriction. This parameter is also used to determine how many states to show in the editor preview. If it is set to no limit, `3` will be rendered by default, if not the number of states drawn will match `maxNumberStates`.
104+
- `getMaxNumberStates(): Int`: returns max number of states. By default is -1 which means that there is no restriction.
105+
- `hasMaxNumberStates(): Boolean`: checks whether there is a limit in the number of states or not.
106+
- `setTextTypeface(textTypeface: Typeface)`: sets typeface.
107+
- `setPadding(left: Int, top: Int, right: Int, bottom: Int)`
108+
109+
110+
#### Listener
111+
112+
To listen to state changes, you have to register a `StateListener`:
113+
114+
```kotlin
115+
binding.defaultSwitch.addStateListener { stateIndex, state ->
116+
// ...
117+
}
118+
```
57119

58120
## Contributing
59121

docs/multi-state-switch.gif

-6.2 MB
Loading

lib/src/main/java/com/davidmiguel/multistateswitch/MultiStateSwitch.kt

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ class MultiStateSwitch @JvmOverloads constructor(
149149
if (hasMaxNumberStates() && getNumberStates() >= getMaxNumberStates()) return
150150
states.add(state)
151151
stateStyle?.run { statesStyles.put(getNumberStates() - 1, stateStyle) }
152+
if(isAttachedToWindow){
153+
populateView()
154+
invalidate()
155+
}
152156
}
153157

154158
/**
@@ -164,7 +168,7 @@ class MultiStateSwitch @JvmOverloads constructor(
164168
}
165169

166170
/**
167-
* Adds state to the switch and the displaying style.
171+
* Adds state to the switch directly from a string and the displaying style.
168172
* The text will be used for normal, selected and disabled states.
169173
*/
170174
@JvmOverloads
@@ -173,7 +177,7 @@ class MultiStateSwitch @JvmOverloads constructor(
173177
}
174178

175179
/**
176-
* Adds states to the switch and the displaying styles.
180+
* Adds states to the switch directly from a string and the displaying styles.
177181
* If you provide styles, you have to provide them for every state.
178182
* The texts will be used for normal, selected and disabled states.
179183
*/
@@ -199,13 +203,29 @@ class MultiStateSwitch @JvmOverloads constructor(
199203
}
200204

201205
/**
202-
* Replaces state.
206+
* Replaces state directly from a string.
203207
* The text will be used for normal, selected and disabled states.
204208
*/
205-
fun replaceState(stateIndex: Int, stateText: String) {
209+
fun replaceStateFromString(stateIndex: Int, stateText: String) {
206210
replaceState(stateIndex, State(stateText))
207211
}
208212

213+
/**
214+
* Removes an state.
215+
*/
216+
fun removeState(stateIndex: Int) {
217+
require(stateIndex < getNumberStates()) { "State index doesn't exist" }
218+
states.removeAt(stateIndex)
219+
statesStyles.remove(stateIndex)
220+
if(stateIndex == currentStateIndex) {
221+
currentStateIndex = if(stateIndex == 0) 0 else stateIndex - 1
222+
}
223+
if(isAttachedToWindow){
224+
populateView()
225+
invalidate()
226+
}
227+
}
228+
209229
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
210230
// Called when the view is first assigned a size, and again if the size changes for any reason
211231
// All calculations related to positions, dimensions, and any other values must be done here (not in onDraw)
@@ -268,6 +288,7 @@ class MultiStateSwitch @JvmOverloads constructor(
268288
* Populates the states that have to be drawn.
269289
*/
270290
private fun populateStates() {
291+
statesSelectors.clear()
271292
for (i in states.indices) {
272293
try {
273294
val selector = createStateSelector(i)
@@ -363,7 +384,7 @@ class MultiStateSwitch @JvmOverloads constructor(
363384
/**
364385
* Creates a bitmap from a view.
365386
*/
366-
fun createBitmapFromView(view: View): Bitmap {
387+
private fun createBitmapFromView(view: View): Bitmap {
367388
val returnedBitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
368389
val canvas = Canvas(returnedBitmap)
369390
view.background?.draw(canvas)
@@ -449,6 +470,7 @@ class MultiStateSwitch @JvmOverloads constructor(
449470
* Calculate the bounds of the view and the centers of the states.
450471
*/
451472
private fun calculateBounds() {
473+
statesCenters.clear()
452474
// Calculate background bounds
453475
val backgroundBounds = Rect().apply {
454476
left = drawingArea.left + shadowStartEndOverflowPx
@@ -486,7 +508,7 @@ class MultiStateSwitch @JvmOverloads constructor(
486508

487509
@SuppressLint("ClickableViewAccessibility")
488510
override fun onTouchEvent(event: MotionEvent): Boolean {
489-
if (!isEnabled) return false
511+
if (!isEnabled || states.isEmpty()) return false
490512
val rawX = event.x
491513
val normalizedX = getNormalizedX(event)
492514
currentStateCenter.x = when {

sample/src/main/java/com/davidmiguel/multistateswitch/sample/MainActivity.kt

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.davidmiguel.multistateswitch.StateListener
1111
import com.davidmiguel.multistateswitch.StateStyle
1212
import com.davidmiguel.multistateswitch.sample.databinding.ActivityMainBinding
1313
import com.davidmiguel.multistateswitch.sample.viewpager.ViewPagerActivity
14+
import kotlin.random.Random
1415

1516
class MainActivity : AppCompatActivity(), StateListener {
1617

@@ -22,6 +23,7 @@ class MainActivity : AppCompatActivity(), StateListener {
2223
setupDefaultSwitch()
2324
setupDisabledSwitch()
2425
setupCustomizedSwitch()
26+
setupAddRemoveSwitch()
2527
setupViewPagerBtn()
2628
}
2729

@@ -40,14 +42,24 @@ class MainActivity : AppCompatActivity(), StateListener {
4042
}
4143

4244
private fun setupCustomizedSwitch() {
43-
binding.customizedSwitch.addStateFromString("Cold", StateStyle.Builder()
44-
.withSelectedBackgroundColor(Color.BLUE)
45-
.build())
46-
binding.customizedSwitch.addState(State("ON", "OFF", "OFF"), StateStyle.Builder()
47-
.withTextColor(ContextCompat.getColor(this, R.color.colorPrimary))
48-
.withDisabledBackgroundColor(Color.BLACK)
49-
.withDisabledTextColor(Color.WHITE)
50-
.build())
45+
binding.customizedSwitch.addStateFromString(
46+
"Cold",
47+
StateStyle.Builder()
48+
.withSelectedBackgroundColor(Color.BLUE)
49+
.build()
50+
)
51+
binding.customizedSwitch.addState(
52+
State(
53+
text = "ON",
54+
selectedText = "OFF",
55+
disabledText = "OFF"
56+
),
57+
StateStyle.Builder()
58+
.withTextColor(ContextCompat.getColor(this, R.color.colorPrimary))
59+
.withDisabledBackgroundColor(Color.BLACK)
60+
.withDisabledTextColor(Color.WHITE)
61+
.build()
62+
)
5163
binding.customizedSwitch.addStateFromString("Hot", StateStyle.Builder()
5264
.withSelectedBackgroundColor(Color.RED)
5365
.build())
@@ -61,6 +73,19 @@ class MainActivity : AppCompatActivity(), StateListener {
6173
binding.select3Btn.setOnClickListener { binding.customizedSwitch.selectState(2) }
6274
}
6375

76+
private fun setupAddRemoveSwitch() {
77+
binding.addRemoveSwitch.addStateListener(this)
78+
binding.addBtn.setOnClickListener {
79+
binding.addRemoveSwitch.addStateFromString("R${Random.nextInt(100)}")
80+
}
81+
binding.removeBtn.setOnClickListener {
82+
val numStates = binding.addRemoveSwitch.getNumberStates()
83+
if (numStates > 0) {
84+
binding.addRemoveSwitch.removeState(numStates - 1)
85+
}
86+
}
87+
}
88+
6489
private fun setupViewPagerBtn() {
6590
binding.viewPagerBtn.setOnClickListener {
6691
startActivity(Intent(this, ViewPagerActivity::class.java))

0 commit comments

Comments
 (0)