Я попробовал обновить Recyclerview с помощью notifyDatasetchAnted () и uppertist () , но проблема сохраняется. Функция устанавливает isselectionmode true и попытки отобразить inding.customsearchView, установив его видимость для просмотра. Visible < /code>.
Несмотря на это, CustomSearchView (edittext) не появляется на экране. Проблема: флажок не появляется
in clipadapter, когда IsseLectionMode True, я скрываю ImageView MotionOptions и показываю SelectionCheckbox. < /P>
Я попытался придумать обновление, позвонив по спискам () < /код>, когда IsseLectionMode изменился, но это не помогло. clip_item.xml
Код: Выделить всё
Код: Выделить всё
class ClipAdapter(
private val selectionListener: OnItemSelectionListener) : ListAdapter(ClipDiffCallback) {
var isSelectionMode: Boolean = false
set(value) {
field = value
val newList = currentList.map { it.copy() }
submitList(newList)
}
private val selectionState: MutableMap = mutableMapOf()
fun updateSelectionState(clipId: Long, isSelected: Boolean) {
selectionState[clipId] = isSelected
notifyItemChanged(currentList.indexOfFirst { it.id == clipId })
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ClipViewHolder {
val binding = ClipItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ClipViewHolder(binding)
}
override fun onBindViewHolder(holder: ClipViewHolder, position: Int) {
val clip = getItem(position)
Log.d("ClipAdapter", "Binding item at position $position: ${clip.title}")
holder.bind(clip)
}
inner class ClipViewHolder(
private val binding: ClipItemBinding
): RecyclerView.ViewHolder(binding.root) {
fun bind(clip: Clip) {
binding.apply {
if (clip.artUri != null) {
Glide.with(binding.root)
.load(clip.artUri)
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.into(albumImage)
} else {
albumImage.setImageResource(R.mipmap.ic_launcher)
}
clipTitle.text = clip.title
clipArtist.text = clip.artist ?: ""
if (isSelectionMode) {
moreOptions.visibility = View.GONE
selectionCheckBox.visibility = View.VISIBLE
selectionCheckBox.isChecked = clip.isSelected
selectionCheckBox.setOnCheckedChangeListener { _, isChecked ->
clip.isSelected = isChecked
selectionListener.onItemSelectionChanged(adapterPosition, isChecked)
}
} else {
moreOptions.visibility = View.VISIBLE
selectionCheckBox.visibility = View.GONE
selectionCheckBox.setOnCheckedChangeListener(null)
}
}
}
}
companion object {
val ClipDiffCallback = object : DiffUtil.ItemCallback() {
override fun areItemsTheSame(oldItem: Clip, newItem: Clip): Boolean {
val result = oldItem.id == newItem.id
Log.d(
"ClipDiffCallback",
"areItemsTheSame: oldItem.id=${oldItem.id}, newItem.id=${newItem.id}, result=$result"
)
return result
}
override fun areContentsTheSame(oldItem: Clip, newItem: Clip): Boolean {
val result = oldItem.title == newItem.title &&
oldItem.artist == newItem.artist &&
oldItem.artUri == newItem.artUri &&
oldItem.isSelected == newItem.isSelected
Log.d(
"ClipDiffCallback",
"areContentsTheSame: oldItem=$oldItem, newItem=$newItem, result=$result"
)
return result
}
}
}
}
Код: Выделить всё
interface OnItemSelectionListener {
fun onItemSelectionChanged(position: Int, isSelected: Boolean)
}
@AndroidEntryPoint
class HomeFragment : Fragment(), MenuProvider, OnItemSelectionListener {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var requestPermissionLauncher: ActivityResultLauncher
private val clipViewModel by viewModels()
private val selectedPositions: MutableSet = mutableSetOf()
private var clipAdapter: ClipAdapter? = null
private var mMainActivityUiController: MainActivityUiController? = null
private var actionMode: ActionMode? = null
private var originalClips: List = emptyList()
private var clipPosition: Int = 0
private var menu: Menu? = null
private var isSelectionMode: Boolean = false
set(value) {
field = value
clipAdapter?.isSelectionMode = value
if (!value) {
selectedPositions.clear()
val newList = clipAdapter?.currentList?.map { it.copy(isSelected = false) }
binding.clipsRV.post { clipAdapter?.submitList(newList) }
}
}
private var menuHost: MenuHost? = null
fun hideMenu() {
isSelectionMode = true
menu?.setGroupVisible(0, false)
}
fun showMenu() {
isSelectionMode = false
menu?.setGroupVisible(0, true)
(requireActivity() as MainActivity).getToolbar().visibility = View.VISIBLE
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
Log.d(TAG, "Adding MenuProvider for HomeFragment")
menuHost = requireActivity()
menuHost?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.STARTED)
this.clipAdapter = ClipAdapter(this)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupPermissionLauncher()
checkAndRequestPermissions()
observeClips()
setupRecyclerView()
binding.clipsRV.visibility = View.VISIBLE
binding.customSearchView.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
Log.d(TAG, "Filtering with query: $s")
filterSelectedClips(s.toString())
}
override fun afterTextChanged(s: Editable?) {}
})
if (activity is MainActivityUiController) {
mMainActivityUiController = activity as MainActivityUiController
}
}
override fun onResume() {
super.onResume()
(requireActivity() as MainActivity).getToolbar().visibility = View.VISIBLE
mMainActivityUiController?.showTabLayout()
requireActivity().invalidateOptionsMenu()
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
this.menu = menu
menu.clear()
menuInflater.inflate(R.menu.clips_home_menu, menu)
customizeMenuItem(menu, R.id.sortBy, "Sort by")
customizeMenuItem(menu, R.id.change_layout, "Change layout")
if (isSelectionMode) {
menu.setGroupVisible(0, false)
}
setupSearchView(menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.select -> {
isSelectionMode = !isSelectionMode
if (isSelectionMode) {
startSelectionMode()
} else {
actionMode?.finish()
}
true
}
R.id.sortBy -> {
true
}
else -> false
}
}
true
}
R.id.change_layout -> {
true
}
else -> false
}
}
private fun setupSearchView(menu: Menu) {
val searchManager = requireActivity().getSystemService(SEARCH_SERVICE) as SearchManager
val searchView = menu.findItem(R.id.app_bar_search).actionView as SearchView
searchView.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
searchView.queryHint = getString(R.string.library_search)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(keyword: String): Boolean {
if (keyword.isEmpty()) {
Snackbar.make(
binding.root,
"please enter keyword to search",
Snackbar.LENGTH_SHORT
).show()
return false
}
filterClips(keyword)
return false
}
override fun onQueryTextChange(newText: String): Boolean {
return true
}
})
}
private fun filterClips(keyword: String) {
val filteredList = if (keyword.isEmpty()) {
originalClips
} else {
originalClips.filter {
it.title.contains(keyword, ignoreCase = true)
}
}
clipAdapter?.submitList(filteredList)
}
private fun filterSelectedClips(keyword: String) {
val filteredList = if (keyword.isEmpty()) {
originalClips
} else {
originalClips.filter {
it.title.contains(keyword, ignoreCase = true)
}
}
clipAdapter?.submitList(filteredList)
}
private fun startSelectionMode() {
isSelectionMode = true
hideMenu()
binding.customSearchView.visibility = View.VISIBLE
mMainActivityUiController?.hideTabLayout()
actionMode = (requireActivity() as MainActivity).startSupportActionMode(object :
ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
(requireActivity()as MainActivity).getToolbar().visibility = View.GONE
mode.menuInflater.inflate(R.menu.select_all_menu, menu)
mode.title = "${selectedPositions.size} clips selected"
updateActionModeTitle()
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_select_all -> {
selectAll()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode) {
isSelectionMode = false
clipAdapter?.isSelectionMode = false
showMenu()
mMainActivityUiController?.showTabLayout()
binding.customSearchView.visibility = View.GONE
binding.customSearchView.text.clear()
clipAdapter?.submitList(originalClips)
actionMode = null
binding.clipsRV.scrollToPosition(0)
requireActivity().invalidateOptionsMenu()
}
})
}
private fun updateActionModeTitle() {
val count = selectedPositions.size
actionMode?.title = "$count selected clips"
}
override fun onItemSelectionChanged(position: Int, isSelected: Boolean) {
if (isSelected) {
selectedPositions.add(position)
} else {
selectedPositions.remove(position)
}
val newList = clipAdapter?.currentList!!.toMutableList()
newList[position] = newList[position].copy(isSelected = isSelected)
binding.clipsRV.post { clipAdapter?.submitList(newList) }
updateActionModeTitle()
}
private fun selectAll() {
if (clipAdapter?.currentList?.size == selectedPositions.size) {
selectedPositions.clear()
val newList = clipAdapter?.currentList!!.map { it.copy(isSelected = false) }
binding.clipsRV.post { clipAdapter?.submitList(newList) }
} else {
selectedPositions.clear()
selectedPositions.addAll(clipAdapter?.currentList!!.indices)
val newList = clipAdapter?.currentList?.map { it.copy(isSelected = true) }
binding.clipsRV.post { clipAdapter?.submitList(newList) }
}
updateActionModeTitle()
}
private fun setupPermissionLauncher() {
requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
clipViewModel.syncAndFetchClips()
} else {
val permissionToRequest =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_AUDIO
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
if (!shouldShowRequestPermissionRationale(permissionToRequest)) {
AlertDialog.Builder(requireContext())
.setTitle("Permission Denied")
.setMessage("This app needs access to your music files to display and play them. Please enable the permission in app settings.")
.setPositiveButton("Go to Settings") { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data =
Uri.fromParts("package", requireContext().packageName, null)
startActivity(intent)
}
.setNegativeButton("Cancel") { _, _ ->
Toast.makeText(
requireContext(),
"Permission denied. Cannot access music files.",
Toast.LENGTH_SHORT
).show()
}
.show()
} else {
Toast.makeText(
requireContext(),
"Permission denied. Cannot access music files.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
private fun checkAndRequestPermissions() {
val permissionToRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_AUDIO
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
if (ContextCompat.checkSelfPermission(
requireContext(),
permissionToRequest
) != PackageManager.PERMISSION_GRANTED
) {
if (shouldShowRequestPermissionRationale(permissionToRequest)) {
showPermissionRationaleDialog(permissionToRequest)
} else {
requestPermissionLauncher.launch(permissionToRequest)
}
} else {
clipViewModel.syncAndFetchClips()
}
}
private fun showPermissionRationaleDialog(permission: String) {
AlertDialog.Builder(requireContext())
.setTitle("Permission Required")
.setMessage("This app needs access to your music files to display and play them. Please grant the permission to continue.")
.setPositiveButton("Grant") { _, _ ->
requestPermissionLauncher.launch(permission)
}
.setNegativeButton("Deny") { _, _ ->
Toast.makeText(
requireContext(),
"Permission denied. Cannot access music files.",
Toast.LENGTH_SHORT
).show()
}
.show()
}
private fun observeClips() {
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
clipViewModel.clipList.collect { resource ->
when (resource) {
is Resource.Loading -> {
binding.progressBar.visibility = View.VISIBLE
}
is Resource.Success -> {
binding.progressBar.visibility = View.GONE
originalClips = resource.data ?: emptyList()
Log.d(TAG, "Clips loaded, size: ${originalClips.size}")
clipAdapter?.submitList(originalClips) {
Log.d(TAG, "Submitted original clips to clipAdapter")
}
}
is Resource.Error -> {
binding.progressBar.visibility = View.GONE
Toast.makeText(
requireContext(),
"Error: ${resource.message}",
Toast.LENGTH_SHORT
).show()
}
is Resource.Empty -> {
binding.progressBar.visibility = View.GONE
}
else -> {}
}
}
}
}
}
private fun setupRecyclerView() {
binding.clipsRV.apply {
setHasFixedSize(true)
setItemViewCacheSize(10)
layoutManager =
LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
adapter = clipAdapter
onItemClick { _, position, _ ->
if (!isSelectionMode) {
clipPosition = position
val navController = requireActivity()
.findNavController(R.id.nav_host_fragment_container)
(requireActivity() as MainActivity).showNavHostFragmentContainer()
if (navController.currentDestination?.id == R.id.homeFragment) {
navController.navigate(
HomeFragmentDirections.actionHomeFragmentToPlayerFragment(
clipPosition,
clipsArray = clipAdapter?.currentList!!.toTypedArray()
)
)
}
} else {
val clip = clipAdapter?.currentList[position]
val newCheckedState = !clip?.isSelected!!
clip.isSelected = newCheckedState
onItemSelectionChanged(position, newCheckedState)
}
}
onItemCheckChange { _, position, isChecked ->
if (isSelectionMode) {
onItemSelectionChanged(position, isChecked)
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
Log.d(TAG, "Removing MenuProvider for HomeFragment")
clipAdapter = null
menuHost?.removeMenuProvider(this)
binding.clipsRV.removeItemClickSupport()
_binding = null
}
}
Код: Выделить всё
class ItemClickSupport private constructor(private val recyclerView: RecyclerView) {
private var onItemClickListener: OnRecyclerViewItemClickListener? = null
private var onItemLongClickListener: OnRecyclerViewItemLongClickListener? = null
private var onItemCheckChangeListener: OnRecyclerViewItemCheckChangeListener? = null
private val attachListener: RecyclerView.OnChildAttachStateChangeListener = object : RecyclerView.OnChildAttachStateChangeListener {
override fun onChildViewAttachedToWindow(view: View) {
val holder = this@ItemClickSupport.recyclerView.getChildViewHolder(view)
val isClickable = (holder as? ItemClickSupportViewHolder)?.isClickable ?: true
val isLongClickable = (holder as? ItemClickSupportViewHolder)?.isLongClickable ?: true
if (onItemClickListener != null && isClickable) {
view.setOnClickListener(onClickListener)
}
if (onItemLongClickListener != null && isLongClickable) {
view.setOnLongClickListener(onLongClickListener)
}
val checkBox = view.findViewById(R.id.selectionCheckBox)
if (checkBox != null && onItemCheckChangeListener != null) {
checkBox.setOnCheckedChangeListener { _, isChecked ->
val position = holder.getAdapterPosition()
if (position != RecyclerView.NO_POSITION) {
onItemCheckChangeListener?.invoke(recyclerView, position, isChecked)
}
}
}
}
override fun onChildViewDetachedFromWindow(view: View) {
val checkBox = view.findViewById(R.id.selectionCheckBox)
checkBox?.setOnCheckedChangeListener(null)
}
}
init {
this.recyclerView.setTag(R.id.item_click_support, this)
this.recyclerView.addOnChildAttachStateChangeListener(attachListener)
}
companion object {
fun addTo(view: RecyclerView): ItemClickSupport {
var support: ItemClickSupport? = view.getTag(R.id.item_click_support) as? ItemClickSupport
if (support == null) {
support = ItemClickSupport(view)
}
return support
}
fun removeFrom(view: RecyclerView): ItemClickSupport? {
val support = view.getTag(R.id.item_click_support) as? ItemClickSupport
support?.detach(view)
return support
}
}
private val onClickListener = View.OnClickListener { v ->
val listener = onItemClickListener ?: return@OnClickListener
val holder = this.recyclerView.getChildViewHolder(v)
val position = holder.getAdapterPosition()
if (position != RecyclerView.NO_POSITION) {
listener.invoke(this.recyclerView, position, v)
}
}
private val onLongClickListener = View.OnLongClickListener { v ->
val listener = onItemLongClickListener ?: return@OnLongClickListener false
val holder = this.recyclerView.getChildViewHolder(v)
val position = holder.getAdapterPosition()
if (position != RecyclerView.NO_POSITION) {
return@OnLongClickListener listener.invoke(this.recyclerView, position, v)
}
return@OnLongClickListener false
}
private fun detach(view: RecyclerView) {
view.removeOnChildAttachStateChangeListener(attachListener)
view.setTag(R.id.item_click_support, null)
}
fun onItemClick(listener: OnRecyclerViewItemClickListener?): ItemClickSupport {
onItemClickListener = listener
return this
}
fun onItemLongClick(listener: OnRecyclerViewItemLongClickListener?): ItemClickSupport {
onItemLongClickListener = listener
return this
}
fun onItemCheckChange(listener: OnRecyclerViewItemCheckChangeListener?): ItemClickSupport {
onItemCheckChangeListener = listener
return this
}
}
interface ItemClickSupportViewHolder {
val isClickable: Boolean get() = true
val isLongClickable: Boolean get() = true
}
fun RecyclerView.addItemClickSupport(configuration: ItemClickSupport.() -> Unit = {}) = ItemClickSupport.addTo(this).apply(configuration)
fun RecyclerView.removeItemClickSupport() = ItemClickSupport.removeFrom(this)
fun RecyclerView.onItemClick(onClick: OnRecyclerViewItemClickListener) {
addItemClickSupport { onItemClick(onClick) }
}
fun RecyclerView.onItemLongClick(onLongClick: OnRecyclerViewItemLongClickListener) {
addItemClickSupport { onItemLongClick(onLongClick) }
}
fun RecyclerView.onItemCheckChange(onCheckChange: OnRecyclerViewItemCheckChangeListener) {
addItemClickSupport { onItemCheckChange(onCheckChange) }
}
Подробнее здесь: https://stackoverflow.com/questions/795 ... pter-in-an