Переместить элемент в RecyclerView в определенную позицию - PullRequest
0 голосов
/ 12 июня 2019

Я использую RecyclerView для заполнения данными, которые выбираются в моей базе данных Room.Когда данные выбираются или удаляются из базы данных моей комнаты, RecyclerView обновляется автоматически.Я реализовал пролистывание для удаления и отмены в моем RecyclerView.Когда я пролистываю, я удаляю элемент в моей базе данных комнат, и RecyclerView обновляется автоматически.Однако, когда я нажимаю кнопку «Отменить», я снова вставляю элемент в базу данных Room, и элемент отображается в Recyclerview.Проблема здесь в том, что когда я восстанавливаю предмет, он восстанавливается в последней позиции.Я хочу, чтобы он восстанавливался в том положении, в котором он был удален, а не последний.

Мой код такой, как показано ниже:

фрагмент.xml

<androidx.recyclerview.widget.RecyclerView
            android:id="@id/RecyclerView_fromFragmentToDo_Main"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/Margin_Views_Small"
            android:layout_marginBottom="?attr/actionBarSize"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>

adapter.kt

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.th3pl4gu3.lifestyle.core.lifestyle.ToDo
import com.th3pl4gu3.lifestyle.databinding.CustomRecyclerviewListTodoBinding

class ToDoAdapter : ListAdapter<ToDo, ToDoAdapter.ViewHolder>(ToDoDiffCallback()) {

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val toDo = getItem(position)
    holder.bind(toDo)
}

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

class ViewHolder private constructor(val binding: CustomRecyclerviewListTodoBinding): RecyclerView.ViewHolder(binding.root) {
    fun bind(toDo: ToDo) {
        binding.myToDo = toDo
        binding.executePendingBindings()
    }

    companion object {
        fun from(parent: ViewGroup): ViewHolder {
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding = CustomRecyclerviewListTodoBinding.inflate(layoutInflater, parent, false)
            return ViewHolder(binding)
        }
    }
}
}

class ToDoDiffCallback: DiffUtil.ItemCallback<ToDo>() {

override fun areItemsTheSame(oldItem: ToDo, newItem: ToDo): Boolean {
    return oldItem.id == newItem.id
}


override fun areContentsTheSame(oldItem: ToDo, newItem: ToDo): Boolean {
    return oldItem == newItem
}

}

viewmodel.kt

import android.app.Application
import androidx.lifecycle.*
import com.th3pl4gu3.lifestyle.core.lifestyle.ToDo
import com.th3pl4gu3.lifestyle.core.operations.Filter
import com.th3pl4gu3.lifestyle.core.operations.ToDoOperations
import com.th3pl4gu3.lifestyle.database.LifestyleDatabase
import com.th3pl4gu3.lifestyle.ui.enums.ToggleButtonStates
import kotlinx.coroutines.*

class ToDoViewModel(
    val database: LifestyleDatabase,
    application: Application) : AndroidViewModel(application) {

private var _viewModelJob = Job()

private val _uiScope = CoroutineScope(Dispatchers.Main + _viewModelJob)

//Current state of the toggle button (Current button checked)
var currentToggleButtonState = ToggleButtonStates.BUTTON_ACTIVE

//Fetch all to dos from database
private var _toDos = ToDoOperations.getAllOffline(database)
val toDosMediatorLiveData = MediatorLiveData<List<ToDo>>()

init {
    //Update the list of the recyclerview on INIT
    updateList(currentToggleButtonState)
}


/**
 * Public functions that are accessible from the outside
 **/

fun updateList(toggleButton: ToggleButtonStates) {
    toDosMediatorLiveData.removeSource(_toDos)

    when(toggleButton){
        ToggleButtonStates.BUTTON_ALL ->{
            currentToggleButtonState = ToggleButtonStates.BUTTON_ALL

            toDosMediatorLiveData.addSource(_toDos){
                toDosMediatorLiveData.value = it
            }
        }

        ToggleButtonStates.BUTTON_ACTIVE ->{
            currentToggleButtonState = ToggleButtonStates.BUTTON_ACTIVE

            toDosMediatorLiveData.addSource(_toDos){
                toDosMediatorLiveData.value = Filter<ToDo>(it).getActive()
            }
        }
        ToggleButtonStates.BUTTON_COMPLETE ->{
            currentToggleButtonState = ToggleButtonStates.BUTTON_COMPLETE

            toDosMediatorLiveData.addSource(_toDos){
                toDosMediatorLiveData.value = Filter<ToDo>(it).getCompleted()
            }
        }
    }
}

fun insertItem(toDo: ToDo) {
    _uiScope.launch {
        insert(toDo)
    }
}

fun markAsDeleted(toDo: ToDo) {
    _uiScope.launch {
        remove(toDo)
    }
}

fun markItem(toDo: ToDo){
    if(toDo.dateCompleted == null){
        markAsCompleted(toDo)
    }else{
        markAsIncomplete(toDo)
    }
}


/**
 * Private functions for internal use ONLY
 **/

private fun markAsCompleted(newToDo: ToDo) {
    _uiScope.launch {
        newToDo.markAsComplete()
        update(newToDo)
    }
}

private fun markAsIncomplete(newToDo: ToDo) {
    _uiScope.launch {
        newToDo.markAsIncomplete()
        update(newToDo)
    }
}

private suspend fun insert(toDo: ToDo) {
    withContext(Dispatchers.IO) {
        toDo.add(database)
    }
}

private suspend fun remove(toDo: ToDo) {
    withContext(Dispatchers.IO) {
        toDo.delete(database)
    }
}

private suspend fun update(newToDo: ToDo) {
    withContext(Dispatchers.IO) {
        newToDo.update(database)
    }
}

/**
 * Overridden functions
 **/
override fun onCleared() {
    super.onCleared()

    //Clear the view model job when user leave
    _viewModelJob.cancel()
}
}

фрагмент.kt

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButton

import com.th3pl4gu3.lifestyle.R
import com.th3pl4gu3.lifestyle.ui.Utils.toast
import com.th3pl4gu3.lifestyle.database.LifestyleDatabase
import com.th3pl4gu3.lifestyle.databinding.FragmentToDoBinding
import com.th3pl4gu3.lifestyle.ui.Utils.action
import com.th3pl4gu3.lifestyle.ui.Utils.snackBar
import com.th3pl4gu3.lifestyle.ui.Utils.snackBarWithAction
import com.th3pl4gu3.lifestyle.ui.enums.ToggleButtonStates
import java.util.*

class FragmentToDo : Fragment() {

private lateinit var mBinding: FragmentToDoBinding
private lateinit var mToDoViewModel: ToDoViewModel

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

    //Inflate the layout for this fragment
    mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_to_do, container, false)

    //Configures the screen views (Eg. Title, appearance of top bar etc...)
    configureScreenAppearance()

    //Get the activity's application
    val application = requireNotNull(this.activity).application

    //Fetch the database
    val dataSource = LifestyleDatabase.getInstance(application)

    //Instantiate the view model factory
    val viewModelFactory = ToDoViewModelFactory(dataSource, application)

    //Instantiate the view model of this fragment
    mToDoViewModel = ViewModelProviders.of(this, viewModelFactory).get(ToDoViewModel::class.java)

    //Bind view model
    mBinding.toDoViewModel = mToDoViewModel

    //Instantiate the lifecycle owner
    mBinding.lifecycleOwner = this

    //RecyclerView's configuration
    val adapter = ToDoAdapter()
    mBinding.RecyclerViewFromFragmentToDoMain.adapter = adapter

    mToDoViewModel.toDosMediatorLiveData.observe(viewLifecycleOwner, Observer {
        it.let { x ->

            //Update the UI and determine whether recyclerview should be visible or not
            updateUI(x.isNotEmpty())

            adapter.submitList(x)
        }
    })

    //Swipe configurations
    val swipeHandler = object : ToDoSwipeToCallback(requireContext()) {
        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {

            val swipedToDo = (mBinding.RecyclerViewFromFragmentToDoMain.adapter as ToDoAdapter).currentList[viewHolder.adapterPosition]
            val fab = requireActivity().findViewById<FloatingActionButton>(R.id.FAB_fromHomeActivity_BottomAppBarAttached)
            val position = viewHolder.adapterPosition

            when(direction){
                ItemTouchHelper.LEFT -> {
                    mToDoViewModel.markItem(swipedToDo)
                }

                ItemTouchHelper.RIGHT -> {
                    mToDoViewModel.markAsDeleted(swipedToDo)

                    //Show Snackbar with 'Undo' action
                    requireActivity().findViewById<View>(android.R.id.content).snackBarWithAction(getString(R.string.Message_Exception_fromFragmentLifeStyleItems_ErrorWhileSwiping, swipedToDo.title), anchorView = fab){
                            action("Undo"){
                                mToDoViewModel.insertItem(swipedToDo)
                                //Restore Item
                            }
                        }
                }

                else ->{
                    requireContext().toast(getString(R.string.Message_Exception_fromFragmentLifeStyleItems_ErrorWhileSwiping))
                }
            }
        }
    }

    val itemTouchHelper = ItemTouchHelper(swipeHandler)
    itemTouchHelper.attachToRecyclerView(mBinding.RecyclerViewFromFragmentToDoMain)

    return mBinding.root
}


/**
 * Private functions for internal use ONLY
 **/

private fun updateUI(recyclerViewVisible: Boolean){
    if(recyclerViewVisible){
        mBinding.RecyclerViewFromFragmentToDoMain.visibility = View.VISIBLE
        mBinding.EmptyViewForRecyclerView.visibility = View.GONE
    }else{
        if(mToDoViewModel.currentToggleButtonState == ToggleButtonStates.BUTTON_COMPLETE){
            mBinding.TextViewFromFragmentToDoEmptyView.text = getString(R.string.TextView_fromToDoFragment_Message_EmptyList_Completed)
        }else if(mToDoViewModel.currentToggleButtonState == ToggleButtonStates.BUTTON_ACTIVE){
            mBinding.TextViewFromFragmentToDoEmptyView.text = getString(R.string.TextView_fromToDoFragment_Message_EmptyList_Active)
        }

        mBinding.RecyclerViewFromFragmentToDoMain.visibility = View.GONE
        mBinding.EmptyViewForRecyclerView.visibility = View.VISIBLE
    }
}

private fun configureScreenAppearance(){
    //Set title of fragment
    val screenTitle = requireActivity().findViewById<TextView>(R.id.TextView_fromHomeActivity_Screen_Title)
    screenTitle.text = getString(R.string.TextView_fromFragmentInHomeActivity_ScreenTitle_ToDo)

    //Show Top Bar
    val topBar = requireActivity().findViewById<RelativeLayout>(R.id.RelativeLayout_fromHomeActivity_TopBar)
    topBar.visibility = View.VISIBLE

    //Show Fab
    val fab = requireActivity().findViewById<FloatingActionButton>(R.id.FAB_fromHomeActivity_BottomAppBarAttached)
    fab.show()
}
}

Мне нужно восстановить предмет здесь:

ItemTouchHelper.RIGHT -> {
                    mToDoViewModel.markAsDeleted(swipedToDo)

                    //Show Snackbar with 'Undo' action
                    requireActivity().findViewById<View>(android.R.id.content).snackBarWithAction(getString(R.string.Message_Exception_fromFragmentLifeStyleItems_ErrorWhileSwiping, swipedToDo.title), anchorView = fab){
                            action("Undo"){
                                mToDoViewModel.insertItem(swipedToDo)
                                //Restore Item
                            }
                        }
                }

IПопытка получить список, а затем сделать list.add (позиция, элемент), но это не работает.Может кто-нибудь помочь, пожалуйста?Благодаря.

Ответы [ 2 ]

0 голосов
/ 13 июня 2019

Обновите вашу функцию вставки в файле viewmodel.kt следующим образом

private suspend fun insert(toDo: ToDo) {
    withContext(Dispatchers.IO) {
        toDo.add(index, database)
    }
}

Здесь index - целое число, которое указывает позицию, в которую вы хотите вставить новый элемент.И при необходимости уведомить адаптер утилизатора следующим образом:

//Show Snackbar with 'Undo' action
requireActivity().findViewById<View>(android.R.id.content).snackBarWithAction(getString(R.string.Message_Exception_fromFragmentLifeStyleItems_ErrorWhileSwiping, swipedToDo.title), anchorView = fab){
    action("Undo"){
        mToDoViewModel.insertItem(swipedToDo)
        adapter.notifyItemInserted(index)
        //Restore Item
    }
}
0 голосов
/ 12 июня 2019

Если list.add не работает, я бы предложил другой подход, когда вы проводите, чтобы удалить, не удаляйте элемент. Добавьте еще один столбец isDeleted в вашей модели и измените запрос помещения, чтобы выбрать все строки, в которых isDeleted имеет значение false.

select * from table where isDeleted = 0

Затем, когда вы удаляете пальцем, обновите строку, установив isDeleted true, а когда вы отмените, просто установите isDeleted в false. Вы можете удалить строку позднее, когда будете уверены, что отмена не требуется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...