You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView搜索内容不更新:编辑搜索框后仍显示旧文章结果

解决NewsApi搜索仅首次有效问题

问题描述

正在开发一款基于NewsApi的应用,设置了搜索EditText用于检索文章,但仅首次搜索有效。当在EditText中输入新关键词后,RecyclerView仍显示与之前关键词相关的文章。

原Fragment代码

class SearchNewsFragment : Fragment(R.layout.fragment_search_news) {

    lateinit var viewModel: NewsViewModel
    lateinit var newsAdapter: NewsAdapter

    private var recyclerView: RecyclerView? = null
    private var gridLayoutManager: GridLayoutManager? = null
    private var sourceAdapters: SourceAdapters? = null

    val TAG = "SearchNewsFragment"

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = (activity as NewsActivity).viewModel

        setupRecyclerView()
        initViews()

        // 跳转到点击的文章详情页
        newsAdapter.setOnItemClickListener {
            val bundle = Bundle().apply {
                putSerializable("article", it)
            }

            findNavController().navigate(
                R.id.action_searchNewsFragment_to_articleFragment,
                bundle
            )
        }

        var job: Job? = null
        etSearch.addTextChangedListener { editable ->
            job?.cancel()
            job = MainScope().launch {
                delay(Constants.SEARCH_NEWS_TIME_DELAY)
                editable?.let {
                    if (editable.toString().isNotEmpty()) {
                        viewModel.searchNews(editable.toString())
                    }
                    if (editable.toString().isEmpty()){
                        hideRecyclerView()
                        showSources()
                    }
                }
            }
        }

        viewModel.searchNews.observe(viewLifecycleOwner, Observer { response ->
            when (response) {
                is Resource.Success -> {
                    hideSources()
                    showRecyclerView()
                    hideProgressBar()
                    response.data?.let { newsResponse ->
                        newsAdapter.differ.submitList(newsResponse.articles.toList())
                        val totalPages = newsResponse.totalResults / Constants.QUERY_PAGE_SIZE + 2
                        isLastPage = viewModel.searchNewsPage == totalPages
                        if (isLastPage) {
                            rvSearchNews.setPadding(0, 0, 0, 0)
                        }
                    }
                }
                is Resource.Error -> {
                    hideProgressBar()
                    response.message?.let { message ->
                        Log.e(TAG, "发生错误: $message")
                    }
                }
                is Resource.Loading -> {
                    showProgressBar()
                }
            }
        })
    }

    private fun hideSources(){
        recycler_view_source.visibility = View.INVISIBLE
    }

    private fun showSources(){
        recycler_view_source.visibility = View.VISIBLE
    }
    private fun hideRecyclerView(){
        rvSearchNews.visibility = View.INVISIBLE
    }

    private fun showRecyclerView(){
        rvSearchNews.visibility = View.VISIBLE
    }

    private fun hideProgressBar() {
        paginationProgressBar.visibility = View.INVISIBLE
        isLoading = false
    }

    private fun showProgressBar() {
        paginationProgressBar.visibility = View.VISIBLE
        isLoading = true
    }

    var isLoading = false
    var isLastPage = false
    var isScrolling = false

    var scrollListener = object : RecyclerView.OnScrollListener() {
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)

            if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                isScrolling = true
            }
        }

        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)

            val layoutManager = recyclerView.layoutManager as LinearLayoutManager
            val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
            val visibleItemCount = layoutManager.childCount
            val totalItemCount = layoutManager.itemCount

            val isNotLoadingAndNotLastPage = !isLoading && !isLastPage
            val isAtLastItem = firstVisibleItemPosition + visibleItemCount >= totalItemCount
            val isNotAtBeginning = firstVisibleItemPosition >= 0
            val isTotalMoreThanVisible = totalItemCount >= Constants.QUERY_PAGE_SIZE
            val shouldPaginate =
                isNotLoadingAndNotLastPage && isAtLastItem && isNotAtBeginning && isTotalMoreThanVisible && isScrolling
            if (shouldPaginate) {
                viewModel.searchNews(etSearch.text.toString())
                isScrolling = false
            }
        }
    }

    private fun setupRecyclerView() {
        newsAdapter = NewsAdapter()
        rvSearchNews.apply {
            adapter = newsAdapter
            layoutManager = LinearLayoutManager(activity)
            addOnScrollListener(this@SearchNewsFragment.scrollListener)
        }
    }

    private fun initViews() {
        recyclerView = requireView().findViewById(R.id.recycler_view_source)
        gridLayoutManager =
            GridLayoutManager(
                requireContext().applicationContext,
                2,
                LinearLayoutManager.VERTICAL,
                false
            )
        recyclerView?.layoutManager = gridLayoutManager
        recyclerView?.setHasFixedSize(true)
        sourceAdapters = SourceAdapters(requireContext().applicationContext, listOf())
        viewModel.getSources("us")
        viewModel.sourcesPage.observe(viewLifecycleOwner){
            sourceAdapters?.submitList(it)
        }
        sourceAdapters?.onSourceClick = {
            val action = SearchNewsFragmentDirections.actionSearchNewsFragmentToBreakingNewsFragment(null, it)
            findNavController().navigate(action)
        }
        recyclerView?.adapter = sourceAdapters
    }
}

原NewsAdapter代码

class NewsAdapter : RecyclerView.Adapter<NewsAdapter.ArticleViewHolder>() {

    inner class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    private val differCallback = object : DiffUtil.ItemCallback<Article>() {
        override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
            return oldItem.url == newItem.url
        }

        override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
            return oldItem == newItem
        }
    }

    val differ = AsyncListDiffer(this, differCallback)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
        return ArticleViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.item_article_preview,
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
        val article = differ.currentList[position]
        holder.itemView.apply {
            Glide.with(this).load(article.urlToImage).into(article_imageView)
            source.text = article.source?.name
            headline.text = article.title
            setOnClickListener{
                onItemClickListener?.let {
                    it(article)
                }
            }
        }
    }

    override fun getItemCount(): Int {
        return differ.currentList.size
    }

    fun setOnItemClickListener(listen: (Article) -> Unit) {
        onItemClickListener = listen
    }

    private var onItemClickListener: ((Article) -> Unit)? = null
}

解决方案

核心问题

搜索新关键词时,ViewModel中的分页页码searchNewsPage未重置,导致请求的是旧关键词的后续页码数据;同时未及时清空旧列表,DiffUtil无法正确识别数据更新。

具体修复

1. 重置ViewModel分页参数

在ViewModel的searchNews方法中,每次接收新关键词时重置页码为1,同时记录当前搜索关键词,避免同一关键词重复重置:

private var currentSearchQuery: String? = null

fun searchNews(query: String) {
    if (currentSearchQuery != query) {
        // 新关键词,重置分页参数
        currentSearchQuery = query
        searchNewsPage = 1
    }
    // 发起搜索请求(根据searchNewsPage加载对应页码)
    viewModelScope.launch {
        val response = newsRepository.searchNews(query, searchNewsPage)
        _searchNews.postValue(response)
    }
}

2. 搜索新关键词时清空旧列表

在Fragment的搜索监听中,发起新搜索前先提交空列表,确保旧数据被清除,同时重置分页状态:

etSearch.addTextChangedListener { editable ->
    job?.cancel()
    job = MainScope().launch {
        delay(Constants.SEARCH_NEWS_TIME_DELAY)
        editable?.let {
            if (editable.toString().isNotEmpty()) {
                // 清空旧列表
                newsAdapter.differ.submitList(emptyList())
                // 重置分页状态
                isLoading = false
                isLastPage = false
                viewModel.searchNews(editable.toString())
            }
            if (editable.toString().isEmpty()){
                hideRecyclerView()
                showSources()
            }
        }
    }
}

3. 确保Article类正确实现equals方法

如果Article不是数据类,手动实现equals()和hashCode(),保证DiffUtil能正确判断内容是否变化;如果是数据类则无需额外处理:

// 示例数据类实现
data class Article(
    val url: String,
    val title: String,
    val source: Source?,
    val urlToImage: String?
)

4. 修正分页加载逻辑

确保ViewModel中分页加载时,只有同一关键词才追加数据,新关键词直接替换现有数据:

// 示例ViewModel中处理搜索结果的逻辑
fun handleSearchResponse(response: Resource<NewsResponse>) {
    if (response is Resource.Success) {
        response.data?.let { newsResponse ->
            val currentList = mutableListOf<Article>()
            if (searchNewsPage > 1) {
                // 同一关键词,追加下一页数据
                currentList.addAll(_searchNews.value?.data?.articles ?: emptyList())
            }
            currentList.addAll(newsResponse.articles)
            _searchNews.postValue(Resource.Success(NewsResponse(currentList, newsResponse.totalResults)))
        }
    } else {
        _searchNews.postValue(response)
    }
}

内容的提问来源于stack exchange,提问作者Fary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 21:15:40