将RecyclerView中的项目移动到特定位置

时间:2019-06-12 20:09:37

标签: android kotlin android-recyclerview viewmodel

我正在使用RecyclerView填充在Room数据库中获取的数据。从Room数据库中获取或删除数据后,RecyclerView会自动更新。我已经在RecyclerView中实现了删除和撤消的滑动操作。滑动时,我会删除房间数据库中的项目,RecyclerView会自动更新。但是,当我单击“撤消”按钮时,我将该项目再次插入到Room数据库中,并且该项目显示在Recyclerview中。这里的问题是当我还原项目时,它会还原到最后一个位置。我希望将其恢复到已删除的位置,而不是最后。

我的代码如下:

fragment.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()
}
}

fragment.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
                            }
                        }
                }

我尝试获取列表,然后执行list.add(position,item),但它不起作用。有人可以帮忙吗?谢谢。

2 个答案:

答案 0 :(得分:0)

如果list.add不起作用,那么当您滑动删除内容时,我建议您使用其他方法,不要删除该项目。在模型中添加另一列isDeleted,并更改房间查询以获取isDeleted为false的所有行。

select * from table where isDeleted = 0

然后,当滑动以删除时,会将行更新为isDeleted,而撤消时只需将isDeleted设置为false。当您确定不需要撤消操作时,可以在以后删除该行。

答案 1 :(得分:0)

按如下所示更新viewmodel.kt文件中的插入功能

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

此处的索引是一个整数,它指示您要插入新项目的位置。并根据需要通知recyclerview适配器

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