0

我有一个 RecyclerView 与 Paging 一起实现,以从 Room Database 加载列表。当大小很小时,该列表可以正常工作。当大小达到 50 - 60 左右时,列表仍然可以正常工作,但是当我切换到另一个片段然后返回列表时,它会阻塞 UI 大约 1.5 - 2 秒,这在用户体验中非常沉闷(参见下面的 GIF ):

Recyclerview 问题

我的代码如下:

@Query("SELECT * FROM account_table WHERE userID = :userID")
fun getAll(userID: String): DataSource.Factory<Int, Account>

存储库

class AccountRepository private constructor(application: Application) {

private val database =
    LockyDatabase.getDatabase(
        application
    )
private val accountDao = database.accountDao()

companion object {
    @Volatile
    private var instance: AccountRepository? = null

    fun getInstance(application: Application) =
        instance ?: synchronized(this) {
            instance ?: AccountRepository(application).also { instance = it }
        }
}

fun getAll(userID: String) = accountDao.getAll(userID)

}

适配器

class CredentialsPagingAdapter(
private val clickListener: ClickListener,
private val optionsClickListener: OptionsClickListener?,
private val isSimplified: Boolean
) : PagedListAdapter<Credentials, CredentialsViewHolder>(
    diffCallback
) {
    companion object {
        private val diffCallback = object : DiffUtil.ItemCallback<Credentials>() {
            override fun areItemsTheSame(oldItem: Credentials, newItem: Credentials): Boolean {
                return oldItem.id == newItem.id
            }

        override fun areContentsTheSame(oldItem: Credentials, newItem: Credentials): Boolean {
            return oldItem.equals(newItem)
        }
    }
}

override fun onBindViewHolder(holder: CredentialsViewHolder, position: Int) {
    holder.bind(
        clickListener,
        optionsClickListener,
        getItem(position),
        isSimplified
    )
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CredentialsViewHolder {
    return CredentialsViewHolder.from(
        parent
    )
}
}

视图模型

val accounts = Transformations.switchMap(_sort) {
    when (true) {
        it.name -> _accounts.sortByEntryName
        it.username -> _accounts.sortByUsername
        it.email -> _accounts.sortByEmail
        it.website -> _accounts.sortByWebsite
        it.authType -> _accounts.sortByAuthenticationType
        else -> _accounts
    }.toLiveData(pageSize = resources.getInteger(R.integer.size_paging_list_default))
}

分段

private fun subscribeAccounts() {
    val adapter = CredentialsPagingAdapter(
        /* The click listener to handle account on clicks */
        ClickListener {
            navigateTo(
                AccountFragmentDirections.actionFragmentAccountToFragmentViewAccount(
                    it as Account
                )
            )
        },
        /* The click listener to handle popup menu for each accounts */
        OptionsClickListener { view, credential ->
            view.apply {
                isEnabled = false
            }
            createPopupMenu(view, credential as Account)
        },
        false
    )

    binding.RecyclerViewAccount.apply {
        /*
        * State that layout size will not change for better performance
        */
        setHasFixedSize(true)

        /* Bind the layout manager */
        layoutManager = LinearLayoutManager(requireContext())

        /* Bind the adapter */
        this.adapter = adapter
    }

    viewModel.accounts.observe(viewLifecycleOwner, Observer {
        if (it != null) {
            /*
             * If accounts is not null
             * Load recyclerview and
             * Update the ui
             */
            lifecycleScope.launch {
                adapter.submitList(it as PagedList<Credentials>)
            }

            updateUI(it.size)
        }
    })
}

主要活动布局

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

<androidx.drawerlayout.widget.DrawerLayout
    android:id="@+id/Drawer_Main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.main.main.MainActivity">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/Layout_Coordinator_Main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/Toolbar_Main"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@drawable/custom_rounded_background_toolbar"
            android:clipChildren="false"
            android:outlineAmbientShadowColor="@color/colorShadowColor"
            android:outlineSpotShadowColor="@color/colorShadowColor"
            android:paddingStart="8dp"
            android:paddingEnd="8dp"
            app:contentInsetStartWithNavigation="0dp"
            tools:targetApi="p">

    ...

        </com.google.android.material.appbar.MaterialToolbar>

        <androidx.core.widget.NestedScrollView
            android:id="@+id/Nested_Scroll"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="?attr/actionBarSize"
            android:fillViewport="true">

            <fragment
                android:id="@+id/Navigation_Host"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:navGraph="@navigation/navigation_drawer_main"
                tools:ignore="FragmentTagUsage" />

        </androidx.core.widget.NestedScrollView>

        <com.google.android.material.floatingactionbutton.FloatingActionButton ... />

        <com.google.android.material.floatingactionbutton.FloatingActionButton ... />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/Navigation_View"
        style="@style/Locky.Widget.Custom.NavigationView"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:clipToPadding="false"
        android:paddingStart="0dp"
        android:paddingEnd="16dp"
        app:headerLayout="@layout/drawer_header"
        app:itemTextAppearance="@style/Locky.Text.Body.Drawer"
        app:menu="@menu/menu_drawer_main" />

</androidx.drawerlayout.widget.DrawerLayout>

</layout>

片段帐户布局:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

<data>

    <import type="android.view.View" />

    <import type="com.th3pl4gu3.locky_offline.repository.Loading.List" />

    <variable
        name="ViewModel"
        type="com.th3pl4gu3.locky_offline.ui.main.main.account.AccountViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/Layout_Fragment_Account"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorOnSurface">


    <!--
       Recyclerview
    -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/RecyclerView_Account"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="@{ViewModel.loadingStatus==List.LIST ? View.VISIBLE : View.GONE}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="gone" />


    <!--
        Empty Views and group
    -->
    <androidx.constraintlayout.widget.Group
        android:id="@+id/Empty_View"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="@{ViewModel.loadingStatus==List.EMPTY_VIEW ? View.VISIBLE : View.GONE}"
        app:constraint_referenced_ids="Empty_View_Illustration,Empty_View_Title,Empty_View_Subtitle" />

    <ImageView
        android:id="@+id/Empty_View_Illustration" ... />

    <TextView
        android:id="@+id/Empty_View_Title" ... />

    <TextView
        android:id="@+id/Empty_View_Subtitle" ... />

    <!--
        Progress Bar
    -->
    <include
        android:id="@+id/Progress_Bar"
        layout="@layout/custom_view_list_loading"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="@{ViewModel.loadingStatus==List.LOADING ? View.VISIBLE : View.GONE}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Recyclerview 列表布局

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

<data>

    <import type="android.view.View" />

    <variable
        name="IsSimplifiedVersion"
        type="Boolean" />

    <variable
        name="Credential"
        type="com.th3pl4gu3.locky_offline.core.main.credentials.Credentials" />

    <variable
        name="ClickListener"
        type="com.th3pl4gu3.locky_offline.ui.main.main.ClickListener" />

    <variable
        name="OptionsClickListener"
        type="com.th3pl4gu3.locky_offline.ui.main.main.OptionsClickListener" />
</data>

<com.google.android.material.card.MaterialCardView
    style="@style/Locky.ListCardView"
    credentialCardConfiguration="@{Credential}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="4dp"
    android:layout_marginEnd="8dp"
    android:clickable="true"
    android:focusable="true"
    android:onClick="@{() -> ClickListener.onClick(Credential)}">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/Credential_Logo"
            configureLogo="@{Credential}"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_marginEnd="16dp"
            android:scaleType="centerCrop"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/Barrier_Logo"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:src="@drawable/ic_locky_with_background_circle" />

        <androidx.constraintlayout.widget.Barrier
            android:id="@+id/Barrier_Logo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:barrierDirection="end"
            app:constraint_referenced_ids="Credential_Logo" />

        <TextView
            android:id="@+id/Credential_Entry_Name"
            style="@style/Locky.Text.Title6.List"
            listTitleMessageCardEligibility="@{Credential}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:ems="11"
            android:singleLine="true"
            android:text="@{Credential.entryName}"
            app:layout_constraintBottom_toTopOf="@+id/Credential_First_Subtitle"
            app:layout_constraintStart_toEndOf="@id/Barrier_Logo"
            app:layout_constraintTop_toTopOf="@+id/Credential_Logo"
            app:layout_constraintVertical_chainStyle="spread_inside"
            tools:text="This is an entry name and it can be very very very long" />

        <TextView
            android:id="@+id/Credential_First_Subtitle"
            style="@style/Locky.Text.Subtitle.List.Primary"
            setCredentialSubtitle="@{Credential}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:ems="13"
            android:singleLine="true"
            app:layout_constraintBottom_toTopOf="@+id/Credential_Second_Subtitle"
            app:layout_constraintStart_toStartOf="@+id/Credential_Entry_Name"
            app:layout_constraintTop_toBottomOf="@+id/Credential_Entry_Name"
            app:layout_constraintVertical_chainStyle="spread"
            tools:text="This is the very first subtitle and this can be very long too" />

        <TextView
            android:id="@+id/Credential_Second_Subtitle"
            style="@style/Locky.Text.Subtitle.List.Secondary"
            setCredentialOtherSubtitle="@{Credential}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:ems="14"
            android:singleLine="true"
            android:textColor="@color/colorAccent"
            app:layout_constraintBottom_toBottomOf="@+id/Credential_Logo"
            app:layout_constraintStart_toStartOf="@+id/Credential_Entry_Name"
            app:layout_constraintTop_toBottomOf="@+id/Credential_First_Subtitle"
            app:layout_constraintVertical_chainStyle="spread"
            tools:text="This is the second subtitle and this can be very long too" />

        <androidx.constraintlayout.widget.Barrier
            android:id="@+id/Barrier_More_Options"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:barrierDirection="start"
            app:constraint_referenced_ids="Credential_More_Options" />

        <ImageButton
            android:id="@+id/Credential_More_Options"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:background="@drawable/custom_states_background_button_image"
            android:onClick="@{(view) -> OptionsClickListener.onClick(view, Credential)}"
            android:scaleType="centerCrop"
            android:src="@drawable/ic_more_options"
            android:visibility="@{IsSimplifiedVersion ? View.GONE : View.VISIBLE}"
            app:layout_constraintBottom_toBottomOf="@+id/Credential_Logo"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/Barrier_More_Options"
            app:layout_constraintTop_toTopOf="@+id/Credential_Logo" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</com.google.android.material.card.MaterialCardView>

</layout>

我的分页版本是 2.1.2

有人可以帮我解决这个问题。我尝试了几天寻找修复,但没有任何效果。

我很感激帮助。

4

1 回答 1

3

您必须在活动布局中删除NestedScrollView( Nested_Scroll) - 您不能RecyclerViewNestedScrollView.

ANestedScrollView在垂直滚动方向上展开每个子项以确定最大滚动距离。这意味着它提供了RecyclerView无限的高度来扩展。这会导致每个RecyclerView元素膨胀,破坏所有视图回收破坏分页的使用 - 给定无限的高度,它将继续要求分页越来越多的行来填充空间。

于 2020-07-27T19:48:52.380 回答