我正在使用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),但它不起作用。有人可以帮忙吗?谢谢。
答案 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
}
}