Adds ViewPager2 Adapter (#594)
This commit is contained in:
+1
-3
@@ -35,6 +35,4 @@ pom.xml.*
|
||||
local.properties
|
||||
|
||||
*.prefs
|
||||
|
||||
# The keystore file
|
||||
app/spothero-release.keystore
|
||||
.DS_Store
|
||||
|
||||
@@ -28,6 +28,9 @@ implementation 'com.bluelinelabs:conductor-androidx-transition:3.0.0-rc5'
|
||||
// ViewPager PagerAdapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager:3.0.0-rc5'
|
||||
|
||||
// ViewPager2 Adapter:
|
||||
implementation 'com.bluelinelabs:conductor-viewpager2:3.0.0-rc4'
|
||||
|
||||
// RxJava2 lifecycle support:
|
||||
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0-rc5'
|
||||
|
||||
|
||||
+5
-5
@@ -6,6 +6,10 @@ import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
@@ -15,12 +19,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
/**
|
||||
* An adapter for ViewPagers that uses Routers as pages
|
||||
* An ViewPager adapter that uses Routers as pages
|
||||
*/
|
||||
public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
|
||||
|
||||
+41
-26
@@ -1,35 +1,42 @@
|
||||
package com.bluelinelabs.conductor.viewpager
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Looper.getMainLooper
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.bluelinelabs.conductor.Conductor
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.viewpager.util.FakePager
|
||||
import com.bluelinelabs.conductor.viewpager.util.TestController
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class StateSaveTests {
|
||||
|
||||
private val pager: FakePager
|
||||
private val pager: ViewPager
|
||||
private val pagerAdapter: RouterPagerAdapter
|
||||
private val destroyedItems = mutableListOf<Int>()
|
||||
|
||||
init {
|
||||
val activityController = Robolectric.buildActivity(Activity::class.java).setup()
|
||||
val layout = FrameLayout(activityController.get())
|
||||
activityController.get().setContentView(layout)
|
||||
val router = Conductor.attachRouter(activityController.get(), FrameLayout(activityController.get()), null)
|
||||
val controller = TestController()
|
||||
router.setRoot(with(controller))
|
||||
pager = FakePager(FrameLayout(activityController.get()).also {
|
||||
pager = ViewPager(activityController.get()).also {
|
||||
it.id = ViewCompat.generateViewId()
|
||||
})
|
||||
}
|
||||
layout.addView(pager)
|
||||
pager.offscreenPageLimit = 1
|
||||
pagerAdapter = object : RouterPagerAdapter(controller) {
|
||||
override fun configureRouter(router: Router, position: Int) {
|
||||
@@ -41,21 +48,27 @@ class StateSaveTests {
|
||||
override fun getCount(): Int {
|
||||
return 20
|
||||
}
|
||||
|
||||
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
|
||||
super.destroyItem(container, position, `object`)
|
||||
destroyedItems.add(position)
|
||||
}
|
||||
}
|
||||
pager.setAdapter(pagerAdapter)
|
||||
pager.adapter = pagerAdapter
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoMaxSaves() {
|
||||
// Load all pages
|
||||
for (i in 0 until pagerAdapter.count) {
|
||||
pager.pageTo(i)
|
||||
pager.currentItem = i
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
pager.pageTo(pagerAdapter.count / 2)
|
||||
|
||||
// Ensure all non-visible pages are saved
|
||||
assertEquals(
|
||||
pagerAdapter.count - 1 - (pager.offscreenPageLimit * 2),
|
||||
destroyedItems.size,
|
||||
pagerAdapter.savedPages.size()
|
||||
)
|
||||
}
|
||||
@@ -67,38 +80,40 @@ class StateSaveTests {
|
||||
|
||||
// Load all pages
|
||||
for (i in 0 until pagerAdapter.count) {
|
||||
pager.pageTo(i)
|
||||
pager.currentItem = i
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
val firstSelectedItem = pagerAdapter.count / 2
|
||||
pager.pageTo(firstSelectedItem)
|
||||
for (i in pagerAdapter.count downTo firstSelectedItem) {
|
||||
pager.currentItem = i
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
var savedPages = pagerAdapter.savedPages
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size())
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(
|
||||
pagerAdapter.count - 3,
|
||||
savedPages.keyAt(0)
|
||||
)
|
||||
assertEquals(
|
||||
pagerAdapter.count - 2,
|
||||
savedPages.keyAt(1)
|
||||
)
|
||||
assertEquals(
|
||||
pagerAdapter.count - 1,
|
||||
savedPages.keyAt(2)
|
||||
)
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex], savedPages.keyAt(0))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 1], savedPages.keyAt(1))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 2], savedPages.keyAt(2))
|
||||
|
||||
val secondSelectedItem = 1
|
||||
pager.pageTo(secondSelectedItem)
|
||||
for (i in firstSelectedItem downTo secondSelectedItem) {
|
||||
pager.currentItem = i
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
savedPages = pagerAdapter.savedPages
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size())
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(firstSelectedItem - 1, savedPages.keyAt(0))
|
||||
assertEquals(firstSelectedItem, savedPages.keyAt(1))
|
||||
assertEquals(firstSelectedItem + 1, savedPages.keyAt(2))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex], savedPages.keyAt(0))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 1], savedPages.keyAt(1))
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 2], savedPages.keyAt(2))
|
||||
}
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
package com.bluelinelabs.conductor.viewpager.util;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FakePager {
|
||||
|
||||
private ViewGroup container;
|
||||
private int offscreenPageLimit;
|
||||
private final SparseArray<Object> pages = new SparseArray<>();
|
||||
|
||||
private RouterPagerAdapter adapter;
|
||||
|
||||
public FakePager(ViewGroup container) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public void setAdapter(RouterPagerAdapter adapter) {
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
public void pageTo(int page) {
|
||||
int firstPage = Math.max(0, page - offscreenPageLimit);
|
||||
int lastPage = Math.min(adapter.getCount() - 1, page + offscreenPageLimit);
|
||||
|
||||
List<Integer> pagesI = new ArrayList<>();
|
||||
for (int i = 0; i < pages.size(); i++) {
|
||||
pagesI.add(pages.keyAt(i));
|
||||
}
|
||||
|
||||
for (int i = pages.size() - 1; i >= 0; i--) {
|
||||
int key = pages.keyAt(i);
|
||||
|
||||
if (key < firstPage || key > lastPage) {
|
||||
adapter.destroyItem(container, key, pages.get(key));
|
||||
pages.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (int key = firstPage; key <= lastPage; key++) {
|
||||
if (pages.get(key) == null) {
|
||||
pages.put(key, adapter.instantiateItem(container, key));
|
||||
}
|
||||
}
|
||||
|
||||
adapter.setPrimaryItem(container, page, pages.get(page));
|
||||
}
|
||||
|
||||
public int getOffscreenPageLimit() {
|
||||
return offscreenPageLimit;
|
||||
}
|
||||
|
||||
public void setOffscreenPageLimit(int offscreenPageLimit) {
|
||||
this.offscreenPageLimit = offscreenPageLimit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||
versionName project.VERSION_NAME
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation rootProject.ext.junit
|
||||
testImplementation rootProject.ext.roboelectric
|
||||
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation rootProject.ext.androidxViewPager2
|
||||
implementation project(':conductor')
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-viewpager2'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply plugin: "com.vanniktech.maven.publish"
|
||||
@@ -0,0 +1,3 @@
|
||||
POM_NAME=Conductor ViewPager2 Adapter
|
||||
POM_ARTIFACT_ID=conductor-viewpager2
|
||||
POM_PACKAGING=aar
|
||||
@@ -0,0 +1,3 @@
|
||||
<manifest package="com.bluelinelabs.conductor.viewpager2">
|
||||
<application />
|
||||
</manifest>
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
package com.bluelinelabs.conductor.viewpager2
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.util.LongSparseArray
|
||||
import android.util.SparseArray
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.adapter.StatefulAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
/**
|
||||
* An ViewPager2 adapter that uses Routers as pages
|
||||
*/
|
||||
abstract class RouterStateAdapter(private val host: Controller) :
|
||||
RecyclerView.Adapter<RouterViewHolder>(), StatefulAdapter {
|
||||
|
||||
private var savedPages = LongSparseArray<Bundle>()
|
||||
internal var savedPageHistory = mutableListOf<Long>()
|
||||
private var maxPagesToStateSave = Int.MAX_VALUE
|
||||
private val visibleRouters = SparseArray<Router>()
|
||||
private var currentPrimaryRouterPosition = 0
|
||||
private var primaryItemCallback: PrimaryItemCallback? = null
|
||||
|
||||
init {
|
||||
super.setHasStableIds(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a router is instantiated. Here the router's root should be set if needed.
|
||||
*
|
||||
* @param router The router used for the page
|
||||
* @param position The page position to be instantiated.
|
||||
*/
|
||||
abstract fun configureRouter(router: Router, position: Int)
|
||||
|
||||
/**
|
||||
* Sets the maximum number of pages that will have their states saved. When this number is exceeded,
|
||||
* the page that was state saved least recently will have its state removed from the save data.
|
||||
*/
|
||||
open fun setMaxPagesToStateSave(maxPagesToStateSave: Int) {
|
||||
require(maxPagesToStateSave >= 0) { "Only positive integers may be passed for maxPagesToStateSave." }
|
||||
this.maxPagesToStateSave = maxPagesToStateSave
|
||||
ensurePagesSaved()
|
||||
}
|
||||
|
||||
private fun inferViewPager(recyclerView: RecyclerView): ViewPager2 {
|
||||
return recyclerView.parent as? ViewPager2 ?:
|
||||
error("Expected ViewPager2 instance. Got: ${recyclerView.parent}")
|
||||
}
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
val viewPager = inferViewPager(recyclerView)
|
||||
primaryItemCallback = PrimaryItemCallback().also {
|
||||
viewPager.registerOnPageChangeCallback(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||
val viewPager = inferViewPager(recyclerView)
|
||||
primaryItemCallback?.let {
|
||||
viewPager.unregisterOnPageChangeCallback(it)
|
||||
}
|
||||
primaryItemCallback = null
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RouterViewHolder {
|
||||
return RouterViewHolder(parent)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RouterViewHolder, position: Int) {
|
||||
holder.currentItemPosition = position
|
||||
|
||||
attachRouter(holder, position)
|
||||
}
|
||||
|
||||
override fun onViewAttachedToWindow(holder: RouterViewHolder) {
|
||||
super.onViewAttachedToWindow(holder)
|
||||
|
||||
if (!holder.attached) {
|
||||
attachRouter(holder, holder.currentItemPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: RouterViewHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
|
||||
detachRouter(holder)
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: RouterViewHolder) {
|
||||
super.onViewRecycled(holder)
|
||||
|
||||
detachRouter(holder)
|
||||
|
||||
holder.currentRouter?.let { router ->
|
||||
host.removeChildRouter(router)
|
||||
holder.currentRouter = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailedToRecycleView(holder: RouterViewHolder): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun setHasStableIds(hasStableIds: Boolean) {
|
||||
throw UnsupportedOperationException("Stable Ids are required for the adapter to function properly")
|
||||
}
|
||||
|
||||
override fun saveState(): Parcelable {
|
||||
return SavedState(
|
||||
savedPagesKeys = (0 until savedPages.size()).map { savedPages.keyAt(it) },
|
||||
savedPagesValues = (0 until savedPages.size()).map { savedPages.valueAt(it) },
|
||||
savedPageHistory = savedPageHistory,
|
||||
maxPagesToStateSave = maxPagesToStateSave
|
||||
)
|
||||
}
|
||||
|
||||
override fun restoreState(state: Parcelable) {
|
||||
if (state !is SavedState) return
|
||||
|
||||
savedPages = LongSparseArray()
|
||||
state.savedPagesKeys.indices.forEach { index ->
|
||||
savedPages.put(state.savedPagesKeys[index], state.savedPagesValues[index])
|
||||
}
|
||||
|
||||
savedPageHistory = state.savedPageHistory.toMutableList()
|
||||
maxPagesToStateSave = state.maxPagesToStateSave
|
||||
}
|
||||
|
||||
private fun attachRouter(holder: RouterViewHolder, position: Int) {
|
||||
val itemId = getItemId(position)
|
||||
val router = host.getChildRouter(holder.container, "$itemId")
|
||||
holder.currentRouter = router
|
||||
holder.currentItemId = itemId
|
||||
|
||||
if (!router.hasRootController()) {
|
||||
val routerSavedState = savedPages[position.toLong()]
|
||||
if (routerSavedState != null) {
|
||||
router.restoreInstanceState(routerSavedState)
|
||||
savedPages.remove(itemId)
|
||||
savedPageHistory.remove(itemId)
|
||||
}
|
||||
}
|
||||
|
||||
router.rebindIfNeeded()
|
||||
configureRouter(router, position)
|
||||
|
||||
if (position != currentPrimaryRouterPosition) {
|
||||
for (transaction in router.backstack) {
|
||||
transaction.controller.setOptionsMenuHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
visibleRouters.put(position, router)
|
||||
|
||||
holder.attached = true
|
||||
}
|
||||
|
||||
private fun detachRouter(holder: RouterViewHolder) {
|
||||
if (!holder.attached) {
|
||||
return
|
||||
}
|
||||
|
||||
holder.currentRouter?.let { router ->
|
||||
router.prepareForHostDetach()
|
||||
|
||||
val savedState = Bundle()
|
||||
router.saveInstanceState(savedState)
|
||||
savedPages.put(holder.currentItemId, savedState)
|
||||
|
||||
savedPageHistory.remove(holder.currentItemId)
|
||||
savedPageHistory.add(holder.currentItemId)
|
||||
|
||||
ensurePagesSaved()
|
||||
|
||||
if (visibleRouters[holder.currentItemPosition] == router) {
|
||||
visibleRouters.remove(holder.currentItemPosition)
|
||||
}
|
||||
}
|
||||
|
||||
holder.attached = false
|
||||
}
|
||||
|
||||
private fun ensurePagesSaved() {
|
||||
while (savedPages.size() > maxPagesToStateSave) {
|
||||
val positionToRemove = savedPageHistory.removeAt(0)
|
||||
savedPages.remove(positionToRemove)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the already instantiated Router in the specified position or `null` if there
|
||||
* is no router associated with this position.
|
||||
*/
|
||||
fun getRouter(position: Int): Router? {
|
||||
return visibleRouters[position]
|
||||
}
|
||||
|
||||
inner class PrimaryItemCallback : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
val router = visibleRouters[position]
|
||||
if (position != currentPrimaryRouterPosition) {
|
||||
val previousRouter = visibleRouters[currentPrimaryRouterPosition]
|
||||
|
||||
previousRouter?.backstack?.forEach { it.controller.setOptionsMenuHidden(true) }
|
||||
router?.backstack?.forEach { it.controller.setOptionsMenuHidden(false) }
|
||||
currentPrimaryRouterPosition = position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
private data class SavedState(
|
||||
val savedPagesKeys: List<Long>,
|
||||
val savedPagesValues: List<Bundle>,
|
||||
val savedPageHistory: List<Long>,
|
||||
val maxPagesToStateSave: Int
|
||||
) : Parcelable
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.bluelinelabs.conductor.viewpager2
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.bluelinelabs.conductor.ChangeHandlerFrameLayout
|
||||
import com.bluelinelabs.conductor.Router
|
||||
|
||||
class RouterViewHolder private constructor(val container: ChangeHandlerFrameLayout) : ViewHolder(container) {
|
||||
var currentRouter: Router? = null
|
||||
var currentItemPosition = 0
|
||||
var currentItemId = 0L
|
||||
var attached = false
|
||||
|
||||
companion object {
|
||||
operator fun invoke(parent: ViewGroup): RouterViewHolder {
|
||||
val container = ChangeHandlerFrameLayout(parent.context)
|
||||
container.id = ViewCompat.generateViewId()
|
||||
container.layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
container.isSaveEnabled = false
|
||||
return RouterViewHolder(container)
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.bluelinelabs.conductor.viewpager2
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Looper.getMainLooper
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bluelinelabs.conductor.Conductor
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
|
||||
import com.bluelinelabs.conductor.viewpager2.util.TestController
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class StateSaveTests {
|
||||
|
||||
private val pager: ViewPager2
|
||||
private val adapter: RouterStateAdapter
|
||||
private val destroyedItems = mutableListOf<Int>()
|
||||
|
||||
init {
|
||||
val activityController = Robolectric.buildActivity(Activity::class.java).setup()
|
||||
val layout = FrameLayout(activityController.get())
|
||||
activityController.get().setContentView(layout)
|
||||
val router = Conductor.attachRouter(activityController.get(), FrameLayout(activityController.get()), null)
|
||||
val controller = TestController()
|
||||
router.setRoot(with(controller))
|
||||
pager = ViewPager2(activityController.get()).also {
|
||||
it.id = ViewCompat.generateViewId()
|
||||
}
|
||||
layout.addView(pager)
|
||||
pager.offscreenPageLimit = 1
|
||||
adapter = object : RouterStateAdapter(controller) {
|
||||
override fun configureRouter(router: Router, position: Int) {
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(with(TestController()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return 20
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: RouterViewHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
|
||||
destroyedItems.add(holder.currentItemPosition)
|
||||
}
|
||||
}
|
||||
pager.adapter = adapter
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoMaxSaves() {
|
||||
// Load all pages
|
||||
for (i in 0 until adapter.itemCount) {
|
||||
pager.setCurrentItem(i, false)
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
// Ensure all non-visible pages are saved
|
||||
assertEquals(
|
||||
destroyedItems.size,
|
||||
adapter.savedPageHistory.size
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMaxSavedSet() {
|
||||
val maxPages = 3
|
||||
adapter.setMaxPagesToStateSave(maxPages)
|
||||
|
||||
// Load all pages
|
||||
for (i in 0 until adapter.itemCount) {
|
||||
pager.setCurrentItem(i, false)
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
val firstSelectedItem = adapter.itemCount / 2
|
||||
for (i in adapter.itemCount downTo firstSelectedItem) {
|
||||
pager.setCurrentItem(i, false)
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
var savedPages = adapter.savedPageHistory
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size)
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex], savedPages[savedPages.lastIndex].toInt())
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 1], savedPages[savedPages.lastIndex - 1].toInt())
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 2], savedPages[savedPages.lastIndex - 2].toInt())
|
||||
|
||||
val secondSelectedItem = 1
|
||||
for (i in adapter.itemCount downTo secondSelectedItem) {
|
||||
pager.setCurrentItem(i, false)
|
||||
shadowOf(getMainLooper()).idle()
|
||||
}
|
||||
|
||||
savedPages = adapter.savedPageHistory
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size)
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex], savedPages[savedPages.lastIndex].toInt())
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 1], savedPages[savedPages.lastIndex - 1].toInt())
|
||||
assertEquals(destroyedItems[destroyedItems.lastIndex - 2], savedPages[savedPages.lastIndex - 2].toInt())
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.bluelinelabs.conductor.viewpager2.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
|
||||
public class TestController extends Controller {
|
||||
|
||||
@Override @NonNull
|
||||
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
|
||||
return new FrameLayout(inflater.getContext());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation rootProject.ext.androidxAppCompat
|
||||
implementation rootProject.ext.androidxViewPager2
|
||||
implementation rootProject.ext.material
|
||||
|
||||
implementation rootProject.ext.archComponentsLiveDataCore // Fix duplicate classes
|
||||
@@ -41,6 +42,7 @@ dependencies {
|
||||
|
||||
implementation project(':conductor')
|
||||
implementation project(':conductor-modules:viewpager')
|
||||
implementation project(':conductor-modules:viewpager2')
|
||||
implementation project(':conductor-modules:rxlifecycle2')
|
||||
implementation project(':conductor-modules:autodispose')
|
||||
implementation project(':conductor-modules:arch-components-lifecycle')
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
|
||||
|
||||
@@ -48,11 +48,12 @@ public class HomeController extends BaseController {
|
||||
SHARED_ELEMENT_TRANSITIONS("Shared Element Demos", R.color.purple_300),
|
||||
CHILD_CONTROLLERS("Child Controllers", R.color.orange_300),
|
||||
VIEW_PAGER("ViewPager", R.color.green_300),
|
||||
TARGET_CONTROLLER("Target Controller", R.color.pink_300),
|
||||
MULTIPLE_CHILD_ROUTERS("Multiple Child Routers", R.color.deep_orange_300),
|
||||
MASTER_DETAIL("Master Detail", R.color.grey_300),
|
||||
DRAG_DISMISS("Drag Dismiss", R.color.lime_300),
|
||||
EXTERNAL_MODULES("Bonus Modules", R.color.teal_300);
|
||||
VIEW_PAGER_2("ViewPager2", R.color.pink_300),
|
||||
TARGET_CONTROLLER("Target Controller", R.color.deep_orange_300),
|
||||
MULTIPLE_CHILD_ROUTERS("Multiple Child Routers", R.color.grey_300),
|
||||
MASTER_DETAIL("Master Detail", R.color.lime_300),
|
||||
DRAG_DISMISS("Drag Dismiss", R.color.teal_300),
|
||||
EXTERNAL_MODULES("Bonus Modules", R.color.deep_purple_300);
|
||||
|
||||
String title;
|
||||
@ColorRes int color;
|
||||
@@ -182,7 +183,12 @@ public class HomeController extends BaseController {
|
||||
.popChangeHandler(new FadeChangeHandler()));
|
||||
break;
|
||||
case VIEW_PAGER:
|
||||
getRouter().pushController(RouterTransaction.with(new PagerController())
|
||||
getRouter().pushController(RouterTransaction.with(new ViewPagerController())
|
||||
.pushChangeHandler(new FadeChangeHandler())
|
||||
.popChangeHandler(new FadeChangeHandler()));
|
||||
break;
|
||||
case VIEW_PAGER_2:
|
||||
getRouter().pushController(RouterTransaction.with(new ViewPager2Controller())
|
||||
.pushChangeHandler(new FadeChangeHandler())
|
||||
.popChangeHandler(new FadeChangeHandler()));
|
||||
break;
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.viewpager2.RouterStateAdapter;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ViewPager2Controller extends BaseController {
|
||||
|
||||
private int[] PAGE_COLORS = new int[]{R.color.green_300, R.color.cyan_300, R.color.deep_purple_300, R.color.lime_300, R.color.red_300};
|
||||
|
||||
@BindView(R.id.tab_layout) TabLayout tabLayout;
|
||||
@BindView(R.id.view_pager) ViewPager2 viewPager;
|
||||
|
||||
private final RouterStateAdapter pagerAdapter;
|
||||
|
||||
public ViewPager2Controller() {
|
||||
pagerAdapter = new RouterStateAdapter(this) {
|
||||
@Override
|
||||
public void configureRouter(@NonNull Router router, int position) {
|
||||
if (!router.hasRootController()) {
|
||||
Controller page = new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
|
||||
router.setRoot(RouterTransaction.with(page));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return PAGE_COLORS.length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_view_pager2, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
viewPager.setAdapter(pagerAdapter);
|
||||
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> tab.setText("Page " + position)).attach();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyView(@NonNull View view) {
|
||||
if (!getActivity().isChangingConfigurations()) {
|
||||
viewPager.setAdapter(null);
|
||||
}
|
||||
tabLayout.setupWithViewPager(null);
|
||||
super.onDestroyView(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return "ViewPager2 Demo";
|
||||
}
|
||||
|
||||
}
|
||||
+7
-6
@@ -1,24 +1,25 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
|
||||
public class PagerController extends BaseController {
|
||||
public class ViewPagerController extends BaseController {
|
||||
|
||||
private int[] PAGE_COLORS = new int[]{R.color.green_300, R.color.cyan_300, R.color.deep_purple_300, R.color.lime_300, R.color.red_300};
|
||||
|
||||
@@ -27,7 +28,7 @@ public class PagerController extends BaseController {
|
||||
|
||||
private final RouterPagerAdapter pagerAdapter;
|
||||
|
||||
public PagerController() {
|
||||
public ViewPagerController() {
|
||||
pagerAdapter = new RouterPagerAdapter(this) {
|
||||
@Override
|
||||
public void configureRouter(@NonNull Router router, int position) {
|
||||
@@ -52,7 +53,7 @@ public class PagerController extends BaseController {
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_pager, container, false);
|
||||
return inflater.inflate(R.layout.controller_view_pager, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="6dp"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
tools:targetApi="lollipop"
|
||||
/>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/view_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
+2
-1
@@ -24,11 +24,12 @@ ext {
|
||||
kotlinVersion = '1.4.10'
|
||||
kotlinStd = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
||||
|
||||
material = "com.google.android.material:material:1.0.0"
|
||||
material = "com.google.android.material:material:1.1.0"
|
||||
androidxAnnotations = "androidx.annotation:annotation:1.1.0"
|
||||
androidxAppCompat = "androidx.appcompat:appcompat:1.0.0"
|
||||
androidxTransition = "androidx.transition:transition:1.3.1"
|
||||
androidxCollection = "androidx.collection:collection:1.1.0"
|
||||
androidxViewPager2 = "androidx.viewpager2:viewpager2:1.0.0"
|
||||
|
||||
butterknife = "com.jakewharton:butterknife:$butterknifeVersion"
|
||||
butterknifeCompiler = "com.jakewharton:butterknife-compiler:$butterknifeVersion"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
include ':conductor'
|
||||
include ':conductor-lint'
|
||||
include ':conductor-modules:viewpager'
|
||||
include ':conductor-modules:viewpager2'
|
||||
include ':conductor-modules:rxlifecycle2'
|
||||
include ':conductor-modules:autodispose'
|
||||
include ':conductor-modules:arch-components-lifecycle'
|
||||
|
||||
Reference in New Issue
Block a user