В моем фрагменте у меня есть компонент ViewPager2
, каждая страница содержит фрагмент, который содержит только RecyclerView
для отображения списков. Элементы внутри списка предназначены для перемещения по спискам (от RecyclerView
одной страницы до RecyclerView
другой страницы). Поэтому я написал несколько logi c, чтобы обновить адаптеры RecyclerView
s, чтобы можно было перемещать элементы.
Обновление наборов данных работает должным образом, но по какой-то причине после перемещения элемента из list на другой, высота списков меняется. Такое поведение непоследовательно. Иногда все списки будут уменьшены до одинаковой высоты, иногда только у некоторых из них высота будет изменена, иногда у некоторых списков высота будет установлена на 0, а иногда все работает нормально. Установка фиксированной высоты для RecyclerView
устранила проблему, хотя я хочу, чтобы список занимал все пространство дисплея, поэтому фиксированная высота, очевидно, не является решением.
Кроме того, я не уверен если это RecyclerView
, который сжимается, а ViewPager
соответственно обновляет его высоту или наоборот.
Глядя на пример проекта Sunflower в документах Android, я не увидел никакой существенной разницы между мой проект и пример, поэтому я понятия не имею, что вызывает такое поведение. У кого-нибудь есть идеи?
Вот соответствующие части моего приложения: Примечание: все, что связано с базой данных, использует Room
API. Кроме того, адаптер для RecyclerView
изначально был RecyclerView.Adapter
, а не ListAdapter
, но поведение такое же. Я готов использовать любой из них, если проблема связана с адаптером.
MainFragment:
class MainFragment : Fragment() {
private lateinit var binding: FragmentMainBinding
private lateinit var viewPagerAdapter: ViewPagerAdapter;
private lateinit var viewPager2: ViewPager2
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentMainBinding.inflate(inflater, container, false);
return binding.root;
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Init the view pager
viewPagerAdapter = ViewPagerAdapter(this)
viewPager2 = binding.viewPager
viewPager2.adapter = viewPagerAdapter
viewPager2.isUserInputEnabled = false
//init the tab layout
binding.tabLayout.apply {
TabLayoutMediator(this, viewPager2) { tab, position ->
tab.text = TAB_LAYOUT_LABELS[position]
}.attach()
}
}
companion object {
@JvmStatic
fun newInstance() = MainFragment()
private val TAB_LAYOUT_LABELS = arrayOf("TO BE READ", "READING", "DONE")
}
}
ViewPagerAdapter:
class ViewPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 3
// TODO: Create a separate fragment for the DONE list
override fun createFragment(position: Int): Fragment {
val fragment = ReadingListFragment()
fragment.arguments = Bundle().apply {
putInt(ReadingListFragment.EXTRA_TYPE, position)
}
return fragment
}
}
ReadingListFragment
class ReadingListFragment : Fragment() {
companion object {
fun newInstance() =
ReadingListFragment()
public const val EXTRA_TYPE = "extraType"
}
private val viewModel: ReadingListViewModel by viewModels<ReadingListViewModel> {
val type = ReadingListType.getType(arguments?.getInt(EXTRA_TYPE) ?: 3)
ReadingListViewModelFactory(requireActivity().application, type)
}
private lateinit var binding: ReadingListFragmentBinding
private lateinit var readingListAdapter: ReadingListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.readingList.observe(this) {
val adapter = ReadingListAdapter(viewModel)
// binding.readingListRecyclerView.swapAdapter(adapter, false)
this.readingListAdapter.changeData(viewModel)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = ReadingListFragmentBinding.inflate(inflater, container, false)
//Init the recycler view
val layoutManager = LinearLayoutManager(activity)
this.readingListAdapter = ReadingListAdapter(viewModel)
binding.readingListRecyclerView.apply {
val value = viewModel.readingList.value
adapter = readingListAdapter
this.layoutManager = layoutManager
}
return binding.root
}
}
ReadingListViewModel:
class ReadingListViewModel(private val app: Application, private val type: ReadingListType) :
AndroidViewModel(app) {
val readingList: LiveData<List<GoodreadsBook>> by lazy {
Database.getInstance(app.applicationContext).goodreadsBookDao()
.getReadingListAsLiveData(type)
}
// Move item to the next list
fun moveToTheNextList(pos: Int) {
val item = readingList.value?.get(pos)
//Update the item in memory
if (item?.owner != null) {
val newOwner = ReadingListType.getType(item.owner!!.value + 1)
item.owner = newOwner
//Update the item in the database
viewModelScope.launch {
withContext(Dispatchers.IO) {
val db = Database.getInstance(app.applicationContext)
db.goodreadsBookDao().updateBook(item)
}
}
}
}
}
@Parcelize
enum class ReadingListType(val value: Int) : Parcelable {
TO_BE_READ(0), READING(1), DONE(2), UNSET(3);
companion object {
fun getType(value: Int) = values().first { it.value == value }
}
}
class ReadingListTypeConverter {
@TypeConverter
fun fromReadingListTypeToInt(it: ReadingListType) = it.value
@TypeConverter
fun fromIntToReadingListType(it: Int) = ReadingListType.getType(it)
}
ReadingListViewModelFactory:
class ReadingListViewModelFactory(private val app: Application, private val type: ReadingListType) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ReadingListViewModel(app, type) as T
}
ReadingListAdapter:
class ReadingListAdapter(private var viewModel: ReadingListViewModel) :
ListAdapter<GoodreadsBook, ReadingListViewHolder>(ReadingListItemDiff()) {
private var dataset = viewModel.readingList.value
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReadingListViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ReadingListItemBinding.inflate(inflater, parent, false)
return ReadingListViewHolder(binding) {
viewModel.moveToTheNextList(it)
}
}
fun changeData(newData: ReadingListViewModel) {
viewModel = newData
this.dataset = newData.readingList.value
submitList(dataset)
}
override fun getItemCount(): Int = dataset?.size ?: 0
override fun onBindViewHolder(holder: ReadingListViewHolder, position: Int) {
holder.bind(this.dataset?.get(position))
}
}
private class ReadingListItemDiff() : ItemCallback<GoodreadsBook>() {
override fun areItemsTheSame(oldItem: GoodreadsBook, newItem: GoodreadsBook): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: GoodreadsBook, newItem: GoodreadsBook): Boolean {
return oldItem.id == newItem.id
}
}
ReadingListViewHolder:
class ReadingListViewHolder(
private var binding: ReadingListItemBinding,
private val moveBookToNextList: (pos: Int) -> Unit
) :
RecyclerView.ViewHolder(binding.root) {
private var animationEndId: Int = 0;
init {
// Add the move animation
binding.readingListItemMotion.setTransitionListener(object :
MotionLayout.TransitionListener {
override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {
}
override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {
// Set the end ID
animationEndId = p2
}
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
}
override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
// Check if it's end and not start
if (p1 == animationEndId) {
moveBookToNextList(adapterPosition)
}
}
})
}
fun bind(newData: GoodreadsBook?) {
binding.book = newData;
binding.executePendingBindings()
}
}
fragment_main_main . xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:id="@+id/main_fragment__root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.main.MainFragment">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryAlt"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:tabIndicatorColor="@color/colorAccent"
app:tabTextColor="@color/design_default_color_background">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tab_item_first" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tab_item_second" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tab_item_third" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tab_layout">
</androidx.viewpager2.widget.ViewPager2>
</LinearLayout>
фрагмент_списка_ чтения. xml
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/search_result_padding"
android:orientation="vertical"
tools:context=".ui.main.readingList.ReadingListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/reading_list_recycler_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>