java - findContainingViewHolder 返回 null
问题描述
在以下应用程序中,您基本上有 2 个片段:1)食物数据库,2)消费食物列表
1) 用户可以手动添加具有相应宏量营养素信息(千卡、碳水化合物、蛋白质等)的食物。数据通过 Room 存储在 SQL 数据库中,并通过 LiveData 进行观察
2) 一个片段 (FoodDiaryFragment.kt),它通过 recyclerView 显示“消耗”的食物。用户可以通过点击将用户发送到另一个片段 (AddConsumedFoodFragment.kt) 的 FAB 来添加食物。这显示了一个微调器和一个表单。微调器显示食物数据库中的食物列表(从 1 开始)。在表格中,用户只需输入一个值(已消耗多少克所选食物)。这些数据也被添加到同一个 SQL 数据库中的一个表中:id(自动生成)、amount(由用户输入)、consumedFood(从微调器中选择并通过外键连接到另一个表)和consumedDate(自动生成的日期在添加项目的那一刻)。
回收站视图 (2) 显示带有在 RecyclerViewAdapter 中计算的附加信息的项目(基本上乘以数量和相应的常量营养素信息 [千卡、碳水化合物等],请参阅适配器中的 getDailyValues())以及“组”按日期列出的项目。这意味着,对于每一天,一个单独的 viewHolder (DateViewHolder) 显示当天(以及每个宏的总和)被添加到 recyclerView(参见 FoodDiaryFragment.kt 中的 sortAndGroupFood())
当我使用 RecyclerView 时,选择 setStableIds() (参见初始化块)设置为 true。StableIdKeyProvider.java 使用 onChilViewDetachedFromWindows() 函数添加了 ChildAttachStateChangeListener。
我不确定何时调用此函数(我希望得到解释),但这似乎有时会发生,当删除一个项目(可以通过 RecyclerView.Selection 删除项目)或添加一个项目时。应用程序崩溃并出现下面显示的 NullPointerException。当应用程序重新启动时,最终以崩溃结束的操作已成功执行。
我真的很绝望,并试图弄清楚为什么一个空视图现在传递给这个函数几个小时,但显然我没有成功。
根据文档findContainingViewHolder 如果提供的视图不是该 RecyclerView 的后代,则返回 null
问题:什么问题导致 null 的返回,我该如何处理它? GitHub 仓库链接
食物日记片段.kt
import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.selection.*
import androidx.recyclerview.widget.RecyclerView
import com.hooni.macrotracker.R
import com.hooni.macrotracker.adapter.ConsumedFoodRecyclerViewAdapter
import com.hooni.macrotracker.data.ConsumedFood
import com.hooni.macrotracker.recyclerviewselector.ConsumedFoodItemDetailsLookup
import com.hooni.macrotracker.viewmodels.FoodViewModel
import kotlinx.android.synthetic.main.fragment_food_diary.view.*
import java.text.DateFormat
import java.text.SimpleDateFormat
class FoodDiaryFragment : Fragment() {
private lateinit var foodViewModel: FoodViewModel
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: ConsumedFoodRecyclerViewAdapter
private val dateFormat = DateFormat.getDateInstance()
var tracker: SelectionTracker<Long>? = null
var actionMode: ActionMode? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.fragment_food_diary, container, false)
initRecyclerView(v)
initViewModel()
initButtons(v)
// this makes sure, that in case the last destination was addConsumedFoodFragment
// the 'back' button doesn't bring you back to the addConsumedFoodFragment, but to the one that
// has been visited before
findNavController().popBackStack(R.id.addConsumedFoodFragment, true)
return v
}
private fun initButtons(v: View) {
val addNewFood = v.addNewConsumedFood
addNewFood.setOnClickListener {
findNavController().navigate(R.id.action_diaryFragment_to_addConsumedFoodFragment)
}
}
private fun initRecyclerView(view: View) {
recyclerView = view.food_diary_recyclerView
adapter = ConsumedFoodRecyclerViewAdapter()
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(activity)
tracker = SelectionTracker.Builder<Long>(
"selectedItemsConsumedFoo",
recyclerView,
StableIdKeyProvider(recyclerView),
ConsumedFoodItemDetailsLookup(recyclerView),
StorageStrategy.createLongStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
val actionModeCallBack = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
val inflater = mode?.menuInflater
actionMode = mode
actionMode?.title = getString(R.string.delete)
inflater?.inflate(R.menu.action_menu, menu)
return true
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return when (item?.itemId) {
R.id.action_menu_delete -> {
removeItems(tracker?.selection!!)
tracker?.clearSelection()
actionMode?.finish()
actionMode = null
return true
}
else -> false
}
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onDestroyActionMode(mode: ActionMode?) {
tracker?.clearSelection()
actionMode?.finish()
actionMode = null
}
}
tracker?.addObserver(
object : SelectionTracker.SelectionObserver<Long>() {
override fun onSelectionChanged() {
if (tracker?.selection!!.size() > 0) {
if (actionMode == null) activity?.startActionMode(actionModeCallBack)
} else {
actionMode?.finish()
actionMode = null
}
}
}
)
adapter.tracker = tracker
}
private fun removeItems(selection: Selection<Long>) {
val foodsToDelete = adapter.getFood(selection)
foodsToDelete.forEach {
foodViewModel.deleteConsumedFood(it)
}
}
private fun initViewModel() {
foodViewModel = ViewModelProvider(this).get(FoodViewModel::class.java)
foodViewModel.allConsumedFood.observe(viewLifecycleOwner, Observer { consumedFoodList ->
consumedFoodList?.let { adapter.setConsumedFood(sortAndGroupFood(consumedFoodList)) }
})
foodViewModel.allFood.observe(viewLifecycleOwner, Observer { food ->
food?.let { adapter.setFood(food.sortedBy {it.foodName}) }
})
}
private fun sortAndGroupFood(consumedFoodList: List<ConsumedFood>): List<ConsumedFood> {
val sortedList = consumedFoodList.sortedBy{it.consumedDate}
val groupedMap: Map<String,List<ConsumedFood>> = sortedList.groupBy { dateFormat.format(it.consumedDate)}
val finalizedList = mutableListOf<ConsumedFood>()
groupedMap.forEach {
finalizedList.add(createDateHeader(it.key))
finalizedList.addAll(it.value)
}
return finalizedList.toList()
}
private fun createDateHeader(dateString: String): ConsumedFood {
val date = dateFormat.parse(dateString)
return ConsumedFood(null,-1,"DATE_HEADER",date!!)
}
}
AddFoodFragment.kt
package com.hooni.macrotracker.fragments
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.google.android.material.textfield.TextInputLayout
import com.hooni.macrotracker.R
import com.hooni.macrotracker.data.Food
import com.hooni.macrotracker.util.Tools
import com.hooni.macrotracker.viewmodels.FoodViewModel
import kotlinx.android.synthetic.main.fragment_add_food.view.*
import java.text.DecimalFormatSymbols
import java.util.*
class AddFoodFragment : Fragment() {
private lateinit var foodViewModel: FoodViewModel
private lateinit var enterTextFoodName: TextInputLayout
private lateinit var enterTextKcal: TextInputLayout
private lateinit var enterTextCarbs: TextInputLayout
private lateinit var enterTextProtein: TextInputLayout
private lateinit var enterTextFat: TextInputLayout
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.fragment_add_food, container, false)
initButtons(v)
initViewModel()
initTextInPutLayouts(v)
return v
}
private fun initTextInPutLayouts(v: View) {
enterTextFoodName = v.enter_food_name
enterTextKcal = v.enter_kcal
enterTextCarbs = v.enter_carbs
enterTextProtein = v.enter_protein
enterTextFat = v.enter_fat
enterTextCarbs.setNumberDecimalInputOnly()
enterTextProtein.setNumberDecimalInputOnly()
enterTextFat.setNumberDecimalInputOnly()
}
private fun initButtons(view: View) {
view.cancel_add_food.setOnClickListener {
findNavController().navigate(R.id.action_addFoodFragment_to_foodListFragment)
Tools.hideSoftKeyboard(view, context)
}
view.add_food.setOnClickListener {
if (validateValues()) {
val newFood = createNewFood()
foodViewModel.insertFood(newFood)
showSuccessfulAdd()
view.cancel_add_food.performClick()
}
}
}
private fun validateValues(): Boolean {
val isFoodNameValid = enterTextFoodName.validateInput { it != null }
val isKcalValid = enterTextKcal.validateInput { it?.toIntOrNull() != null }
val isCarbsValid = enterTextCarbs.validateInput { it?.toDoubleOrNull() != null }
val isProteinValid = enterTextProtein.validateInput { it?.toDoubleOrNull() != null }
val isFatValid = enterTextFat.validateInput { it?.toDoubleOrNull() != null }
return (isFoodNameValid
&& isKcalValid
&& isCarbsValid
&& isProteinValid
&& isFatValid)
}
private fun initViewModel() {
foodViewModel = ViewModelProvider(this).get(FoodViewModel::class.java)
}
private fun createNewFood(): Food {
val foodName =
enterTextFoodName.editText?.text.toString().trim()
val kcal =
enterTextKcal.editText?.text.toString().trim().toInt()
val carbs =
enterTextCarbs.editText?.text.toString().replaceDecimalSeparator().toDouble()
val protein =
enterTextProtein.editText?.text.toString().replaceDecimalSeparator().toDouble()
val fat = enterTextFat.editText?.text.toString().replaceDecimalSeparator().toDouble()
return Food(foodName, kcal, carbs.round(), fat.round(), protein.round())
}
private fun showSuccessfulAdd() {
Toast.makeText(
activity,
"${enterTextFoodName.editText?.text.toString().trim()} added to database",
Toast.LENGTH_SHORT
).show()
}
private inline fun TextInputLayout.validateInput(validate: (String?) -> Boolean): Boolean {
val textToValidate = this.editText?.text.toString().replaceDecimalSeparator().trim()
when {
textToValidate.isEmpty() -> {
error = getString(R.string.cant_be_empty)
}
!validate(textToValidate) -> {
error = getString(R.string.invalid_value)
}
else -> {
error = null
return true
}
}
return false
}
private fun TextInputLayout.setNumberDecimalInputOnly() {
this.editText?.apply {
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
}
}
private fun Double.round(decimals: Int = 1): Double = "%.${decimals}f".format(Locale.US,this).toDouble()
@SuppressLint("NewApi")
private fun String.replaceDecimalSeparator(): String {
val decimalSeparator = when(Build.VERSION.SDK_INT) {
in Int.MIN_VALUE..Build.VERSION_CODES.M -> {DecimalFormatSymbols(resources.configuration.locale).decimalSeparator}
else -> DecimalFormatSymbols(resources.configuration.locales[0]).decimalSeparator
}
return this.replace(decimalSeparator, '.')
}
}
ConsumedFoodRecyclerViewAdapter.kt
package com.hooni.macrotracker.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.selection.ItemDetailsLookup
import androidx.recyclerview.selection.Selection
import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.widget.RecyclerView
import com.hooni.macrotracker.R
import com.hooni.macrotracker.data.ConsumedFood
import com.hooni.macrotracker.data.Food
import kotlinx.android.synthetic.main.fragment_food_diary_date_list_item.view.*
import kotlinx.android.synthetic.main.fragment_food_diary_list_item.view.*
import java.text.DateFormat
import java.util.*
private const val DATE_VIEW_HOLDER = 0
private const val CONSUMED_FOOD_VIEW_HOLDER = 1
class ConsumedFoodRecyclerViewAdapter : RecyclerView.Adapter<ConsumedFoodRecyclerViewAdapter.BaseViewHolder<*>>() {
private var mConsumedFoodList = emptyList<ConsumedFood>()
private var mFoodList = emptyList<Food>()
var tracker: SelectionTracker<Long>? = null
init {
setHasStableIds(true)
}
abstract class BaseViewHolder<T>(itemView: View): RecyclerView.ViewHolder(itemView) {
abstract fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long>
}
inner class ConsumedFoodViewHolder(mView: View) :BaseViewHolder<ConsumedFood>(mView) {
private val mConsumedFoodName: TextView = mView.consumed_food_name
private val mConsumedKcal: TextView = mView.consumed_kcal
private val mConsumedAmount: TextView = mView.consumed_amount
private val mConsumedCarbs: TextView = mView.consumed_carb_amount
private val mConsumedProtein: TextView = mView.consumed_protein_amount
private val mConsumedFat: TextView = mView.consumed_fat_amount
override fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object : ItemDetailsLookup.ItemDetails<Long>() {
override fun getSelectionKey(): Long? = itemId
override fun getPosition(): Int = adapterPosition
}
fun bind(isActivated: Boolean = false, item: ConsumedFood) {
val consumedMacroOfFood = calculateConsumedMacro(item.amount,item.consumedFood)
itemView.isActivated = isActivated
mConsumedFoodName.text = item.consumedFood
mConsumedKcal.text = itemView.context.getString(R.string.list_item_kcal,consumedMacroOfFood[0].toInt())
mConsumedAmount.text = itemView.context.getString(R.string.list_item_amount,item.amount)
mConsumedCarbs.text = itemView.context.getString(R.string.list_item_macro,consumedMacroOfFood[1])
mConsumedProtein.text = itemView.context.getString(R.string.list_item_macro,consumedMacroOfFood[2])
mConsumedFat.text = itemView.context.getString(R.string.list_item_macro,consumedMacroOfFood[3])
}
}
inner class DateViewHolder(mView: View): BaseViewHolder<ConsumedFood>(mView) {
private val mConsumedDate: TextView = mView.food_diary_date
private val mTotalConsumedDayKcal: TextView = mView.food_diary_date_sum_kcal
private val mTotalConsumedDayCarbs: TextView = mView.food_diary_date_sum_carbs
private val mTotalConsumedDayProtein: TextView = mView.food_diary_date_sum_protein
private val mTotalConsumedDayFat: TextView = mView.food_diary_date_sum_fat
override fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object : ItemDetailsLookup.ItemDetails<Long>() {
override fun getSelectionKey(): Long? = itemId
override fun getPosition(): Int = adapterPosition
}
fun bind(item: ConsumedFood) {
mConsumedDate.text = itemView.context
.getString(R.string.list_item_date,DateFormat.getDateInstance(DateFormat.MEDIUM,DateFormat.getAvailableLocales()[0]).format(item.consumedDate))
mTotalConsumedDayKcal.text = itemView.context.getString(R.string.list_item_kcal,getDailyValues(item.consumedDate)[0].toInt())
mTotalConsumedDayCarbs.text = itemView.context.getString(R.string.list_item_macro,getDailyValues(item.consumedDate)[1])
mTotalConsumedDayProtein.text = itemView.context.getString(R.string.list_item_macro,getDailyValues(item.consumedDate)[2])
mTotalConsumedDayFat.text = itemView.context.getString(R.string.list_item_macro,getDailyValues(item.consumedDate)[3])
}
}
override fun getItemViewType(position: Int): Int {
return if(mConsumedFoodList[position].amount < 0) {
DATE_VIEW_HOLDER
} else CONSUMED_FOOD_VIEW_HOLDER
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<ConsumedFood> {
return when(viewType) {
DATE_VIEW_HOLDER -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_food_diary_date_list_item,parent,false)
DateViewHolder(view)
}
//CONSUMED_FOOD_VIEW_HOLDER
else -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_food_diary_list_item,parent,false)
ConsumedFoodViewHolder(view)
}
}
}
override fun getItemCount(): Int = mConsumedFoodList.size
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
when(holder) {
is DateViewHolder -> {
tracker?.let {
holder.bind(mConsumedFoodList[position])
}
}
is ConsumedFoodViewHolder -> {
tracker?.let {
holder.bind(it.isSelected(position.toLong()),mConsumedFoodList[position])
}
}
}
}
override fun getItemId(position: Int): Long = position.toLong()
internal fun getFood(ids: Selection<Long>): List<ConsumedFood> {
val foodsToDelete: MutableList<ConsumedFood> = mutableListOf()
ids.forEach {
foodsToDelete.add(mConsumedFoodList[it.toInt()])
}
return foodsToDelete.toList()
}
internal fun setConsumedFood(consumedFood: List<ConsumedFood>) {
mConsumedFoodList = consumedFood
notifyDataSetChanged()
}
internal fun setFood(food: List<Food>) {
mFoodList = food
notifyDataSetChanged()
}
private fun calculateConsumedMacro(amount: Int, food: String): List<Double>{
val kcalOfTheFood = mFoodList.firstOrNull{ it.foodName == food}?.kcal?.toDouble() ?: 0.0
val carbsOfTheFood = mFoodList.firstOrNull{ it.foodName == food}?.carbs ?: 0.0
val proteinOfTheFood = mFoodList.firstOrNull{ it.foodName == food}?.protein ?: 0.0
val fatOfTheFood = mFoodList.firstOrNull{ it.foodName == food}?.fat ?: 0.0
return listOf(kcalOfTheFood.getAmountOfMacro(amount),
carbsOfTheFood.getAmountOfMacro(amount),
proteinOfTheFood.getAmountOfMacro(amount),
fatOfTheFood.getAmountOfMacro(amount))
}
private fun Double.getAmountOfMacro(amount: Int): Double {
return this * amount / 100
}
private fun getDailyValues(day: Date): List<Double> {
val foodsOfSameDay = mConsumedFoodList.filter{ DateFormat.getDateInstance().format(it.consumedDate) == DateFormat.getDateInstance().format(day)}
val macrosOfSameDay = mutableListOf<List<Double>>()
foodsOfSameDay.forEach {macrosOfSameDay.add(calculateConsumedMacro(it.amount,it.consumedFood))}
val sumKcal = macrosOfSameDay.sumByDouble { it[0] }
val sumCarbs = macrosOfSameDay.sumByDouble { it[1] }
val sumProtein = macrosOfSameDay.sumByDouble { it[2] }
val sumFat = macrosOfSameDay.sumByDouble { it[3] }
return listOf(sumKcal,sumCarbs,sumProtein,sumFat)
}
}
错误
2020-02-18 18:34:07.287 28614-28614/com.hooni.macrotracker E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.hooni.macrotracker, PID: 28614
java.lang.NullPointerException: Attempt to invoke virtual method 'int androidx.recyclerview.widget.RecyclerView$ViewHolder.getAdapterPosition()' on a null object reference
at androidx.recyclerview.selection.StableIdKeyProvider.onDetached(StableIdKeyProvider.java:90)
at androidx.recyclerview.selection.StableIdKeyProvider$1.onChildViewDetachedFromWindow(StableIdKeyProvider.java:69)
at androidx.recyclerview.widget.RecyclerView.dispatchChildDetached(RecyclerView.java:7546)
at androidx.recyclerview.widget.RecyclerView.removeDetachedView(RecyclerView.java:4349)
at androidx.recyclerview.widget.RecyclerView$Recycler.getScrapOrCachedViewForId(RecyclerView.java:6738)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6189)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1812)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1656)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1565)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:753)
at android.view.View.layout(View.java:20672)
at android.view.ViewGroup.layout(ViewGroup.java:6194)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2792)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2319)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1460)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7183)
2020-02-18 18:34:07.288 28614-28614/com.hooni.macrotracker E/AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949)
at android.view.Choreographer.doCallbacks(Choreographer.java:761)
at android.view.Choreographer.doFrame(Choreographer.java:696)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
向 recyclerView 添加新项目时。
解决方案
问题与 SelectrionTracker(RecyclerView 选择,刚刚简单提及)有关,它是 StableIdKeyProvider。
所以......显然,当StableIdKeyProvider 试图删除/替换稳定ID 而项目(cardview)不再附加到ViewHolder(DateViewHolder 或ConsumedFoodViewHolder)时,就会发生这种崩溃。
老实说,我仍然不明白这背后的逻辑,但一旦我明白了,我会回来更新这篇文章。
解决方案是提供一个自定义 KeyProvider,如下所示:
class ConsumedFoodListItemKeyProvider(private val recyclerView: RecyclerView) : ItemKeyProvider<Long>(ItemKeyProvider.SCOPE_MAPPED) {
override fun getKey(position: Int): Long? {
return recyclerView.adapter?.getItemId(position)
}
override fun getPosition(key: Long): Int {
val viewHolder = recyclerView.findViewHolderForItemId(key)
return viewHolder?.layoutPosition ?: RecyclerView.NO_POSITION
}
}
并在 SelectionTracker.Builder 中使用它
推荐阅读
- javascript - 从 Angular 中的 @Input 更新 ngOnChanges 视图
- javascript - CKFinder - 图像切割到指定尺寸
- amazon-web-services - SonicWall 和 AWS 之间的站点到站点连接 - IAM 策略
- mongodb - 汇总聚合中的所有命名字段
- sql-server-2016 - 为什么 SQL Server 不能正确显示 sys.columns.object_id?
- r - 使用 ggplot2 绘制条形图不起作用
- apache-kafka - 在 apache camel 的 .to() 方法中使用交换消息
- android - 每次获取不同App的Hash String | 短信检索 API
- ruby-on-rails - 如何将 ahoy 电子邮件与仅 api 应用程序一起使用?
- scala - 包含广义类型约束的类的模式匹配