From 997f65d67a26a97234147ee8a07045a8d40dbd31 Mon Sep 17 00:00:00 2001 From: Levi Bostian Date: Tue, 17 Nov 2020 12:11:47 -0600 Subject: [PATCH] feat: easier way to swap to views Fixes: https://github.com/levibostian/Swapper-Android/issues/2 BREAKING CHANGE: SwapperView now uses a generic View identifier. This makes using enums way easier. * Use `SwapperViewContainer` in XML instead of `SwapperView` * Call `swapperViewContainer.init(...)` to setup strongly typed `SwapperView` --- .../swapperexample/MainActivity.kt | 26 +++--- app/src/main/res/layout/activity_main.xml | 4 +- .../com/levibostian/swapper/SwapperView.kt | 18 ++++- .../swapper/SwapperViewContainer.kt | 80 +++++++++++++++++++ 4 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 swapper/src/main/java/com/levibostian/swapper/SwapperViewContainer.kt diff --git a/app/src/main/java/com/levibostian/swapperexample/MainActivity.kt b/app/src/main/java/com/levibostian/swapperexample/MainActivity.kt index 92157ff..080a80b 100644 --- a/app/src/main/java/com/levibostian/swapperexample/MainActivity.kt +++ b/app/src/main/java/com/levibostian/swapperexample/MainActivity.kt @@ -4,6 +4,8 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import coil.load import com.levibostian.swapper.SwapperView +import com.levibostian.swapperexample.MainActivity.SwapperViews.FIRST_VIEW +import com.levibostian.swapperexample.MainActivity.SwapperViews.SECOND_VIEW import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { @@ -13,6 +15,8 @@ class MainActivity : AppCompatActivity() { SECOND_VIEW } + private lateinit var swapperView: SwapperView + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -22,24 +26,24 @@ 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") - } - } + swapperView = swapper_view.init( + mapOf( + Pair(FIRST_VIEW, first_view), + Pair(SECOND_VIEW, second_view) + ), + 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) { + swapperView.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) {} + swapperView.swapTo(FIRST_VIEW) } animation_duration_100.setOnClickListener { SwapperView.config.animationDuration = 100 } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ac44b5f..2ee7419 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -43,7 +43,7 @@ android:text="5s"/> - - + \ No newline at end of file diff --git a/swapper/src/main/java/com/levibostian/swapper/SwapperView.kt b/swapper/src/main/java/com/levibostian/swapper/SwapperView.kt index 199eab1..a0ee39a 100644 --- a/swapper/src/main/java/com/levibostian/swapper/SwapperView.kt +++ b/swapper/src/main/java/com/levibostian/swapper/SwapperView.kt @@ -6,11 +6,11 @@ import android.os.Build.VERSION_CODES import android.util.AttributeSet import android.view.View import android.widget.FrameLayout +import java.lang.ref.WeakReference -typealias SwapperViewId = String typealias SwapperViewSwapAnimator = (oldView: View, newView: View, duration: Long, onComplete: () -> Unit) -> Unit -class SwapperView : FrameLayout { +class SwapperView : FrameLayout { companion object { val config: SwapperConfig = SwapperConfig @@ -39,6 +39,8 @@ class SwapperView : FrameLayout { private var currentlyShownViewId: Pair? = null + internal var container: WeakReference? = null + private val children: List get() { val children: MutableList = mutableListOf() @@ -47,10 +49,14 @@ class SwapperView : FrameLayout { children.add(getChildAt(index)) } + container?.get()?.let { container -> + children.addAll(container.childrenExceptSwapperView) + } + return children } - @Transient var viewMap: Map? = null + private var viewMap: Map? = null set(value) { field = value @@ -84,6 +90,12 @@ class SwapperView : FrameLayout { hideAllChildren() } + @Synchronized fun init(views: Map, swapTo: SwapperViewId) { + this.viewMap = views + + this.swapTo(swapTo) + } + private fun hideAllChildren() { children.forEach { childView -> childView.visibility = View.GONE diff --git a/swapper/src/main/java/com/levibostian/swapper/SwapperViewContainer.kt b/swapper/src/main/java/com/levibostian/swapper/SwapperViewContainer.kt new file mode 100644 index 0000000..8f0b8e9 --- /dev/null +++ b/swapper/src/main/java/com/levibostian/swapper/SwapperViewContainer.kt @@ -0,0 +1,80 @@ +package com.levibostian.swapper + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout +import java.lang.ref.WeakReference + +class SwapperViewContainer : FrameLayout { + + private var attrs: AttributeSet? = null + private var defStyleAttr: Int = -1 + + internal val childrenExceptSwapperView: List + get() { + val children: MutableList = mutableListOf() + val swapperViewIndex = this.swapperViewChildIndex + + for (index in 0 until childCount) { + if (index != swapperViewIndex) { + children.add(getChildAt(index)) + } + } + + return children + } + + private val swapperViewChildIndex: Int? + get() { + for (index in 0 until childCount) { + val view = getChildAt(index) + + if (view is SwapperView<*>) { + return index + } + } + + return null + } + + constructor(context: Context) : this(context, null) + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + initialize(context, attrs, defStyleAttr) + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + initialize(context, attrs, defStyleAttr) + } + + private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + + this.attrs = attrs + this.defStyleAttr = defStyleAttr + } + + fun init(views: Map, swapTo: SwapperViewId): SwapperView { + swapperViewChildIndex?.let { removeViewAt(it) } + + val swapperView = SwapperView(context, attrs, defStyleAttr) + addView(swapperView) + swapperView.container = WeakReference(this) + swapperView.init(views, swapTo) + + return swapperView + } +}