[UI] Add lazy loading to fragments with view pager.

This commit is contained in:
Kuba Szczodrzyński 2020-03-29 23:11:17 +02:00
parent 41a79caf83
commit 043f8210ba
12 changed files with 148 additions and 41 deletions

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.base.lazypager
import androidx.fragment.app.Fragment
abstract class LazyFragment : Fragment() {
private var isPageCreated = false
var swipeRefreshLayoutCallback: ((isEnabled: Boolean) -> Unit)? = null
/**
* Called when the page is first shown, or if previous
* [onPageCreated] returned false
*
* @return true if the view is set up
* @return false if the setup failed. The method may be then called
* again, when page becomes visible.
*/
abstract fun onPageCreated(): Boolean
internal fun createPage() {
if (!isPageCreated && isAdded) {
isPageCreated = onPageCreated()
}
}
override fun onResume() {
createPage()
super.onResume()
}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.base.lazypager
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
abstract class LazyPagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
var swipeRefreshLayoutCallback: ((isEnabled: Boolean) -> Unit)? = null
abstract override fun getItem(position: Int): LazyFragment
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-29.
*/
package pl.szczodrzynski.edziennik.ui.modules.base.lazypager
import android.content.Context
import android.util.AttributeSet
import androidx.viewpager.widget.ViewPager
class LazyViewPager @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
init {
addOnPageChangeListener(object : OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
if (adapter is LazyPagerAdapter) {
val fragment = adapter?.instantiateItem(this@LazyViewPager, position)
val lazyFragment = fragment as? LazyFragment
lazyFragment?.createPage()
}
}
})
}
}

View File

@ -68,6 +68,10 @@ class HomeworkFragment : Fragment() {
})) }))
b.viewPager.adapter = MessagesFragment.Adapter(childFragmentManager).also { adapter -> b.viewPager.adapter = MessagesFragment.Adapter(childFragmentManager).also { adapter ->
adapter.swipeRefreshLayoutCallback = { isEnabled ->
b.refreshLayout.isEnabled = isEnabled
}
adapter.addFragment(HomeworkListFragment().also { fragment -> adapter.addFragment(HomeworkListFragment().also { fragment ->
fragment.arguments = Bundle().also { args -> fragment.arguments = Bundle().also { args ->
args.putInt("homeworkDate", HomeworkDate.CURRENT) args.putInt("homeworkDate", HomeworkDate.CURRENT)

View File

@ -4,17 +4,18 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.databinding.HomeworkListBinding import pl.szczodrzynski.edziennik.databinding.HomeworkListBinding
import pl.szczodrzynski.edziennik.getInt import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class HomeworkListFragment : Fragment() { class HomeworkListFragment : LazyFragment() {
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
@ -30,11 +31,7 @@ class HomeworkListFragment : Fragment() {
return b.root return b.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onPageCreated(): Boolean {
// TODO check if app, activity, b can be null
if (!isAdded)
return
if (arguments != null) { if (arguments != null) {
homeworkDate = arguments.getInt("homeworkDate", HomeworkDate.CURRENT) homeworkDate = arguments.getInt("homeworkDate", HomeworkDate.CURRENT)
} }
@ -45,6 +42,16 @@ class HomeworkListFragment : Fragment() {
b.homeworkView.setHasFixedSize(true) b.homeworkView.setHasFixedSize(true)
b.homeworkView.layoutManager = layoutManager b.homeworkView.layoutManager = layoutManager
b.homeworkView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (recyclerView.canScrollVertically(-1)) {
swipeRefreshLayoutCallback?.invoke(false)
}
if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
swipeRefreshLayoutCallback?.invoke(true)
}
}
})
val filter = when(homeworkDate) { val filter = when(homeworkDate) {
HomeworkDate.CURRENT -> "eventDate >= '" + Date.getToday().stringY_m_d + "'" HomeworkDate.CURRENT -> "eventDate >= '" + Date.getToday().stringY_m_d + "'"
@ -66,5 +73,6 @@ class HomeworkListFragment : Fragment() {
b.homeworkNoData.visibility = View.VISIBLE b.homeworkNoData.visibility = View.VISIBLE
} }
}) })
return true
} }
} }

View File

@ -6,7 +6,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
@ -14,8 +13,9 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.databinding.FragmentMessagesBinding import pl.szczodrzynski.edziennik.databinding.FragmentMessagesBinding
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyPagerAdapter
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import java.util.*
class MessagesFragment : Fragment() { class MessagesFragment : Fragment() {
companion object { companion object {
@ -56,6 +56,10 @@ class MessagesFragment : Fragment() {
b.viewPager.adapter = Adapter(childFragmentManager).also { adapter -> b.viewPager.adapter = Adapter(childFragmentManager).also { adapter ->
adapter.swipeRefreshLayoutCallback = { isEnabled ->
b.refreshLayout.isEnabled = isEnabled
}
adapter.addFragment(MessagesListFragment().also { fragment -> adapter.addFragment(MessagesListFragment().also { fragment ->
fragment.arguments = Bundle().also { args -> fragment.arguments = Bundle().also { args ->
args.putInt("messageType", Message.TYPE_RECEIVED) args.putInt("messageType", Message.TYPE_RECEIVED)
@ -126,11 +130,11 @@ class MessagesFragment : Fragment() {
}*/ }*/
} }
internal class Adapter(manager: FragmentManager) : FragmentPagerAdapter(manager) { internal class Adapter(manager: FragmentManager) : LazyPagerAdapter(manager) {
private val mFragmentList = ArrayList<Fragment>() private val mFragmentList = mutableListOf<LazyFragment>()
private val mFragmentTitleList = ArrayList<String>() private val mFragmentTitleList = mutableListOf<String>()
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): LazyFragment {
return mFragmentList[position] return mFragmentList[position]
} }
@ -138,7 +142,8 @@ class MessagesFragment : Fragment() {
return mFragmentList.size return mFragmentList.size
} }
fun addFragment(fragment: Fragment, title: String) { fun addFragment(fragment: LazyFragment, title: String) {
fragment.swipeRefreshLayoutCallback = this@Adapter.swipeRefreshLayoutCallback
mFragmentList.add(fragment) mFragmentList.add(fragment)
mFragmentTitleList.add(title) mFragmentTitleList.add(title)
} }

View File

@ -10,11 +10,10 @@ import android.view.ViewGroup;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -26,13 +25,15 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message;
import pl.szczodrzynski.edziennik.data.db.full.MessageFull; import pl.szczodrzynski.edziennik.data.db.full.MessageFull;
import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull; import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull;
import pl.szczodrzynski.edziennik.databinding.MessagesListBinding; import pl.szczodrzynski.edziennik.databinding.MessagesListBinding;
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment;
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration; import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration;
import pl.szczodrzynski.edziennik.utils.Themes; import pl.szczodrzynski.edziennik.utils.Themes;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
import static pl.szczodrzynski.edziennik.utils.Utils.d; import static pl.szczodrzynski.edziennik.utils.Utils.d;
public class MessagesListFragment extends Fragment { public class MessagesListFragment extends LazyFragment {
private App app = null; private App app = null;
private MainActivity activity = null; private MainActivity activity = null;
@ -65,9 +66,9 @@ public class MessagesListFragment extends Fragment {
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public boolean onPageCreated() {
if (app == null || activity == null || b == null || !isAdded()) if (app == null || activity == null || b == null || !isAdded())
return; return false;
long messageId = -1; long messageId = -1;
if (getArguments() != null) { if (getArguments() != null) {
@ -78,7 +79,7 @@ public class MessagesListFragment extends Fragment {
args.putLong("messageId", messageId); args.putLong("messageId", messageId);
getArguments().remove("messageId"); getArguments().remove("messageId");
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, args); activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, args);
return; return false;
} }
if (getArguments() != null) { if (getArguments() != null) {
@ -161,11 +162,24 @@ public class MessagesListFragment extends Fragment {
// TODO ANIMATION // TODO ANIMATION
//postponeEnterTransition(); //postponeEnterTransition();
viewParent = (ViewGroup) view.getParent(); viewParent = (ViewGroup) getView().getParent();
b.emailList.setLayoutManager(new LinearLayoutManager(view.getContext())); b.emailList.setLayoutManager(new LinearLayoutManager(getView().getContext()));
b.emailList.addItemDecoration(new SimpleDividerItemDecoration(view.getContext())); b.emailList.addItemDecoration(new SimpleDividerItemDecoration(getView().getContext()));
b.emailList.setAdapter(messagesAdapter); b.emailList.setAdapter(messagesAdapter);
b.emailList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (b.emailList.canScrollVertically(-1)) {
if (getSwipeRefreshLayoutCallback() != null)
getSwipeRefreshLayoutCallback().invoke(false);
}
if (!b.emailList.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) {
if (getSwipeRefreshLayoutCallback() != null)
getSwipeRefreshLayoutCallback().invoke(true);
}
}
});
if (messageType == Message.TYPE_RECEIVED) { if (messageType == Message.TYPE_RECEIVED) {
App.db.messageDao().getReceived(App.Companion.getProfileId()).observe(this, messageFulls -> { App.db.messageDao().getReceived(App.Companion.getProfileId()).observe(this, messageFulls -> {
@ -215,7 +229,7 @@ public class MessagesListFragment extends Fragment {
}); });
} }
return true;
} }
private void createMessageList(List<MessageFull> messageFulls) { private void createMessageList(List<MessageFull> messageFulls) {

View File

@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.base.PagerFragment import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView import pl.szczodrzynski.edziennik.utils.ListenerScrollView
@ -36,7 +36,7 @@ import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
class TimetableDayFragment : PagerFragment(), CoroutineScope { class TimetableDayFragment : LazyFragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TimetableDayFragment" private const val TAG = "TimetableDayFragment"
} }
@ -104,9 +104,6 @@ class TimetableDayFragment : PagerFragment(), CoroutineScope {
} }
override fun onPageCreated(): Boolean { override fun onPageCreated(): Boolean {
if (!isAdded)
return false
// observe lesson database // observe lesson database
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons -> app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons ->
launch { launch {

View File

@ -5,9 +5,9 @@
package pl.szczodrzynski.edziennik.ui.modules.timetable package pl.szczodrzynski.edziennik.ui.modules.timetable
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyPagerAdapter
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
@ -16,7 +16,7 @@ class TimetablePagerAdapter(
private val items: List<Date>, private val items: List<Date>,
private val startHour: Int, private val startHour: Int,
private val endHour: Int private val endHour: Int
) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { ) : LazyPagerAdapter(fragmentManager) {
companion object { companion object {
private const val TAG = "TimetablePagerAdapter" private const val TAG = "TimetablePagerAdapter"
} }
@ -25,8 +25,9 @@ class TimetablePagerAdapter(
private val weekStart by lazy { today.weekStart } private val weekStart by lazy { today.weekStart }
private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) } private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) }
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): LazyFragment {
return TimetableDayFragment().apply { return TimetableDayFragment().apply {
swipeRefreshLayoutCallback = this@TimetablePagerAdapter.swipeRefreshLayoutCallback
arguments = Bundle().apply { arguments = Bundle().apply {
putInt("date", items[position].value) putInt("date", items[position].value)
putInt("startHour", startHour) putInt("startHour", startHour)

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:tools="http://schemas.android.com/tools">
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator <pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
android:id="@+id/refreshLayout" android:id="@+id/refreshLayout"
@ -23,7 +22,7 @@
app:tabSelectedTextColor="?colorPrimary" app:tabSelectedTextColor="?colorPrimary"
app:tabTextColor="?android:textColorPrimary" /> app:tabTextColor="?android:textColorPrimary" />
<androidx.viewpager.widget.ViewPager <pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyViewPager
android:id="@+id/viewPager" android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@ -21,7 +21,7 @@
app:tabSelectedTextColor="?colorPrimary" app:tabSelectedTextColor="?colorPrimary"
app:tabTextColor="?android:textColorPrimary"/> app:tabTextColor="?android:textColorPrimary"/>
<androidx.viewpager.widget.ViewPager <pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyViewPager
android:id="@+id/viewPager" android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -29,4 +29,4 @@
</LinearLayout> </LinearLayout>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator> </pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout> </layout>

View File

@ -28,7 +28,7 @@
android:id="@+id/tabLayout" android:id="@+id/tabLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:background="@color/colorSurface_6dp" android:background="@color/colorSurface_1dp"
app:rtl_tabIndicatorColor="?colorPrimary" app:rtl_tabIndicatorColor="?colorPrimary"
app:rtl_tabMaxWidth="300dp" app:rtl_tabMaxWidth="300dp"
app:rtl_tabMinWidth="90dp" app:rtl_tabMinWidth="90dp"
@ -40,7 +40,7 @@
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" /> app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager <pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyViewPager
android:id="@+id/viewPager" android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -94,4 +94,4 @@
</FrameLayout> </FrameLayout>
<!--</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>--> <!--</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>-->
</layout> </layout>