过滤 RecyclerView 后位置变得混乱
Positions getting messed up after filtering RecyclerView
所以这是我的问题:
- 我正在从 api(成功)
中获取数据
- 我正在 recyclerview 中显示此数据(成功)
- 我正在使用搜索视图过滤 recyclerview(成功)
- 我在 recyclerview 中的项目上有一个点击侦听器,它正在使用项目[位置]信息开始一个新的 activity,并且在我过滤我的回收站视图之前这个工作完美。当我这样做时,过滤器,我的项目的位置变得混乱所以我给新 activity 的信息是错误的......我起初以为这是因为我使用了 List 而不是 ArrayList (可变的)但问题似乎也不是来自那里......
事实是我被困住了,我正在学习 android 和 kotlin,这就是我可以在没有帮助的情况下走多远:
(我展示的是房子)
HouseAdapter(recyclerview 适配器):
class HouseAdapter(private var recyclerViewInterface: RecyclerViewInterface) : RecyclerView.Adapter<HouseAdapter.HouseViewHolder>() {
inner class HouseViewHolder(val binding: ItemHouseBinding) : RecyclerView.ViewHolder(binding.root){
private val formatter : NumberFormat = DecimalFormat("$#,###")
fun bind(house: House) {
binding.tvPrice.text = formatter.format(house.price)
"${house.zip} ${house.city}".also { binding.tvAddress.text = it }
binding.tvBed.text = house.bedrooms.toString()
binding.tvBath.text = house.bathrooms.toString()
binding.tvLayers.text = house.size.toString()
val houseImageUrl : String = BASE_URL + house.image
Glide.with(binding.root.context).load(houseImageUrl).centerCrop().into(binding.ivHouse)
}
}
private val diffCallBack = object : DiffUtil.ItemCallback<House>(){
// Call to check whether two items represent the same item
override fun areItemsTheSame(oldItem: House, newItem: House): Boolean {
return oldItem.id == newItem.id
}
// Call to check whether two items have the same data
override fun areContentsTheSame(oldItem: House, newItem: House): Boolean {
return oldItem == newItem
}
}
private val differ = AsyncListDiffer(this, diffCallBack)
var houses: List<House>
get() = differ.currentList
set(value) {differ.submitList(value)}
override fun getItemCount(): Int {
return houses.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HouseViewHolder {
return HouseViewHolder(ItemHouseBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
))
}
override fun onBindViewHolder(holder: HouseViewHolder, position: Int) {
println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
holder.bind(houses[position])
holder.binding.vBody.setOnClickListener {
recyclerViewInterface.onItemClick(position)
}
}
fun setFilteredList(filteredList: List<House>){
this.houses = filteredList
}
}
HomeFragment(包含回收站视图):
class HomeFragment : Fragment(), RecyclerViewInterface {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var container : ViewGroup
private lateinit var houseAdapter: HouseAdapter
private lateinit var listHouses: ArrayList<House>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
if (container != null) {
this.container = container
}
setupRecyclerView()
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getHouses(API_KEY)
} catch (e: IOException) {
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException){
Log.e(TAG, "HttpException, unexpected response ")
return@launchWhenCreated
}
if (response.isSuccessful && response.body() != null){
listHouses = (response.body() as ArrayList<House>?)!!
houseAdapter.houses = listHouses
listHouses.sortWith(compareBy {it.price})
listHouses.forEach {println(it.price) }
}else{
Log.e(TAG, "Response not successful")
}
}
setupSearchView()
return binding.root
}
// Called on fragment launch - Add OnQueryTextListener to the SearchView
private fun setupSearchView(){
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener,
androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
filterList(newText)
return true
}
})
}
// Called when text from SearchView is changed - Create new filtered list or display empty information
private fun filterList(text: String) {
val filteredList = arrayListOf<House>()
listHouses.forEach { house : House ->
if (house.city.contains(text) || house.zip.contains(text)){
filteredList.add(house)
}
}
if (filteredList.isEmpty()){
binding.rvHouses.visibility = View.INVISIBLE
binding.ivSearchEmpty.visibility = View.VISIBLE
binding.tvSearchEmpty1.visibility = View.VISIBLE
binding.tvSearchEmpty2.visibility = View.VISIBLE
}else{
houseAdapter.setFilteredList(filteredList)
binding.rvHouses.visibility = View.VISIBLE
binding.ivSearchEmpty.visibility = View.INVISIBLE
binding.tvSearchEmpty1.visibility = View.INVISIBLE
binding.tvSearchEmpty2.visibility = View.INVISIBLE
}
}
// Called on fragment launch - Setup the RecyclerView with the HouseAdapter
private fun setupRecyclerView() = binding.rvHouses.apply {
houseAdapter = HouseAdapter(this@HomeFragment)
adapter = houseAdapter
layoutManager = LinearLayoutManager(context)
}
// Called when user click on House item - Pass properties from the chosen house to DetailActivity
override fun onItemClick(position: Int) {
val i = Intent(this.context, DetailActivity::class.java)
println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
i.putExtra("PRICE", listHouses[position].price)
i.putExtra("IMAGE_URL", listHouses[position].image)
i.putExtra("BEDS", listHouses[position].bedrooms)
i.putExtra("BATH", listHouses[position].bathrooms)
i.putExtra("SIZE", listHouses[position].size)
i.putExtra("LAT", listHouses[position].latitude)
i.putExtra("LONG", listHouses[position].longitude)
i.putExtra("DESC", listHouses[position].description)
startActivity(i)
}
}
(我使用 println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
跟踪项目在 recylcerview 的 bindviewholder 函数中的位置,当我单击一个项目以查看它是否对应时...答案是: “这取决于”有时是有时不是。我试图找到一些逻辑,但我不确定我的理论:我认为当我过滤 recyclerview 之前没有看到所有项目时,过滤列表不知道什么位置给那些“看不见”的物品。
如果您除了我的问题还有其他建议,我很乐意听取(正如我所说,我目前正在学习 kotlin)
有布局和适配器两种。你想使用适配器之一。因此,将 onBindViewHolder
函数更改为使用 getAbsoluteAdapterPosition
:
holder.binding.vBody.setOnClickListener {
recyclerViewInterface.onItemClick(holder.absoluteAdapterPosition) // not position
}
所以这是我的问题:
- 我正在从 api(成功) 中获取数据
- 我正在 recyclerview 中显示此数据(成功)
- 我正在使用搜索视图过滤 recyclerview(成功)
- 我在 recyclerview 中的项目上有一个点击侦听器,它正在使用项目[位置]信息开始一个新的 activity,并且在我过滤我的回收站视图之前这个工作完美。当我这样做时,过滤器,我的项目的位置变得混乱所以我给新 activity 的信息是错误的......我起初以为这是因为我使用了 List 而不是 ArrayList (可变的)但问题似乎也不是来自那里...... 事实是我被困住了,我正在学习 android 和 kotlin,这就是我可以在没有帮助的情况下走多远:
(我展示的是房子)
HouseAdapter(recyclerview 适配器):
class HouseAdapter(private var recyclerViewInterface: RecyclerViewInterface) : RecyclerView.Adapter<HouseAdapter.HouseViewHolder>() {
inner class HouseViewHolder(val binding: ItemHouseBinding) : RecyclerView.ViewHolder(binding.root){
private val formatter : NumberFormat = DecimalFormat("$#,###")
fun bind(house: House) {
binding.tvPrice.text = formatter.format(house.price)
"${house.zip} ${house.city}".also { binding.tvAddress.text = it }
binding.tvBed.text = house.bedrooms.toString()
binding.tvBath.text = house.bathrooms.toString()
binding.tvLayers.text = house.size.toString()
val houseImageUrl : String = BASE_URL + house.image
Glide.with(binding.root.context).load(houseImageUrl).centerCrop().into(binding.ivHouse)
}
}
private val diffCallBack = object : DiffUtil.ItemCallback<House>(){
// Call to check whether two items represent the same item
override fun areItemsTheSame(oldItem: House, newItem: House): Boolean {
return oldItem.id == newItem.id
}
// Call to check whether two items have the same data
override fun areContentsTheSame(oldItem: House, newItem: House): Boolean {
return oldItem == newItem
}
}
private val differ = AsyncListDiffer(this, diffCallBack)
var houses: List<House>
get() = differ.currentList
set(value) {differ.submitList(value)}
override fun getItemCount(): Int {
return houses.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HouseViewHolder {
return HouseViewHolder(ItemHouseBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
))
}
override fun onBindViewHolder(holder: HouseViewHolder, position: Int) {
println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
holder.bind(houses[position])
holder.binding.vBody.setOnClickListener {
recyclerViewInterface.onItemClick(position)
}
}
fun setFilteredList(filteredList: List<House>){
this.houses = filteredList
}
}
HomeFragment(包含回收站视图):
class HomeFragment : Fragment(), RecyclerViewInterface {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var container : ViewGroup
private lateinit var houseAdapter: HouseAdapter
private lateinit var listHouses: ArrayList<House>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
if (container != null) {
this.container = container
}
setupRecyclerView()
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getHouses(API_KEY)
} catch (e: IOException) {
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException){
Log.e(TAG, "HttpException, unexpected response ")
return@launchWhenCreated
}
if (response.isSuccessful && response.body() != null){
listHouses = (response.body() as ArrayList<House>?)!!
houseAdapter.houses = listHouses
listHouses.sortWith(compareBy {it.price})
listHouses.forEach {println(it.price) }
}else{
Log.e(TAG, "Response not successful")
}
}
setupSearchView()
return binding.root
}
// Called on fragment launch - Add OnQueryTextListener to the SearchView
private fun setupSearchView(){
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener,
androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
filterList(newText)
return true
}
})
}
// Called when text from SearchView is changed - Create new filtered list or display empty information
private fun filterList(text: String) {
val filteredList = arrayListOf<House>()
listHouses.forEach { house : House ->
if (house.city.contains(text) || house.zip.contains(text)){
filteredList.add(house)
}
}
if (filteredList.isEmpty()){
binding.rvHouses.visibility = View.INVISIBLE
binding.ivSearchEmpty.visibility = View.VISIBLE
binding.tvSearchEmpty1.visibility = View.VISIBLE
binding.tvSearchEmpty2.visibility = View.VISIBLE
}else{
houseAdapter.setFilteredList(filteredList)
binding.rvHouses.visibility = View.VISIBLE
binding.ivSearchEmpty.visibility = View.INVISIBLE
binding.tvSearchEmpty1.visibility = View.INVISIBLE
binding.tvSearchEmpty2.visibility = View.INVISIBLE
}
}
// Called on fragment launch - Setup the RecyclerView with the HouseAdapter
private fun setupRecyclerView() = binding.rvHouses.apply {
houseAdapter = HouseAdapter(this@HomeFragment)
adapter = houseAdapter
layoutManager = LinearLayoutManager(context)
}
// Called when user click on House item - Pass properties from the chosen house to DetailActivity
override fun onItemClick(position: Int) {
val i = Intent(this.context, DetailActivity::class.java)
println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
i.putExtra("PRICE", listHouses[position].price)
i.putExtra("IMAGE_URL", listHouses[position].image)
i.putExtra("BEDS", listHouses[position].bedrooms)
i.putExtra("BATH", listHouses[position].bathrooms)
i.putExtra("SIZE", listHouses[position].size)
i.putExtra("LAT", listHouses[position].latitude)
i.putExtra("LONG", listHouses[position].longitude)
i.putExtra("DESC", listHouses[position].description)
startActivity(i)
}
}
(我使用 println("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO : $position")
跟踪项目在 recylcerview 的 bindviewholder 函数中的位置,当我单击一个项目以查看它是否对应时...答案是: “这取决于”有时是有时不是。我试图找到一些逻辑,但我不确定我的理论:我认为当我过滤 recyclerview 之前没有看到所有项目时,过滤列表不知道什么位置给那些“看不见”的物品。
如果您除了我的问题还有其他建议,我很乐意听取(正如我所说,我目前正在学习 kotlin)
有布局和适配器两种。你想使用适配器之一。因此,将 onBindViewHolder
函数更改为使用 getAbsoluteAdapterPosition
:
holder.binding.vBody.setOnClickListener {
recyclerViewInterface.onItemClick(holder.absoluteAdapterPosition) // not position
}