【问题标题】:One checkbox triggering another checkbox in a different layout一个复选框触发不同布局中的另一个复选框
【发布时间】:2021-09-01 18:37:11
【问题描述】:

我目前正在编写一个显示电影列表的应用程序。我有许多显示包含电影的卡片视图的片段,每个卡片视图都有一个复选框。用户可以按卡片视图进入电影的详细信息页面,其中存在另一个复选框。

这两个复选框的目的是将电影添加到收藏夹标签中。

我的问题是,当用户检查卡片视图中的复选框时,如何检查详细信息页面内的复选框?

下面是相关代码。

感谢我能得到的所有帮助。

MoviesListFragment.kt


package com.example.moviesapp.ui.Fragments

import android.os.Bundle
import android.view.*
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.moviesapp.R
import com.example.moviesapp.databinding.FragmentMoviesListBinding
import com.example.moviesapp.network.MoviesFavorites
import com.example.moviesapp.network.MoviesResults
import com.example.moviesapp.ui.DaoViewModel
import com.example.moviesapp.ui.MovieApiStatus
import com.example.moviesapp.ui.MoviesListAdapter
import com.example.moviesapp.ui.MoviesListViewModel
import dagger.hilt.android.AndroidEntryPoint


@AndroidEntryPoint
class MoviesListFragment : Fragment(R.layout.fragment_movies_list), MoviesListAdapter.OnItemClickListener {


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

        return inflater.inflate(R.layout.fragment_movies_list, container, false)
    }



    private val daoViewModel by viewModels<DaoViewModel>()
    private val viewModel by viewModels<MoviesListViewModel>()
    private var _binding: FragmentMoviesListBinding? = null
    private val binding get() = _binding!!




    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        //View is inflated layout

        _binding = FragmentMoviesListBinding.bind(view)

        val adapter = MoviesListAdapter(this)


        binding.apply {
            recyclerView.layoutManager = LinearLayoutManager(requireContext())
            //Disable animations
            recyclerView.setHasFixedSize(true)
            recyclerView.adapter = adapter


        }

        //Observe the movies livedata
        //Use viewLifecycleOwner instead of this because the UI should stop being updated when the fragment view is destroyed
        viewModel.getTrending()

       viewModel.moviesTrending.observe(viewLifecycleOwner) {
           adapter.submitList(it)

       }

        viewModel.networkState.observe(viewLifecycleOwner, {
            binding.progressBar.isVisible = if (it==MovieApiStatus.LOADING) true else view.isGone
            binding.buttonRetry.isVisible = if(it==MovieApiStatus.ERROR) true else view.isGone
            binding.errorTextView.isVisible = if(it==MovieApiStatus.ERROR) true else view.isGone
            binding.recyclerView.isVisible =  if(it==MovieApiStatus.DONE) true else view.isGone
            binding.noResultsText.isVisible = false


        })









        //Display trending movies

        //loadstate is of type combined loadstates, which combines the loadstate of different scenarios(when we refresh dataset or when we append new data to it) into this one object
        //We can use it to check for these scenarios and make our views visible or unvisible according to it

       setHasOptionsMenu(true)
    }

    override fun  onItemClick(movie: MoviesResults.Movies) {
        val action = MoviesListFragmentDirections.actionMoviesListFragmentToMoviesDetailsFragment(movie)
        findNavController().navigate(action)
    }

    override fun onFavoriteClick(favorites: MoviesFavorites) {
        daoViewModel.addMovieToFavs(favorites)
    }

    override fun onDeleteClick(favorites: MoviesFavorites) {
        daoViewModel.deleteMovieFromFavs(favorites)
    }






    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)

        // Inflate the gallery menu
        inflater.inflate(R.menu.menu_gallery, menu)




    }





    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

}

MoviesListAdapter.kt


package com.example.moviesapp.ui

import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.moviesapp.R
import com.example.moviesapp.databinding.MovieLayoutBinding
import com.example.moviesapp.network.MoviesFavorites
import com.example.moviesapp.network.MoviesResults

val IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"

class MoviesListAdapter constructor(private val listener: OnItemClickListener) :
     ListAdapter<MoviesResults.Movies, MoviesListAdapter.MoviesListViewHolder>(DiffCallback) {

    private lateinit var fav: MoviesFavorites





    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoviesListViewHolder {
        val binding = MovieLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)


        return MoviesListViewHolder(binding)
    }


    override fun onBindViewHolder(holder: MoviesListViewHolder, position: Int) {
        val currentItem = getItem(position)

        holder.binding.favoritesCheckbox.isChecked = currentItem.isFavorite
        holder.binding.favoritesCheckbox.setOnCheckedChangeListener { _, isChecked ->
            currentItem.isFavorite

        }

        if(holder.binding.favoritesCheckbox.isChecked ) {
            currentItem.isFavorite = true

        }


        if (currentItem != null) {
            holder.bind(currentItem)

        }







    }

    inner class MoviesListViewHolder(val binding: MovieLayoutBinding) :
        RecyclerView.ViewHolder(binding.root) {




        init {
            binding.root.setOnClickListener {
                val position = absoluteAdapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val item = getItem(position)
                    listener.onItemClick(item)
                }
            }
        }

        init {

            binding.favoritesCheckbox.setOnClickListener{

                if(binding.favoritesCheckbox.isChecked) {

                    val position = absoluteAdapterPosition
                    if (position != RecyclerView.NO_POSITION) {
                        val item = getItem(position)
                        item.isFavorite = true
                        fav = MoviesFavorites(item.title, item.id, item.release_date, item.overview, item.vote_average, item.poster_path, item.original_language, item.isFavorite)
                        listener.onFavoriteClick(fav)
                        listener.onCheckboxClick(binding.favoritesCheckbox.isChecked)

                    }

                    showToast("${fav.title} is added to your favorites")





                }
                else {
                    val position = absoluteAdapterPosition
                    if (position != RecyclerView.NO_POSITION) {
                        val item = getItem(position)
                        item.isFavorite = false
                         fav = MoviesFavorites(item.title, item.id, item.release_date, item.overview, item.vote_average, item.poster_path, item.original_language, item.isFavorite)
                        listener.onDeleteClick(fav)
                        listener.onCheckboxClick(binding.favoritesCheckbox.isChecked)
                    }


                    showToast("${fav.title} is removed from your favorites")
                }

            }



        }



        fun bind(movie: MoviesResults.Movies) {
            binding.apply {
                movieTitle.text = movie.title
                movieRating.text = movie.vote_average
                movieYear.text = movie.release_date
                Glide.with(itemView)
                    .load(IMAGE_BASE_URL + movie.poster_path)
                    .centerCrop()
                    .error(R.drawable.ic_baseline_error_outline_24)
                    .into(movieImage)
               val item = getItem(absoluteAdapterPosition)
                favoritesCheckbox.isChecked = item.isFavorite






            }

        }


        private fun showToast(string: String) {
            Toast.makeText(itemView.context, string, Toast.LENGTH_SHORT).show()

        }


    }


    interface OnItemClickListener {
        fun onItemClick(movie: MoviesResults.Movies)
        fun onFavoriteClick(favorites: MoviesFavorites)
        fun onDeleteClick(favorites: MoviesFavorites)
        fun onCheckboxClick(fav: Boolean)
    }







    companion object DiffCallback : DiffUtil.ItemCallback<MoviesResults.Movies>() {
        override fun areItemsTheSame(
            oldItem: MoviesResults.Movies,
            newItem: MoviesResults.Movies
        ): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(
            oldItem: MoviesResults.Movies,
            newItem: MoviesResults.Movies
        ): Boolean {
            return oldItem == newItem
        }
    }


}







MoviesDetailsFragment.kt


package com.example.moviesapp.ui.Fragments

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.navArgs
import com.bumptech.glide.Glide
import com.example.moviesapp.R
import com.example.moviesapp.databinding.FragmentMoviesDetailsBinding
import com.example.moviesapp.network.MoviesFavorites
import com.example.moviesapp.network.MoviesResults
import com.example.moviesapp.ui.DaoViewModel
import com.example.moviesapp.ui.IMAGE_BASE_URL
import com.example.moviesapp.ui.SharedViewModel
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MoviesDetailsFragment() : Fragment(R.layout.fragment_movies_details) {


    //We can get the movies from the args property
    private val args by navArgs<MoviesDetailsFragmentArgs>()
    private val daoViewModel by viewModels<DaoViewModel>()
    private val sharedViewModel by viewModels<SharedViewModel>()
    private fun showToast(string: String) {
        Toast.makeText(view?.context, string, Toast.LENGTH_SHORT).show()

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val binding = FragmentMoviesDetailsBinding.bind(view)


        sharedViewModel.checkBox.observe(viewLifecycleOwner) {
            binding.favCheckbox.isChecked = it

        }



        binding.apply {
            val movie: MoviesResults.Movies = args.movie
            val fav = MoviesFavorites(
                movie.title,
                movie.id,
                movie.release_date,
                movie.overview,
                movie.vote_average,
                movie.poster_path,
                movie.original_language,
                movie.isFavorite,
            )
            //When you are in fragment/activity, pass it to a glide.with because view is less efficient
            Glide.with(this@MoviesDetailsFragment)
                .load(IMAGE_BASE_URL + movie.poster_path)
                //Have the textview visible only when image is visible
                .error(R.drawable.ic_baseline_error_outline_24)
                .fitCenter()
                .into(coverPhoto)

            title.text = movie.title
            releaseDate.text = movie.release_date
            language.text = movie.original_language
            rating.text = movie.vote_average
            plot.text = movie.overview





            favCheckbox.setOnClickListener {


                if (favCheckbox.isChecked) {


                    fav.isFavorite = true
                    daoViewModel.addMovieToFavs(fav)
                    showToast("${fav.title} is added to your favorites")

                } else {


                    fav.isFavorite = false
                    daoViewModel.deleteMovieFromFavs(fav)
                    showToast("${fav.title} is removed from your favorites")


                }


            }
        }


    }


}


SharedViewModel.kt

package com.example.moviesapp.ui

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider



class SharedViewModel: ViewModel() {


    val checkBox = MutableLiveData<Boolean>()


    fun sendValue(favorite: Boolean) {
        checkBox.value = favorite

    }

    class SharedViewModelFactor(

    ) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(SharedViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return SharedViewModel() as T
            }
            throw IllegalArgumentException("Unknown ViewModel class")

        }


    }








}


【问题讨论】:

    标签: android kotlin android-fragments android-recyclerview


    【解决方案1】:

    您可以使用 2 种方法来做到这一点:
    1 ) 您可以使用LocalBroadcast 通知一个片段/活动在另一个片段/活动中发生变化。
    注意LocalBroadcast 现已弃用。或者,您可以使用eventbus 在片段之间进行通信

    1. 创建本地广播

       private BroadcastReceiver onNotice= new BroadcastReceiver() {
      
         @Override
         public void onReceive(Context context, Intent intent) {
           // intent can contain anydata
           Log.d(TAG,"onReceive called");
         }
       };
      
    2. 在片段的 onResume 中注册您的接收器,例如:

      public void onResume() {
           super.onResume();
           IntentFilter iff= new IntentFilter(MyIntentService.ACTION);
           LocalBroadcastManager.getInstance(this).registerReceiver(onNotice, iff);
       }
      
    3. 在 onPause 中取消注册接收器:

       public void onPause() {    
         super.onPause();    
         LocalBroadcastManager.getInstance(this).unregisterReceiver(onNotice);    
       }
      

    更多信息可以参考:
    https://blog.mindorks.com/using-localbroadcastmanager-in-android

    2 ) 可以使用LiveData观察一个fragment在另一个fragment中的数据变化

    1. 创建共享视图模型

      public class SharedViewModel extends ViewModel {
      
      private MutableLiveData<String> name;
      
      public void setNameData(String nameData) {
          name.setValue(nameData);
      }
      
      public MutableLiveData<String> getNameData() {
          if (name == null) {
              name = new MutableLiveData<>();
          }
      
          return name;
      }
      }
      
    2. 片段一

       private SharedViewModel sharedViewModel;
      
       public FragmentOne() {
      
       }
      
       @Override
       public void onCreate(@Nullable Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
      
           sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
           submitButton.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View view) {
      
                  sharedViewModel.setNameData(submitText.getText().toString());
                 }
            });
      
       }
      
    3. 片段二

       private SharedViewModel sharedViewModel;
      
       public FragmentTwo() {
      
       }
      
       @Override
       public void onCreate(@Nullable Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
      
           sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
      
           sharedViewModel.getNameData().observe(this, nameObserver);
       }
      
       Observer<String> nameObserver = new Observer<String>() {
          @Override
          public void onChanged(String name) {
             receivedText.setText(name);
          }
       };
      

    关于viewmodel的更多细节可以参考:

    https://nabeelj.medium.com/android-how-to-share-data-between-fragments-using-viewmodel-and-livedata-android-mvvm-9fc463af5152

    https://developer.android.com/guide/fragments/communicate#fragments

    【讨论】:

    • 我已经有两个视图模型,一个用于 api,一个用于房间数据库。我应该为这两个片段添加第三个吗?
    • 是的,您可以添加第三个视图模型,只要您小心管理所有三个的观察者。
    • 但问题是我无法访问第一个复选框,因为该片段只有一个布局的 recyclerview 并且该布局有复选框
    • 使用从回收器视图适配器到片段的接口提供回调希望这会有所帮助:codexpedia.com/android/…
    • 我试过了,但它不起作用。我用新代码更新了帖子,你能看一下吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 2020-05-15
    • 2018-04-15
    • 2021-12-23
    • 1970-01-01
    • 2018-12-19
    相关资源
    最近更新 更多