From 043f8210ba34e7dd1c2ed8fe1cdf4391bb75d943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sun, 29 Mar 2020 23:11:17 +0200 Subject: [PATCH] [UI] Add lazy loading to fragments with view pager. --- .../ui/modules/base/lazypager/LazyFragment.kt | 33 ++++++++++++++++++ .../base/lazypager/LazyPagerAdapter.kt | 13 +++++++ .../modules/base/lazypager/LazyViewPager.kt | 33 ++++++++++++++++++ .../ui/modules/homework/HomeworkFragment.kt | 4 +++ .../modules/homework/HomeworkListFragment.kt | 22 ++++++++---- .../ui/modules/messages/MessagesFragment.kt | 19 +++++++---- .../messages/MessagesListFragment.java | 34 +++++++++++++------ .../modules/timetable/TimetableDayFragment.kt | 7 ++-- .../timetable/TimetablePagerAdapter.kt | 9 ++--- app/src/main/res/layout/fragment_homework.xml | 5 ++- app/src/main/res/layout/fragment_messages.xml | 4 +-- .../main/res/layout/fragment_timetable_v2.xml | 6 ++-- 12 files changed, 148 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyFragment.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyPagerAdapter.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyViewPager.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyFragment.kt new file mode 100644 index 00000000..8af42708 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyFragment.kt @@ -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() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyPagerAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyPagerAdapter.kt new file mode 100644 index 00000000..1e0ab030 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyPagerAdapter.kt @@ -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 +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyViewPager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyViewPager.kt new file mode 100644 index 00000000..cf6324ae --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/lazypager/LazyViewPager.kt @@ -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() + } + } + }) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkFragment.kt index 24bb408a..243abcd1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkFragment.kt @@ -68,6 +68,10 @@ class HomeworkFragment : Fragment() { })) b.viewPager.adapter = MessagesFragment.Adapter(childFragmentManager).also { adapter -> + adapter.swipeRefreshLayoutCallback = { isEnabled -> + b.refreshLayout.isEnabled = isEnabled + } + adapter.addFragment(HomeworkListFragment().also { fragment -> fragment.arguments = Bundle().also { args -> args.putInt("homeworkDate", HomeworkDate.CURRENT) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkListFragment.kt index 950e6172..38a33976 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkListFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/homework/HomeworkListFragment.kt @@ -4,17 +4,18 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.databinding.HomeworkListBinding import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.utils.models.Date -class HomeworkListFragment : Fragment() { +class HomeworkListFragment : LazyFragment() { private lateinit var app: App private lateinit var activity: MainActivity @@ -30,11 +31,7 @@ class HomeworkListFragment : Fragment() { return b.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - // TODO check if app, activity, b can be null - if (!isAdded) - return - + override fun onPageCreated(): Boolean { if (arguments != null) { homeworkDate = arguments.getInt("homeworkDate", HomeworkDate.CURRENT) } @@ -45,6 +42,16 @@ class HomeworkListFragment : Fragment() { b.homeworkView.setHasFixedSize(true) 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) { HomeworkDate.CURRENT -> "eventDate >= '" + Date.getToday().stringY_m_d + "'" @@ -66,5 +73,6 @@ class HomeworkListFragment : Fragment() { b.homeworkNoData.visibility = View.VISIBLE } }) + return true } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt index abea430a..d19d0c1b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesFragment.kt @@ -6,7 +6,6 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter import androidx.viewpager.widget.ViewPager import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import pl.szczodrzynski.edziennik.App @@ -14,8 +13,9 @@ import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Message 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 java.util.* class MessagesFragment : Fragment() { companion object { @@ -56,6 +56,10 @@ class MessagesFragment : Fragment() { b.viewPager.adapter = Adapter(childFragmentManager).also { adapter -> + adapter.swipeRefreshLayoutCallback = { isEnabled -> + b.refreshLayout.isEnabled = isEnabled + } + adapter.addFragment(MessagesListFragment().also { fragment -> fragment.arguments = Bundle().also { args -> args.putInt("messageType", Message.TYPE_RECEIVED) @@ -126,11 +130,11 @@ class MessagesFragment : Fragment() { }*/ } - internal class Adapter(manager: FragmentManager) : FragmentPagerAdapter(manager) { - private val mFragmentList = ArrayList() - private val mFragmentTitleList = ArrayList() + internal class Adapter(manager: FragmentManager) : LazyPagerAdapter(manager) { + private val mFragmentList = mutableListOf() + private val mFragmentTitleList = mutableListOf() - override fun getItem(position: Int): Fragment { + override fun getItem(position: Int): LazyFragment { return mFragmentList[position] } @@ -138,7 +142,8 @@ class MessagesFragment : Fragment() { return mFragmentList.size } - fun addFragment(fragment: Fragment, title: String) { + fun addFragment(fragment: LazyFragment, title: String) { + fragment.swipeRefreshLayoutCallback = this@Adapter.swipeRefreshLayoutCallback mFragmentList.add(fragment) mFragmentTitleList.add(title) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.java index bfbf749d..4891c146 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/MessagesListFragment.java @@ -10,11 +10,10 @@ import android.view.ViewGroup; import android.view.animation.Interpolator; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.databinding.DataBindingUtil; -import androidx.fragment.app.Fragment; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; 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.MessageRecipientFull; 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.Themes; 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; -public class MessagesListFragment extends Fragment { +public class MessagesListFragment extends LazyFragment { private App app = null; private MainActivity activity = null; @@ -65,9 +66,9 @@ public class MessagesListFragment extends Fragment { } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + public boolean onPageCreated() { if (app == null || activity == null || b == null || !isAdded()) - return; + return false; long messageId = -1; if (getArguments() != null) { @@ -78,7 +79,7 @@ public class MessagesListFragment extends Fragment { args.putLong("messageId", messageId); getArguments().remove("messageId"); activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, args); - return; + return false; } if (getArguments() != null) { @@ -161,11 +162,24 @@ public class MessagesListFragment extends Fragment { // TODO ANIMATION //postponeEnterTransition(); - viewParent = (ViewGroup) view.getParent(); + viewParent = (ViewGroup) getView().getParent(); - b.emailList.setLayoutManager(new LinearLayoutManager(view.getContext())); - b.emailList.addItemDecoration(new SimpleDividerItemDecoration(view.getContext())); + b.emailList.setLayoutManager(new LinearLayoutManager(getView().getContext())); + b.emailList.addItemDecoration(new SimpleDividerItemDecoration(getView().getContext())); 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) { 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 messageFulls) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index 1966a5d6..848d0801 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding 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_START_HOUR import pl.szczodrzynski.edziennik.utils.ListenerScrollView @@ -36,7 +36,7 @@ import java.util.* import kotlin.coroutines.CoroutineContext import kotlin.math.min -class TimetableDayFragment : PagerFragment(), CoroutineScope { +class TimetableDayFragment : LazyFragment(), CoroutineScope { companion object { private const val TAG = "TimetableDayFragment" } @@ -104,9 +104,6 @@ class TimetableDayFragment : PagerFragment(), CoroutineScope { } override fun onPageCreated(): Boolean { - if (!isAdded) - return false - // observe lesson database app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons -> launch { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt index 31851498..e55e1ba8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt @@ -5,9 +5,9 @@ package pl.szczodrzynski.edziennik.ui.modules.timetable import android.os.Bundle -import androidx.fragment.app.Fragment 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.Week @@ -16,7 +16,7 @@ class TimetablePagerAdapter( private val items: List, private val startHour: Int, private val endHour: Int -) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { +) : LazyPagerAdapter(fragmentManager) { companion object { private const val TAG = "TimetablePagerAdapter" } @@ -25,8 +25,9 @@ class TimetablePagerAdapter( private val weekStart by lazy { today.weekStart } 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 { + swipeRefreshLayoutCallback = this@TimetablePagerAdapter.swipeRefreshLayoutCallback arguments = Bundle().apply { putInt("date", items[position].value) putInt("startHour", startHour) diff --git a/app/src/main/res/layout/fragment_homework.xml b/app/src/main/res/layout/fragment_homework.xml index 91d904be..4e2583c3 100644 --- a/app/src/main/res/layout/fragment_homework.xml +++ b/app/src/main/res/layout/fragment_homework.xml @@ -1,7 +1,6 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_timetable_v2.xml b/app/src/main/res/layout/fragment_timetable_v2.xml index c14b26e5..953638cf 100644 --- a/app/src/main/res/layout/fragment_timetable_v2.xml +++ b/app/src/main/res/layout/fragment_timetable_v2.xml @@ -28,7 +28,7 @@ android:id="@+id/tabLayout" android:layout_width="match_parent" android:layout_height="48dp" - android:background="@color/colorSurface_6dp" + android:background="@color/colorSurface_1dp" app:rtl_tabIndicatorColor="?colorPrimary" app:rtl_tabMaxWidth="300dp" app:rtl_tabMinWidth="90dp" @@ -40,7 +40,7 @@ app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" /> - - \ No newline at end of file +