我有一个 RecyclerView 活动和一个 ViewModel 类。该活动调用ViewModel中的方法,该方法使用Web服务,并且还观察LiveData字段。每次方法从Web服务返回一个项目时,它都会将其设置为LiveData,因此会通知Activity中的观察者,因此所有项目都会进入RecyclerView。保证这个流程正常工作,因为我可以在日志和UI中看到。
当我在使用Web服务的方法中使用 Thread.slepp(500)延迟时出现问题。
不是在RecyclerView中放置一个项目,而是等待500millis再放另一个,它等待500 millis * numberOfItems,然后将它们全部拉到一起。
我可以确保ViewModel和LiveData设置没有问题,因为日志按预期的方式工作,它打印所创建项目的标题,等待500毫秒,打印下一个。所以问题在于适配器以及只有在方法调用完成后才会通知它。
我的问题是如何在每次调用观察者时通知适配器?
这是我对三个类的实现:
RecipeList
class RecipeList : LifecycleActivity() {
var recipeList: MutableList<Recipe> = mutableListOf()
var adapter: RecipeAdapter? = null
var viewModel: RecipeViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recipe_list)
val ingredients = intent.getStringExtra("ingredients")
val term = intent.getStringExtra("term")
viewModel = ViewModelProviders.of(this).get(RecipeViewModel::class.java)
val url = "http://www.recipepuppy.com/api/?i=${ingredients}onions,garlic&q=${term}"
val layoutManager = LinearLayoutManager(this)
adapter = RecipeAdapter(this, recipeList)
rec_recycler_id.layoutManager = layoutManager
rec_recycler_id.adapter = adapter
subscribe()
viewModel?.getRecipe(url)
}
fun subscribe() {
val observer = Observer<Recipe> { recipe ->
if (recipe != null) {
Log.d("mike", "subscribe ${recipe?.title} ")
recipeList.add(recipe)
adapter?.notifyDataSetChanged()
}
}
viewModel?.mRecipe?.observe(this, observer)
}
}
RecipeViewModel
class RecipeViewModel(application: Application): AndroidViewModel(application) {
var recipes: MutableLiveData<MutableList<Recipe>>? = MutableLiveData<MutableList<Recipe>>()
var mRecipe: MutableLiveData<Recipe> = MutableLiveData()
fun getRecipe(url:String){
val requestQueue = Volley.newRequestQueue(this.getApplication())
val recipeRequest = JsonObjectRequest(Request.Method.GET,url,
Response.Listener {
response: JSONObject ->
try {
val results = response.getJSONArray("results")
for( i in 0..results.length()-1){
var recipeObj = results.getJSONObject(i)
var title = recipeObj.getString("title")
var link = recipeObj.getString("href")
var thumbnail = recipeObj.getString("thumbnail")
var ingredients = recipeObj.getString("ingredients")
var recipe = Recipe(title,ingredients,thumbnail,link)
mRecipe.value = recipe
Log.d("mike",title)
Thread.sleep(200)
}
}catch (e: JSONException){
e.printStackTrace()
}
},
Response.ErrorListener {
error: VolleyError? ->
try{
Log.d("error",error.toString())
}catch (e: JSONException){
e.printStackTrace()
}
})
requestQueue?.add(recipeRequest)
}
和 RecipeAdapter
class RecipeAdapter(val context: Context, var recipes: MutableList<Recipe>) : RecyclerView.Adapter<RecipeAdapter.ViewHolder>() {
override fun getItemCount(): Int = recipes.size
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.recipe_rec_row, null)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
holder?.bindViews(recipes[position])
}
inner class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
fun bindViews(recipe: Recipe) {
itemView.textView7.text = recipe.title
itemView.textView9.text = recipe.ingredients
itemView.button6.setOnClickListener() {
if(!recipe.link.trim().isEmpty())
context.startActivity<ShowLinkAct>("url" to recipe.link)
else
context.toast("No link available")
}
if (!recipe.thumbnail.isEmpty()) {
Picasso.with(context)
.load(recipe.thumbnail)
.placeholder(android.R.drawable.ic_menu_report_image)
.error(android.R.drawable.ic_menu_report_image)
.into(itemView.imageView)
} else {
Picasso.with(context).load(android.R.drawable.ic_menu_report_image).into(itemView.imageView)
}
}
}
}
我期待着您的建议,谢谢您提前
答案 0 :(得分:1)
我建议您将适配器的数据存储在适配器中。如果您使用AAC,还应检查GithubBrowser的样本。这是一个小的(未经测试的)样本。
警告:您不应在RecyclerView中使用上下文操作,因为您可能会泄漏。
BaseAdapter(所有适配器扩展此适配器,具有DiffUtil)
abstract class DataBoundListAdapter<T, V : ViewDataBinding> : RecyclerView.Adapter<DataBoundViewHolder<V>>() {
val log = AnkoLogger(javaClass.simpleName)
private var items: List<T>? = null
private var dataVersion = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataBoundViewHolder<V> {
val binding = createBinding(parent)
return DataBoundViewHolder(binding)
}
protected abstract fun createBinding(parent: ViewGroup): V
override fun onBindViewHolder(holder: DataBoundViewHolder<V>, position: Int) {
bind(holder.binding, items!![position])
holder.binding.executePendingBindings()
}
@SuppressLint("StaticFieldLeak")
@MainThread
fun replace(update: List<T>?) {
dataVersion++
if (items == null) {
if (update == null) {
return
}
items = update
notifyDataSetChanged()
} else if (update == null) {
val oldSize = items!!.size
items = null
notifyItemRangeRemoved(0, oldSize)
} else {
val startVersion = dataVersion
val oldItems = items
object : AsyncTask<Void, Void, DiffUtil.DiffResult>() {
override fun doInBackground(vararg voids: Void): DiffUtil.DiffResult {
return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldItems!!.size
}
override fun getNewListSize(): Int {
return update.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems!![oldItemPosition]
val newItem = update[newItemPosition]
return this@DataBoundListAdapter.areItemsTheSame(oldItem, newItem)
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems!![oldItemPosition]
val newItem = update[newItemPosition]
return this@DataBoundListAdapter.areContentsTheSame(oldItem, newItem)
}
})
}
override fun onPostExecute(diffResult: DiffUtil.DiffResult) {
if (startVersion != dataVersion) {
// ignore update
return
}
items = update
diffResult.dispatchUpdatesTo(this@DataBoundListAdapter)
}
}.execute()
}
}
protected abstract fun bind(binding: V, item: T)
protected abstract fun areItemsTheSame(oldItem: T, newItem: T): Boolean
protected abstract fun areContentsTheSame(oldItem: T, newItem: T): Boolean
override fun getItemCount(): Int {
return if (items == null) 0 else items!!.size
}
}
这是一个示例适配器。如果使用AAC,可能需要使用数据绑定。我推荐你的情况!因为您可能会泄漏,所以请注意上下文操作不应该在RecyclerView中。
class RecipeAdapter(private val dataBindingComponent: DataBindingComponent,
private val yourVm: ViewModel, private val context: Context) : DataBoundListAdapter<Recipe, RecipeRecRowBinding>() {
override fun createBinding(parent: ViewGroup): RecipeRecRowBinding {
val binding = DataBindingUtil.inflate<RecipeRecRowBinding>(LayoutInflater.from(parent.context), R.layout.recipe_rec_row, parent, false, dataBindingComponent)
return binding
}
override fun bind(binding: RecipeRecRowBinding, recipe: Recipe) {
binding.model = recipe
binding.viewModel = yourVm
binding.itemView.textView7.text = recipe.title
binding.itemView.textView9.text = recipe.ingredients
binding.itemView.button6.setOnClickListener() {
if(!recipe.link.trim().isEmpty())
//ohoh, you shouldnt call something on your activity within your adapter
context.startActivity<ShowLinkAct>("url" to recipe.link)
else
//ohoh, you shouldnt call something on your activity within your adapter
context.toast("No link available")
}
if (!recipe.thumbnail.isEmpty()) {
Picasso.with(context)
.load(recipe.thumbnail)
.placeholder(android.R.drawable.ic_menu_report_image)
.error(android.R.drawable.ic_menu_report_image)
.into(itemView.imageView)
} else {
Picasso.with(context).load(android.R.drawable.ic_menu_report_image).into(itemView.imageView)
}
}
override fun areItemsTheSame(oldItem: Recipe, newItem: Recipe) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Recipe, newItem: Recipe) = oldItem.equals(newItem)
}
最后,您的订户将数据推送到适配器并注意更改(DiffUtil)
fun subscribe() {
val observer = Observer<Recipe> {
if (it!= null) {
// it cant be null since you validate it here
Log.d("mike", "subscribe ${it.title} ")
adapter.replace(it)
}
}
viewModel?.mRecipe?.observe(this, observer)
}