diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a877d4..07fe8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ ## `2.0.0` (22/03/21) -- Update dependencies +- Support remove states programmatically #6 +- Support nested scroll when used inside a RecyclerView or ViewPager #8 - Migrate to Kotlin #9 +- Update dependencies **Breaking change:** - Minimum API requirements: API >= 21 (Android 5.0 - LOLLIPOP) diff --git a/README.md b/README.md index 7c98281..979b58e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Android library that provides a multi state switch view. ## Usage +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. + #### Step 1 Add the JitPack repository to your `build.gradle ` file: @@ -41,19 +43,79 @@ Use `MultiStateSwitch` view in your layout: ... /> ``` -TODO +Add states: -#### Attributes +```kotlin +switch.addStatesFromStrings(listOf("One", "Two", "Three")) +``` -TODO +Use `StateStyle` to customize the style of the states: -#### API +```kotlin +switch.addStateFromString( + "Cold", + StateStyle.Builder() + .withSelectedBackgroundColor(Color.BLUE) + .withDisabledTextColor(Color.WHITE) + .build() +) +``` -TODO +Use `State` object if you need to display different text depending on the state's state: -#### Callback +```kotlin +switch.addState( + State( + text = "ON", + selectedText = "OFF", + disabledText = "OFF" + ) +) +``` -TODO +#### Attributes + +- `app:multistateswitch_background_color=[color|reference]` +- `app:multistateswitch_text_color=[color|reference]` +- `app:multistateswitch_text_size=[dimension|reference]` +- `app:multistateswitch_selected_state_index=[integer|reference]` +- `app:multistateswitch_selected_background_color=[color|reference]` +- `app:multistateswitch_selected_text_color=[color|reference]` +- `app:multistateswitch_selected_text_size=[dimension|reference]` +- `app:multistateswitch_disabled_state_enabled=[boolean|reference]` +- `app:multistateswitch_disabled_state_index=[integer|reference]` +- `app:multistateswitch_disabled_background_color=[color|reference]` +- `app:multistateswitch_disabled_text_color=[color|reference]` +- `app:multistateswitch_disabled_text_size=[dimension|reference]` +- `app:multistateswitch_max_number_states=[integer|reference]` + +#### API + +- `addState(state: State, stateStyle: StateStyle? = null)`: adds state to the switch. +- `addStates(states: List, stateStyles: List? = null)`: adds states to the switch and the displaying styles. If you provide styles, you have to provide them for every state. +- `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. +- `addStatesFromStrings(statesTexts: List, stateStyles: List? = 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. +- `replaceState(stateIndex: Int, state: State, stateStyle: StateStyle? = null)`: replaces state. +- `replaceStateFromString(stateIndex: Int, stateText: String)`: replaces state directly from a string. The text will be used for normal, selected and disabled states. +- `removeState(stateIndex: Int)`: removes an state. +- `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. +- `getNumberStates(): Int`: returns number of states of the switch. +- `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`. +- `getMaxNumberStates(): Int`: returns max number of states. By default is -1 which means that there is no restriction. +- `hasMaxNumberStates(): Boolean`: checks whether there is a limit in the number of states or not. +- `setTextTypeface(textTypeface: Typeface)`: sets typeface. +- `setPadding(left: Int, top: Int, right: Int, bottom: Int)` + + +#### Listener + +To listen to state changes, you have to register a `StateListener`: + +```kotlin +binding.defaultSwitch.addStateListener { stateIndex, state -> + // ... +} +``` ## Contributing diff --git a/docs/multi-state-switch.gif b/docs/multi-state-switch.gif index ee9258e..56d28b2 100644 Binary files a/docs/multi-state-switch.gif and b/docs/multi-state-switch.gif differ diff --git a/lib/src/main/java/com/davidmiguel/multistateswitch/MultiStateSwitch.kt b/lib/src/main/java/com/davidmiguel/multistateswitch/MultiStateSwitch.kt index 81d2d87..f52b66b 100644 --- a/lib/src/main/java/com/davidmiguel/multistateswitch/MultiStateSwitch.kt +++ b/lib/src/main/java/com/davidmiguel/multistateswitch/MultiStateSwitch.kt @@ -149,6 +149,10 @@ class MultiStateSwitch @JvmOverloads constructor( if (hasMaxNumberStates() && getNumberStates() >= getMaxNumberStates()) return states.add(state) stateStyle?.run { statesStyles.put(getNumberStates() - 1, stateStyle) } + if(isAttachedToWindow){ + populateView() + invalidate() + } } /** @@ -164,7 +168,7 @@ class MultiStateSwitch @JvmOverloads constructor( } /** - * Adds state to the switch and the displaying style. + * Adds state to the switch directly from a string and the displaying style. * The text will be used for normal, selected and disabled states. */ @JvmOverloads @@ -173,7 +177,7 @@ class MultiStateSwitch @JvmOverloads constructor( } /** - * Adds states to the switch and the displaying styles. + * 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. */ @@ -199,13 +203,29 @@ class MultiStateSwitch @JvmOverloads constructor( } /** - * Replaces state. + * Replaces state directly from a string. * The text will be used for normal, selected and disabled states. */ - fun replaceState(stateIndex: Int, stateText: String) { + fun replaceStateFromString(stateIndex: Int, stateText: String) { replaceState(stateIndex, State(stateText)) } + /** + * Removes an state. + */ + fun removeState(stateIndex: Int) { + require(stateIndex < getNumberStates()) { "State index doesn't exist" } + states.removeAt(stateIndex) + statesStyles.remove(stateIndex) + if(stateIndex == currentStateIndex) { + currentStateIndex = if(stateIndex == 0) 0 else stateIndex - 1 + } + if(isAttachedToWindow){ + populateView() + invalidate() + } + } + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { // Called when the view is first assigned a size, and again if the size changes for any reason // 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( * Populates the states that have to be drawn. */ private fun populateStates() { + statesSelectors.clear() for (i in states.indices) { try { val selector = createStateSelector(i) @@ -363,7 +384,7 @@ class MultiStateSwitch @JvmOverloads constructor( /** * Creates a bitmap from a view. */ - fun createBitmapFromView(view: View): Bitmap { + private fun createBitmapFromView(view: View): Bitmap { val returnedBitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888) val canvas = Canvas(returnedBitmap) view.background?.draw(canvas) @@ -449,6 +470,7 @@ class MultiStateSwitch @JvmOverloads constructor( * Calculate the bounds of the view and the centers of the states. */ private fun calculateBounds() { + statesCenters.clear() // Calculate background bounds val backgroundBounds = Rect().apply { left = drawingArea.left + shadowStartEndOverflowPx @@ -486,7 +508,7 @@ class MultiStateSwitch @JvmOverloads constructor( @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { - if (!isEnabled) return false + if (!isEnabled || states.isEmpty()) return false val rawX = event.x val normalizedX = getNormalizedX(event) currentStateCenter.x = when { diff --git a/sample/src/main/java/com/davidmiguel/multistateswitch/sample/MainActivity.kt b/sample/src/main/java/com/davidmiguel/multistateswitch/sample/MainActivity.kt index 598d338..d8bd468 100644 --- a/sample/src/main/java/com/davidmiguel/multistateswitch/sample/MainActivity.kt +++ b/sample/src/main/java/com/davidmiguel/multistateswitch/sample/MainActivity.kt @@ -11,6 +11,7 @@ import com.davidmiguel.multistateswitch.StateListener import com.davidmiguel.multistateswitch.StateStyle import com.davidmiguel.multistateswitch.sample.databinding.ActivityMainBinding import com.davidmiguel.multistateswitch.sample.viewpager.ViewPagerActivity +import kotlin.random.Random class MainActivity : AppCompatActivity(), StateListener { @@ -22,6 +23,7 @@ class MainActivity : AppCompatActivity(), StateListener { setupDefaultSwitch() setupDisabledSwitch() setupCustomizedSwitch() + setupAddRemoveSwitch() setupViewPagerBtn() } @@ -40,14 +42,24 @@ class MainActivity : AppCompatActivity(), StateListener { } private fun setupCustomizedSwitch() { - binding.customizedSwitch.addStateFromString("Cold", StateStyle.Builder() - .withSelectedBackgroundColor(Color.BLUE) - .build()) - binding.customizedSwitch.addState(State("ON", "OFF", "OFF"), StateStyle.Builder() - .withTextColor(ContextCompat.getColor(this, R.color.colorPrimary)) - .withDisabledBackgroundColor(Color.BLACK) - .withDisabledTextColor(Color.WHITE) - .build()) + binding.customizedSwitch.addStateFromString( + "Cold", + StateStyle.Builder() + .withSelectedBackgroundColor(Color.BLUE) + .build() + ) + binding.customizedSwitch.addState( + State( + text = "ON", + selectedText = "OFF", + disabledText = "OFF" + ), + StateStyle.Builder() + .withTextColor(ContextCompat.getColor(this, R.color.colorPrimary)) + .withDisabledBackgroundColor(Color.BLACK) + .withDisabledTextColor(Color.WHITE) + .build() + ) binding.customizedSwitch.addStateFromString("Hot", StateStyle.Builder() .withSelectedBackgroundColor(Color.RED) .build()) @@ -61,6 +73,19 @@ class MainActivity : AppCompatActivity(), StateListener { binding.select3Btn.setOnClickListener { binding.customizedSwitch.selectState(2) } } + private fun setupAddRemoveSwitch() { + binding.addRemoveSwitch.addStateListener(this) + binding.addBtn.setOnClickListener { + binding.addRemoveSwitch.addStateFromString("R${Random.nextInt(100)}") + } + binding.removeBtn.setOnClickListener { + val numStates = binding.addRemoveSwitch.getNumberStates() + if (numStates > 0) { + binding.addRemoveSwitch.removeState(numStates - 1) + } + } + } + private fun setupViewPagerBtn() { binding.viewPagerBtn.setOnClickListener { startActivity(Intent(this, ViewPagerActivity::class.java)) diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index 1fcab56..0200f87 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -3,142 +3,205 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> - + android:layout_height="match_parent"> - - - - - - - - - - - - - - -