Skip to content

Commit

Permalink
feat: easier way to swap to views
Browse files Browse the repository at this point in the history
Fixes: #2
BREAKING CHANGE:

No more needing to setup Swapper! Simply add children Views to SwapperView and swap between them with `swapTo(childView`.
  • Loading branch information
levibostian committed Nov 17, 2020
1 parent 1437fc4 commit 42d65ee
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 72 deletions.
57 changes: 28 additions & 29 deletions README.md
Expand Up @@ -21,6 +21,8 @@ You know those moments in your app when you have a `RecyclerView` that has no ro
* Lightweight. Zero dependencies.
* UI testing friendly.
* Setup with default values that should work for 95% of your use cases. Fully customizable for those other cases.
* Strict semantic versioning for backwards compatibility.
* Example app in this project to learn from.

I recommend you check out 2 other libraries that work nicely with Swapper: [Empty](https://github.com/levibostian/Empty-Android) and [PleaseHold](https://github.com/levibostian/PleaseHold-Android).

Expand Down Expand Up @@ -63,21 +65,14 @@ Replace `version-here` with: [![Download](https://api.bintray.com/packages/levib
</LinearLayout>
```

*Note: The views you want to swap between must be children of `SwapperView`.*
> Note: The views you want to swap between **must be children** of `SwapperView`.
> Note: You can programmatically add more children into `SwapperView`. It's just a `ViewGroup`.
*Note: You can programmatically add more children into `SwapperView`. It's just a `ViewGroup`. Doing it in XML is just easier.*

* Setup the `SwapperView` with a map of the views that you want to swap between:
* Swap to views:

```kotlin
class MainActivity: AppCompatActivity() {

// You do not need to an enum as the IDs for Swapper, but it's recommended to avoid typos
enum class SwapperViews {
FIRST_VIEW,
SECOND_VIEW
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -87,25 +82,20 @@ class MainActivity: AppCompatActivity() {
}

private fun setupViews() {
swapper_view.apply {
viewMap = mapOf(
// `first_view` and `second_view` are children of the `SwapperView`.
// Here, we are mapping an ID to each view.
Pair(SwapperViews.FIRST_VIEW.name, first_view),
Pair(SwapperViews.SECOND_VIEW.name, second_view)
)
// When `SwapperView` initializes, it hides all of it's children by default. You must call `swapTo()` to swap to your first view.
swapTo(SwapperViews.FIRST_VIEW.name) {
// optional callback that's called when animation is complete.
}
// Make sure to set the first swapping view:
swapper_view.swapTo(first_view)
// Note: the first time you call swapTo() there will *not* be an animation.

// ... do stuff ....
// Anytime you want to swap to another View:
swapper_view.swapTo(second_view) {
// optional callback when animation is done.
}
}

}
```

* That's it! Now, when you want to Swapper to transition from the currently shown view to another one, call `swapTo(id)`. Swapper will then show the View for you. Swapper will even fade out the currently shown view and fade in the new view for you for a nice touch 👌. If you want to override the default animation, you can!

## Customize Swapper

#### Set animation duration
Expand Down Expand Up @@ -151,18 +141,27 @@ Swapper comes with an example app you can use to play with the library. To run t
* Open up Swapper project in Android Studio.
* Get to writing code!

## Author
## Contributors

* Levi Bostian - [GitHub](https://github.com/levibostian), [Twitter](https://twitter.com/levibostian), [Website/blog](http://levibostian.com)
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key))

![Levi Bostian image](https://gravatar.com/avatar/22355580305146b21508c74ff6b44bc5?s=250)
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://github.com/levibostian"><img src="https://avatars1.githubusercontent.com/u/2041082?v=4" width="100px;" alt=""/><br /><sub><b>Levi Bostian</b></sub></a><br /><a href="https://github.com/levibostian/Purslane/commits?author=levibostian" title="Code">💻</a> <a href="https://github.com/levibostian/Purslane/commits?author=levibostian" title="Documentation">📖</a> <a href="#maintenance-levibostian" title="Maintenance">🚧</a></td>
</tr>
</table>

## Contribute
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

Swapper is open for pull requests. Check out the [list of issues](https://github.com/levibostian/Swapper-android/issues) for tasks I am planning on working on. Check them out if you wish to contribute in that way.
## Contribute

**Want to add features to Swapper?** Before you decide to take a bunch of time and add functionality to the library, please, [create an issue](https://github.com/levibostian/Swapper-android/issues/new) stating what you wish to add. This might save you some time in case your purpose does not fit well in the use cases of Swapper.

## License

Swapper is available under the MIT license. See the LICENSE file for more info.
Swapper is available under the MIT license. See the LICENSE file for more info.
21 changes: 5 additions & 16 deletions app/src/main/java/com/levibostian/swapperexample/MainActivity.kt
Expand Up @@ -8,11 +8,6 @@ import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

enum class SwapperViews {
FIRST_VIEW,
SECOND_VIEW
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -22,24 +17,18 @@ class MainActivity : AppCompatActivity() {
}

private fun setupViews() {
swapper_view.apply {
viewMap = mapOf(
Pair(SwapperViews.FIRST_VIEW.name, first_view),
Pair(SwapperViews.SECOND_VIEW.name, second_view)
)
swapTo(SwapperViews.FIRST_VIEW.name) {
first_view_imageview.load("https://raw.githubusercontent.com/levibostian/Swapper-iOS/d494bc41894b5e5bc7eeacc162a96ddadca024cc/Example/Swapper/Images.xcassets/little_hill.imageset/little_hill.jpg")
}
}
swapper_view.swapTo(first_view)

first_view_imageview.load("https://raw.githubusercontent.com/levibostian/Swapper-iOS/d494bc41894b5e5bc7eeacc162a96ddadca024cc/Example/Swapper/Images.xcassets/little_hill.imageset/little_hill.jpg")

first_view_swap_button.setOnClickListener {
swapper_view.swapTo(SwapperViews.SECOND_VIEW.name) {
swapper_view.swapTo(second_view) {
second_view_imageview.load("https://raw.githubusercontent.com/levibostian/Swapper-iOS/d494bc41894b5e5bc7eeacc162a96ddadca024cc/Example/Swapper/Images.xcassets/mt_mckinley.imageset/mt_mckinley.jpg")
}
}

second_view_swap_button.setOnClickListener {
swapper_view.swapTo(SwapperViews.FIRST_VIEW.name) {}
swapper_view.swapTo(first_view)
}

animation_duration_100.setOnClickListener { SwapperView.config.animationDuration = 100 }
Expand Down
40 changes: 13 additions & 27 deletions swapper/src/main/java/com/levibostian/swapper/SwapperView.kt
Expand Up @@ -7,7 +7,6 @@ import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout

typealias SwapperViewId = String
typealias SwapperViewSwapAnimator = (oldView: View, newView: View, duration: Long, onComplete: () -> Unit) -> Unit

class SwapperView : FrameLayout {
Expand Down Expand Up @@ -37,7 +36,9 @@ class SwapperView : FrameLayout {
return _swapAnimator ?: config.swapAnimator
}

private var currentlyShownViewId: Pair<SwapperViewId, View>? = null
private var currentlyShownViewId: Int? = null
private val currentlyShownView: View?
get() = children.filter { it.id == currentlyShownViewId }.firstOrNull()

private val children: List<View>
get() {
Expand All @@ -50,22 +51,6 @@ class SwapperView : FrameLayout {
return children
}

@Transient var viewMap: Map<SwapperViewId, View>? = null
set(value) {
field = value

var currentlyShownViewFound = false
value?.forEach { mapView ->
val id = mapView.key
val view = mapView.value

children.firstOrNull { it == view } ?: throw IllegalArgumentException("Cannot add view, $mapView, as it's not a child.")
if (id == currentlyShownViewId?.first) currentlyShownViewFound = true
}

if (!currentlyShownViewFound) hideAllChildren()
}

constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

Expand All @@ -90,25 +75,26 @@ class SwapperView : FrameLayout {
}
}

@Synchronized fun swapTo(id: SwapperViewId, onComplete: (() -> Unit)? = null) {
checkNotNull(viewMap) { "Can't swap to a view if you have not set viewMap" }
if (currentlyShownViewId?.first == id) return
@Synchronized fun swapTo(viewToSwapTo: View, onComplete: (() -> Unit)? = null) {
if (!children.contains(viewToSwapTo)) throw IllegalArgumentException("View must be child to swap to it. Given: ${viewToSwapTo.id}, children: ${children.map { it.id }.joinToString(", ")}")
if (currentlyShownViewId == id) return

val viewToSwapTo = viewMap!!.getValue(id)
val currentlyShownView = currentlyShownViewId?.second
currentlyShownViewId = Pair(id, viewToSwapTo)
val isFirstView = currentlyShownViewId == null
val oldView = currentlyShownView
currentlyShownViewId = viewToSwapTo.id

fun doneWithAnimation() {
viewToSwapTo.visibility = View.VISIBLE
currentlyShownView?.visibility = View.GONE
oldView?.visibility = View.GONE

onComplete?.invoke()
}

if (currentlyShownView == null) {
if (isFirstView) {
hideAllChildren()
doneWithAnimation()
} else {
swapAnimator(currentlyShownView, viewToSwapTo, animationDuration) {
swapAnimator(oldView!!, viewToSwapTo, animationDuration) {
doneWithAnimation()
}
}
Expand Down

0 comments on commit 42d65ee

Please sign in to comment.