如何在不跳帧的情况下更新 UI
How can I update the UI without skipping frames
所以我正在使用协程在 Kotlin 中开发和 android 应用程序,无论我做了什么更改,我仍然不断收到消息:
I/Choreographer: Skipped 59 frames! The application may be doing too much work on its main thread.
我怎样才能摆脱它。我的意思是我只显示九张照片...下面是我的代码
型号:
data class Food (
val id: String,
val name: String,
val price: String,
@Json(name = "img_url") val imgSrcUrl: String,
val type: String,
val description: String,
val average_rating: String,
val number_of_raters: String,
val special_price: String
)
data class FoodCategory(
val id: String,
val title: String,
val foods: List<Food>
)
ViewModel:
enum class NetworkStatus {LOADING, DONE, FAILED}
enum class FontFamily (@FontRes val fontRes: Int) {
POPPINS_BOLD(R.font.poppins_bold),
POPPINS(R.font.poppins)
}
class FoodOverviewViewModel(private val foodRepository: FoodRepository): ViewModel() {
private lateinit var foodProducts: List<Food>
//This is the data that is gonna be exposed to the viewmodel
//It will be submitted to a ListAdapter
private val _foodCategory = MutableLiveData<List<FoodCategory>>()
val foodCategory: LiveData<List<FoodCategory>>
get() = _foodCategory
//Used to display a progress bar for network status
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
init {
getOverviewProducts()
}
private fun getOverviewProducts() {
viewModelScope.launch(Dispatchers.Default) {
_status.postValue(NetworkStatus.LOADING)
try {
getUpdatedFood()
Log.i("getOverviewProducts","I am running on tread: $coroutineContext")
_status.postValue(NetworkStatus.DONE)
}catch (e: Exception) {
_status.postValue(NetworkStatus.FAILED)
}
}
}
private suspend fun getUpdatedFood() {
//withContext(Dispatchers.Default) {
val limiter = 6 //Number of items I want to get from the server
val foodCategory = arrayListOf<FoodCategory>()
Log.i("getUpdatedFood","I am running on tread: $coroutineContext")
val getRecommended = foodRepository.getRecommendedFood(limiter.toString())
foodCategory += FoodCategory(id = 0.toString(), title = "Recommended for you", foods = getRecommended)
val getSpecials = foodRepository.getSpecials(limiter.toString())
foodCategory += FoodCategory(id = 1.toString(), title = "Specials", foods = getSpecials)
_foodCategory.postValue(foodCategory)
//}
}
}
存储库:
class FoodRepository {
suspend fun getRecommendedFood(limiter: String) = withContext(Dispatchers.IO) {
Log.i("Resp-getRecommended","I am running on tread: $coroutineContext")
return@withContext ProductApi.retrofitService.getRecommended(limiter)
}
suspend fun getSpecials(limiter: String) = withContext(Dispatchers.IO) {
Log.i("Resp-getSpecials","I am running on tread: $coroutineContext")
return@withContext ProductApi.retrofitService.getSpecials(limiter)
}
}
绑定适配器:
//Load image using Glide (in Food item recycleview)
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView , imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("http").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(
RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
//set raters count (in Food item recycleview)
@BindingAdapter("ratersCount")
fun bindText(txtView: TextView, number_of_raters: String?) {
number_of_raters?.let {
val ratersCount = "(${number_of_raters})"
txtView.text = ratersCount
}
}
//update the progressbar visibilty (in outer-parent recycleview)
@BindingAdapter("updateStatus")
fun ProgressBar.updateStatus(status: NetworkStatus?) {
visibility = when (status) {
NetworkStatus.LOADING -> View.VISIBLE
NetworkStatus.DONE -> View.GONE
else -> View.GONE
}
}
//Hide or view an imageview based in the network Status. When network Error, an error image
//will show (in outer-parent recycleview)
@BindingAdapter("setNoInternet")
fun ImageView.setNoInternet(status: NetworkStatus?) {
when(status) {
NetworkStatus.LOADING -> {
visibility = View.GONE
}
NetworkStatus.DONE -> {
visibility = View.GONE
}
NetworkStatus.FAILED -> {
visibility = View.VISIBLE
setImageResource(R.drawable.ic_connection_error)
}
}
}
//Submit the list of FoodCatergory item to the outer-parent recycleview
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<FoodCategory>?) {
(recyclerView.adapter as FoodCategoryAdapter).submitList(data)
}
//Submit list the the Food item recyclew view (child recycleView)
@BindingAdapter("setProducts")
fun RecyclerView.setProducts(foods: List<Food>?) {
if (foods != null) {
val foodAdapter = FoodItemAdapter()
foodAdapter.submitList(foods)
adapter = foodAdapter
}
}
我有一个 Food Item 的回收视图和一个 FoodCategory 的回收视图池。如果我注释掉
_foodCategory.postValue(foodCategory)
在 ViewModel: getUpdatedFood() 中我没有收到消息。但是,当我将列表提交到外部回收视图(持有视图池的那个)时,我得到了这个答案。请帮忙。我坚持了一段时间,试图摆脱这条消息。
谢谢..
已更新
下面是适配器及其视图持有者
FoodItem 布局
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="foodItem"
type="com.example.e_commerceapp.models.Food"/>
<variable
name="font"
type="com.example.e_commerceapp.products.overview.FontFamily"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/child_item_main_layout"
android:background="@drawable/search_background"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
android:layout_width="150dp"
android:layout_height="250dp">
<ImageView
android:layout_width="120dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:id="@+id/burger_image"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_height="160dp"
/>
<!-- app:imageUrl="@{foodItem.imgSrcUrl}"-->
<TextView
android:layout_width="match_parent"
android:layout_height="34dp"
android:layout_marginStart="5dp"
android:id="@+id/burger_title"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="5dp"
android:singleLine="true"
android:textColor="#B4000000"
app:layout_constraintTop_toBottomOf="@id/burger_image"
android:text="@{foodItem.name}"
android:textSize="12sp"
android:fontFamily="@font/poppins"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="35dp"
app:layout_constraintTop_toBottomOf="@id/burger_title"
android:layout_marginEnd="5dp"
android:gravity="center"
android:id="@+id/burger_price"
android:layout_marginStart="5dp"
app:layout_constraintStart_toEndOf="@id/special_price"
android:textColor="#D0000000"/>
<!-- app:price="@{foodItem.price}"-->
<!-- app:specialPrice="@{foodItem.special_price}"-->
<!-- app:fontRes="@{foodItem.special ? font.POPPINS : font.POPPINS_BOLD}"-->
<TextView
android:layout_width="wrap_content"
android:layout_height="35dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/burger_title"
android:layout_marginEnd="5dp"
android:gravity="center_vertical"
android:layout_marginStart="5dp"
android:id="@+id/special_price"
android:textColor="#D0000000"
android:visibility="gone"
android:textStyle="normal"
android:fontFamily="@font/poppins_bold"/>
<!-- app:setSpecialPrice="@{foodItem.special_price}"-->
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
app:layout_constraintTop_toBottomOf="@id/burger_price"
android:src="@drawable/ic_baseline_star_24"
android:visibility="@{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:id="@+id/rating_star"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="15dp"
android:layout_marginStart="5dp"
android:gravity="center"
android:textSize="12sp"
android:visibility="@{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:id="@+id/rating_count"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/burger_price"
android:text="@{foodItem.average_rating}"
android:layout_marginBottom="10dp"
app:layout_constraintStart_toEndOf="@id/rating_star"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="15dp"
android:id="@+id/number_of_raters"
android:textSize="12sp"
android:visibility="@{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:layout_marginStart="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/rating_count"
app:ratersCount="@{foodItem.number_of_raters}"
android:layout_marginBottom="10dp"
app:layout_constraintTop_toBottomOf="@id/burger_price"/>
<ImageView android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FoodItem 适配器
class FoodItemAdapter: ListAdapter<Food ,
FoodItemAdapter.ItemFoodViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup , viewType: Int): ItemFoodViewHolder {
return ItemFoodViewHolder(
FoodItemBinding.inflate(LayoutInflater.from(parent.context),
parent, false))
}
override fun onBindViewHolder(holder: ItemFoodViewHolder , position: Int) {
val currentFood = getItem(position)
holder.bind(currentFood)
}
class ItemFoodViewHolder(private var binding: FoodItemBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(food: Food) {
binding.foodItem = food
binding.executePendingBindings()
}
}
object DiffCallback: DiffUtil.ItemCallback<Food>() {
override fun areItemsTheSame(oldItem: Food , newItem: Food): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Food , newItem: Food): Boolean {
return oldItem.id == newItem.id
}
}
}
食品类别布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="foodCategory"
type="com.example.e_commerceapp.models.FoodCategory"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#fff"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:id="@+id/category_title"
android:layout_marginTop="16dp"
android:text="@{foodCategory.title}"
android:textColor="#2B2A2A"
android:fontFamily="@font/poppins_bold"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/nestedRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:setProducts="@{foodCategory.foods}"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/category_title"
tools:itemCount="4"
tools:listitem="@layout/food_item"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FoodCategory 适配器
class FoodCategoryAdapter: ListAdapter<FoodCategory,
FoodCategoryAdapter.CategoryFoodViewHolder>(Companion) {
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup , viewType: Int): CategoryFoodViewHolder {
return CategoryFoodViewHolder(FoodCategoryBinding.inflate(LayoutInflater.from(parent.context),
parent, false))
}
override fun onBindViewHolder(holder: CategoryFoodViewHolder , position: Int) {
val currentFoodCategory = getItem(position)
holder.bind(currentFoodCategory)
}
inner class CategoryFoodViewHolder(private var binding: FoodCategoryBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(currentFoodCategory: FoodCategory?) {
binding.foodCategory = currentFoodCategory
binding.nestedRecyclerView.setRecycledViewPool(viewPool)
binding.executePendingBindings()
}
}
companion object: DiffUtil.ItemCallback<FoodCategory>() {
override fun areItemsTheSame(oldItem: FoodCategory , newItem: FoodCategory): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: FoodCategory, newItem: FoodCategory): Boolean {
return oldItem.id == newItem.id
}
}
}
父recycleView
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".products.overview.FoodOverviewFragment">
<data>
<variable
name="foodOverview"
type="com.example.e_commerceapp.products.overview.FoodOverviewViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="@color/grey"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:id="@+id/relative_layout"
android:layout_height="105dp"
android:elevation="8dp"
android:layout_marginBottom="5dp"
android:background="#fff"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="200dp"
android:layout_marginTop="10dp"
android:layout_height="35dp"
android:id="@+id/logo_and_name"
android:src="@drawable/compony_logo_and_name"
android:layout_alignParentStart="true"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginTop="10dp"
android:id="@+id/notifications"
android:src="@drawable/ic_baseline_notifications_24"
android:layout_alignParentEnd="true"
android:paddingEnd="20dp"
android:paddingStart="20dp"/>
<TextView
android:layout_width="match_parent"
android:id="@+id/search"
android:layout_marginTop="10dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@drawable/search_background"
android:layout_below="@id/logo_and_name"
android:gravity="center_vertical"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:text="@string/search_text"
tools:ignore="RtlSymmetry"
app:drawableEndCompat="@drawable/ic_baseline_search_24"/>
</RelativeLayout>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:updateStatus="@{foodOverview.status}"
app:layout_constraintTop_toBottomOf="@id/relative_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/progressbar"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/relative_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/noInternetImage"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:id="@+id/foodCategory"
android:clipToPadding="false"
tools:itemCount="4"
tools:listitem="@layout/food_category"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_height="0dp"
app:listData="@{foodOverview.foodCategory}"
app:layout_constraintTop_toBottomOf="@id/relative_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
跳帧可能与您 post 编写的代码无关:对我来说,这听起来像是 RecyclerViews / Adapters 配置错误。不过,您需要 post 使其代码更加清晰。
然而,即使您 post 编辑的可能不是罪魁祸首,您仍然可以优化您拥有的协程代码:
class FoodOverviewViewModel(private val foodRepository: FoodRepository): ViewModel() {
private lateinit var foodProducts: List<Food>
private val _foodCategory = MutableLiveData<List<FoodCategory>>()
val foodCategory: LiveData<List<FoodCategory>>
get() = _foodCategory
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
init {
getOverviewProducts()
}
private fun getOverviewProducts() {
viewModelScope.launch { // <------- Don't apply a custom scope here
_status.value = NetworkStatus.LOADING // <--- Don't call "postValue" here
try {
val food = getUpdatedFood() // <------ This is already using a background dispatcher
_foodCategory.value = food // <------- Emit this value here
_status.value = NetworkStatus.DONE
} catch (e: Exception) {
_status.value = NetworkStatus.FAILED
}
}
}
private suspend fun getUpdatedFood(): List<FoodCategory> { // <---- Return a value here
val limiter = 6 //Number of items I want to get from the server
val foodCategory = arrayListOf<FoodCategory>()
Log.i("getUpdatedFood","I am running on tread: $coroutineContext")
val getRecommended = foodRepository.getRecommendedFood(limiter.toString())
foodCategory += FoodCategory(id = 0.toString(), title = "Recommended for you", foods = getRecommended)
val getSpecials = foodRepository.getSpecials(limiter.toString())
foodCategory += FoodCategory(id = 1.toString(), title = "Specials", foods = getSpecials)
return foodCategories
}
}
这里的关键思想:
- 使用
viewModelScope.launch { ... }
而不是应用范围。你想要那里的所有东西 而不是 主线程上 运行 的协程。例如,参见 https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe
- Return 来自
getUpdatedFood
的值,因此您可以在 viewModelScope
的主线程上更新 _foodCategory
值
所以我正在使用协程在 Kotlin 中开发和 android 应用程序,无论我做了什么更改,我仍然不断收到消息:
I/Choreographer: Skipped 59 frames! The application may be doing too much work on its main thread.
我怎样才能摆脱它。我的意思是我只显示九张照片...下面是我的代码
型号:
data class Food (
val id: String,
val name: String,
val price: String,
@Json(name = "img_url") val imgSrcUrl: String,
val type: String,
val description: String,
val average_rating: String,
val number_of_raters: String,
val special_price: String
)
data class FoodCategory(
val id: String,
val title: String,
val foods: List<Food>
)
ViewModel:
enum class NetworkStatus {LOADING, DONE, FAILED}
enum class FontFamily (@FontRes val fontRes: Int) {
POPPINS_BOLD(R.font.poppins_bold),
POPPINS(R.font.poppins)
}
class FoodOverviewViewModel(private val foodRepository: FoodRepository): ViewModel() {
private lateinit var foodProducts: List<Food>
//This is the data that is gonna be exposed to the viewmodel
//It will be submitted to a ListAdapter
private val _foodCategory = MutableLiveData<List<FoodCategory>>()
val foodCategory: LiveData<List<FoodCategory>>
get() = _foodCategory
//Used to display a progress bar for network status
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
init {
getOverviewProducts()
}
private fun getOverviewProducts() {
viewModelScope.launch(Dispatchers.Default) {
_status.postValue(NetworkStatus.LOADING)
try {
getUpdatedFood()
Log.i("getOverviewProducts","I am running on tread: $coroutineContext")
_status.postValue(NetworkStatus.DONE)
}catch (e: Exception) {
_status.postValue(NetworkStatus.FAILED)
}
}
}
private suspend fun getUpdatedFood() {
//withContext(Dispatchers.Default) {
val limiter = 6 //Number of items I want to get from the server
val foodCategory = arrayListOf<FoodCategory>()
Log.i("getUpdatedFood","I am running on tread: $coroutineContext")
val getRecommended = foodRepository.getRecommendedFood(limiter.toString())
foodCategory += FoodCategory(id = 0.toString(), title = "Recommended for you", foods = getRecommended)
val getSpecials = foodRepository.getSpecials(limiter.toString())
foodCategory += FoodCategory(id = 1.toString(), title = "Specials", foods = getSpecials)
_foodCategory.postValue(foodCategory)
//}
}
}
存储库:
class FoodRepository {
suspend fun getRecommendedFood(limiter: String) = withContext(Dispatchers.IO) {
Log.i("Resp-getRecommended","I am running on tread: $coroutineContext")
return@withContext ProductApi.retrofitService.getRecommended(limiter)
}
suspend fun getSpecials(limiter: String) = withContext(Dispatchers.IO) {
Log.i("Resp-getSpecials","I am running on tread: $coroutineContext")
return@withContext ProductApi.retrofitService.getSpecials(limiter)
}
}
绑定适配器:
//Load image using Glide (in Food item recycleview)
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView , imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("http").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(
RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
//set raters count (in Food item recycleview)
@BindingAdapter("ratersCount")
fun bindText(txtView: TextView, number_of_raters: String?) {
number_of_raters?.let {
val ratersCount = "(${number_of_raters})"
txtView.text = ratersCount
}
}
//update the progressbar visibilty (in outer-parent recycleview)
@BindingAdapter("updateStatus")
fun ProgressBar.updateStatus(status: NetworkStatus?) {
visibility = when (status) {
NetworkStatus.LOADING -> View.VISIBLE
NetworkStatus.DONE -> View.GONE
else -> View.GONE
}
}
//Hide or view an imageview based in the network Status. When network Error, an error image
//will show (in outer-parent recycleview)
@BindingAdapter("setNoInternet")
fun ImageView.setNoInternet(status: NetworkStatus?) {
when(status) {
NetworkStatus.LOADING -> {
visibility = View.GONE
}
NetworkStatus.DONE -> {
visibility = View.GONE
}
NetworkStatus.FAILED -> {
visibility = View.VISIBLE
setImageResource(R.drawable.ic_connection_error)
}
}
}
//Submit the list of FoodCatergory item to the outer-parent recycleview
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<FoodCategory>?) {
(recyclerView.adapter as FoodCategoryAdapter).submitList(data)
}
//Submit list the the Food item recyclew view (child recycleView)
@BindingAdapter("setProducts")
fun RecyclerView.setProducts(foods: List<Food>?) {
if (foods != null) {
val foodAdapter = FoodItemAdapter()
foodAdapter.submitList(foods)
adapter = foodAdapter
}
}
我有一个 Food Item 的回收视图和一个 FoodCategory 的回收视图池。如果我注释掉
_foodCategory.postValue(foodCategory)
在 ViewModel: getUpdatedFood() 中我没有收到消息。但是,当我将列表提交到外部回收视图(持有视图池的那个)时,我得到了这个答案。请帮忙。我坚持了一段时间,试图摆脱这条消息。
谢谢..
已更新 下面是适配器及其视图持有者
FoodItem 布局
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="foodItem"
type="com.example.e_commerceapp.models.Food"/>
<variable
name="font"
type="com.example.e_commerceapp.products.overview.FontFamily"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/child_item_main_layout"
android:background="@drawable/search_background"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
android:layout_width="150dp"
android:layout_height="250dp">
<ImageView
android:layout_width="120dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:id="@+id/burger_image"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_height="160dp"
/>
<!-- app:imageUrl="@{foodItem.imgSrcUrl}"-->
<TextView
android:layout_width="match_parent"
android:layout_height="34dp"
android:layout_marginStart="5dp"
android:id="@+id/burger_title"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="5dp"
android:singleLine="true"
android:textColor="#B4000000"
app:layout_constraintTop_toBottomOf="@id/burger_image"
android:text="@{foodItem.name}"
android:textSize="12sp"
android:fontFamily="@font/poppins"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="35dp"
app:layout_constraintTop_toBottomOf="@id/burger_title"
android:layout_marginEnd="5dp"
android:gravity="center"
android:id="@+id/burger_price"
android:layout_marginStart="5dp"
app:layout_constraintStart_toEndOf="@id/special_price"
android:textColor="#D0000000"/>
<!-- app:price="@{foodItem.price}"-->
<!-- app:specialPrice="@{foodItem.special_price}"-->
<!-- app:fontRes="@{foodItem.special ? font.POPPINS : font.POPPINS_BOLD}"-->
<TextView
android:layout_width="wrap_content"
android:layout_height="35dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/burger_title"
android:layout_marginEnd="5dp"
android:gravity="center_vertical"
android:layout_marginStart="5dp"
android:id="@+id/special_price"
android:textColor="#D0000000"
android:visibility="gone"
android:textStyle="normal"
android:fontFamily="@font/poppins_bold"/>
<!-- app:setSpecialPrice="@{foodItem.special_price}"-->
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
app:layout_constraintTop_toBottomOf="@id/burger_price"
android:src="@drawable/ic_baseline_star_24"
android:visibility="@{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:id="@+id/rating_star"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="15dp"
android:layout_marginStart="5dp"
android:gravity="center"
android:textSize="12sp"
android:visibility="@{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:id="@+id/rating_count"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/burger_price"
android:text="@{foodItem.average_rating}"
android:layout_marginBottom="10dp"
app:layout_constraintStart_toEndOf="@id/rating_star"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="15dp"
android:id="@+id/number_of_raters"
android:textSize="12sp"
android:visibility="@{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:layout_marginStart="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/rating_count"
app:ratersCount="@{foodItem.number_of_raters}"
android:layout_marginBottom="10dp"
app:layout_constraintTop_toBottomOf="@id/burger_price"/>
<ImageView android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FoodItem 适配器
class FoodItemAdapter: ListAdapter<Food ,
FoodItemAdapter.ItemFoodViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup , viewType: Int): ItemFoodViewHolder {
return ItemFoodViewHolder(
FoodItemBinding.inflate(LayoutInflater.from(parent.context),
parent, false))
}
override fun onBindViewHolder(holder: ItemFoodViewHolder , position: Int) {
val currentFood = getItem(position)
holder.bind(currentFood)
}
class ItemFoodViewHolder(private var binding: FoodItemBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(food: Food) {
binding.foodItem = food
binding.executePendingBindings()
}
}
object DiffCallback: DiffUtil.ItemCallback<Food>() {
override fun areItemsTheSame(oldItem: Food , newItem: Food): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Food , newItem: Food): Boolean {
return oldItem.id == newItem.id
}
}
}
食品类别布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="foodCategory"
type="com.example.e_commerceapp.models.FoodCategory"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#fff"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:id="@+id/category_title"
android:layout_marginTop="16dp"
android:text="@{foodCategory.title}"
android:textColor="#2B2A2A"
android:fontFamily="@font/poppins_bold"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/nestedRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:setProducts="@{foodCategory.foods}"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/category_title"
tools:itemCount="4"
tools:listitem="@layout/food_item"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FoodCategory 适配器
class FoodCategoryAdapter: ListAdapter<FoodCategory,
FoodCategoryAdapter.CategoryFoodViewHolder>(Companion) {
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup , viewType: Int): CategoryFoodViewHolder {
return CategoryFoodViewHolder(FoodCategoryBinding.inflate(LayoutInflater.from(parent.context),
parent, false))
}
override fun onBindViewHolder(holder: CategoryFoodViewHolder , position: Int) {
val currentFoodCategory = getItem(position)
holder.bind(currentFoodCategory)
}
inner class CategoryFoodViewHolder(private var binding: FoodCategoryBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(currentFoodCategory: FoodCategory?) {
binding.foodCategory = currentFoodCategory
binding.nestedRecyclerView.setRecycledViewPool(viewPool)
binding.executePendingBindings()
}
}
companion object: DiffUtil.ItemCallback<FoodCategory>() {
override fun areItemsTheSame(oldItem: FoodCategory , newItem: FoodCategory): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: FoodCategory, newItem: FoodCategory): Boolean {
return oldItem.id == newItem.id
}
}
}
父recycleView
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".products.overview.FoodOverviewFragment">
<data>
<variable
name="foodOverview"
type="com.example.e_commerceapp.products.overview.FoodOverviewViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="@color/grey"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:id="@+id/relative_layout"
android:layout_height="105dp"
android:elevation="8dp"
android:layout_marginBottom="5dp"
android:background="#fff"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="200dp"
android:layout_marginTop="10dp"
android:layout_height="35dp"
android:id="@+id/logo_and_name"
android:src="@drawable/compony_logo_and_name"
android:layout_alignParentStart="true"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginTop="10dp"
android:id="@+id/notifications"
android:src="@drawable/ic_baseline_notifications_24"
android:layout_alignParentEnd="true"
android:paddingEnd="20dp"
android:paddingStart="20dp"/>
<TextView
android:layout_width="match_parent"
android:id="@+id/search"
android:layout_marginTop="10dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@drawable/search_background"
android:layout_below="@id/logo_and_name"
android:gravity="center_vertical"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:text="@string/search_text"
tools:ignore="RtlSymmetry"
app:drawableEndCompat="@drawable/ic_baseline_search_24"/>
</RelativeLayout>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:updateStatus="@{foodOverview.status}"
app:layout_constraintTop_toBottomOf="@id/relative_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/progressbar"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/relative_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/noInternetImage"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:id="@+id/foodCategory"
android:clipToPadding="false"
tools:itemCount="4"
tools:listitem="@layout/food_category"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_height="0dp"
app:listData="@{foodOverview.foodCategory}"
app:layout_constraintTop_toBottomOf="@id/relative_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
跳帧可能与您 post 编写的代码无关:对我来说,这听起来像是 RecyclerViews / Adapters 配置错误。不过,您需要 post 使其代码更加清晰。
然而,即使您 post 编辑的可能不是罪魁祸首,您仍然可以优化您拥有的协程代码:
class FoodOverviewViewModel(private val foodRepository: FoodRepository): ViewModel() {
private lateinit var foodProducts: List<Food>
private val _foodCategory = MutableLiveData<List<FoodCategory>>()
val foodCategory: LiveData<List<FoodCategory>>
get() = _foodCategory
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
init {
getOverviewProducts()
}
private fun getOverviewProducts() {
viewModelScope.launch { // <------- Don't apply a custom scope here
_status.value = NetworkStatus.LOADING // <--- Don't call "postValue" here
try {
val food = getUpdatedFood() // <------ This is already using a background dispatcher
_foodCategory.value = food // <------- Emit this value here
_status.value = NetworkStatus.DONE
} catch (e: Exception) {
_status.value = NetworkStatus.FAILED
}
}
}
private suspend fun getUpdatedFood(): List<FoodCategory> { // <---- Return a value here
val limiter = 6 //Number of items I want to get from the server
val foodCategory = arrayListOf<FoodCategory>()
Log.i("getUpdatedFood","I am running on tread: $coroutineContext")
val getRecommended = foodRepository.getRecommendedFood(limiter.toString())
foodCategory += FoodCategory(id = 0.toString(), title = "Recommended for you", foods = getRecommended)
val getSpecials = foodRepository.getSpecials(limiter.toString())
foodCategory += FoodCategory(id = 1.toString(), title = "Specials", foods = getSpecials)
return foodCategories
}
}
这里的关键思想:
- 使用
viewModelScope.launch { ... }
而不是应用范围。你想要那里的所有东西 而不是 主线程上 运行 的协程。例如,参见 https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe - Return 来自
getUpdatedFood
的值,因此您可以在viewModelScope
的主线程上更新
_foodCategory
值