Android: RecyclerView случайно меняет высоту - PullRequest
0 голосов
/ 07 августа 2020

В моем фрагменте у меня есть компонент 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>

1 Ответ

0 голосов
/ 11 августа 2020

У вас есть установленные ограничения, которые не используются, потому что ваше представление не находится внутри ConstraintLayout. Измените это:

<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>
    

на это:

<androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginTop="48dp">

    </androidx.viewpager2.widget.ViewPager2>

Кроме того, измените высоту вашего вида ресайклера на:

android:layout_height="match_parent"
...