我有RecyclerView
,里面还有另一个物品。每个RecyclerView
项目都有其他项目列表,如果我在RecyclerView
中单击 click 项目,这些项目将在下面显示为子项目。为了避免嵌套的RecyclerView ,我在onBindViewHolder()
中遍历了这些项目,并将它们添加到空的LinearLayout 膨胀子项目布局中。
OutOfMemory
错误,because there can be 1000 items and each item could have 1000 subitems.
在我的应用程序中,显示其订单列表,如果单击此列表中的项目,则会显示一个已订购零件的列表一个。
如何解决此问题。滚动也变得迟钝。我正在使用Glide API缓存图像,但是仍然会发生此错误。
recyclerView = view.findViewById<RecyclerView>(R.id.order_recycler_view).apply {
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
//set cache for rv
setItemViewCacheSize(50)
isDrawingCacheEnabled = true
drawingCacheQuality = View.DRAWING_CACHE_QUALITY_HIGH
}
在RVAdapter内部onBindViewHolder():
for(orderItem in it.itemList){
val contentLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.ordered_list_item_layout, null, false)
fillItemView(contentLayout, orderItem, res)
holder.orderContentLayout.addView(contentLayout)
}
FillItemView方法:
private fun fillItemView(contentLayout: View, orderItem: OrderedItem, res: Resources){
val orderItemImage: ImageView = contentLayout.findViewById(R.id.orderPartImage)
val orderItemName: TextView = contentLayout.findViewById(R.id.orderPartName)
val orderItemWeight: TextView = contentLayout.findViewById(R.id.orderPartWeight)
val orderPartPhoto: String? = orderItem.item.itemPhoto.optString(ctx.getString(R.string.part_photo))
setOrderedPartLogo(orderItemImage, res, orderPartPhoto)
val itemPrice: String = String.format("%.2f", orderItem.item.itemPrice)
val spanText = SpannableString(foodPrice + " €" + " " + "(" + orderItem.quantity.toString() + "x" + ")" + orderItem.item.itemName)
spanText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, itemPrice.length + 2, 0)
orderItemName.text = spanText
orderItemWeight.text = orderItem.item.itemWeigth
}
private fun setOrderedPartLogo(orderImageView: ImageView, resources: Resources, imageURL: String?) {
val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.no_photo)
val roundedBitMapDrawable: RoundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap)
roundedBitMapDrawable.isCircular = true
val requestOptions: RequestOptions = RequestOptions()
.circleCrop()
.placeholder(roundedBitMapDrawable)
.error(roundedBitMapDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
Glide.with(orderImageView.context)
.load(imageURL)
.apply(requestOptions)
.into(orderImageView)
}
整个适配器:
class OrderListAdapter(private var mActivity: FragmentActivity,
private var orderList: ArrayList<Order>, private var fragment: OrderListFragment):
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var expandedPosition = -1
private lateinit var mRecyclerView: RecyclerView
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//order item views
val orderName: TextView = itemView.findViewById(R.id.orderName)
val orderWaitTime: TextView = itemView.findViewById(R.id.orderWaitTime)
val orderAddress: TextView = itemView.findViewById(R.id.orderAddress)
val orderDate: TextView = itemView.findViewById(R.id.orderDate)
//details expandable layout
val orderDetailsExpandable: LinearLayout = itemView.findViewById(R.id.orderDetails)
val orderContentLayout: LinearLayout = itemView.findViewById(R.id.contentLayout)
val orderLayout: ConstraintLayout = itemView.findViewById(R.id.mainLayout)
}
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): RecyclerView.ViewHolder {
// create a new view
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.order_recyclerview_item_layout, parent, false)
return ViewHolder(itemView)
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
mRecyclerView = recyclerView
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolder) {
val res = holder.itemView.context.resources
val ctx = holder.itemView.context
orderList[position].let {
val orderPrice: Int = it.orderJSON.getInt(ctx.getString(R.string.order_total_price))
val orderSupplierName: String? = it.orderSupplier?.json?.getString(ctx.getString(R.string.sup_name))
val orderDate: String = it.orderJSON.getString(ctx.getString(R.string.order_date))
val orderPaymentType: Int = it.orderJSON.getInt(ctx.getString(R.string.order_payment_type))
var orderPaymentTypeString = "unknown"
val orderDeliveryType: Int = it.orderJSON.getInt(ctx.getString(R.string.order_delivery_type))
val orderDeliveryPrice: Int = it.orderJSON.getInt(ctx.getString(R.string.order_delivery_price))
val orderJSONObject: JSONObject = it.orderJSON
val orderItemList: ArrayList<OrderedItem> = it.partsList
//OrderDate -> hours, minutes, day, month, year
val formattedOrderDate: OrderDate = getOrderDate(orderDate)
when(orderPaymentType){
1 -> orderPaymentTypeString = "credit"
2 -> orderPaymentTypeString = "credit"
3 -> orderPaymentTypeString = "money"
4 -> orderPaymentTypeString = "voucher"
}
//set order price, name and type
val orderPriceString: String = convertCentsToFloat(orderPrice)
if (orderSupplierName == null){
val spannableText = SpannableString(orderPriceString + " € " + ctx.getString(R.string.default_sup_name) + " - " + orderPaymentTypeString)
spannableText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, orderPriceString.length + 3, 0)
holder.orderName.text = spannableText
} else {
val spannableText = SpannableString(orderPriceString + " € " + orderSupplierName + " - " + orderPaymentTypeString)
spannableText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, orderPriceString.length + 3, 0)
holder.orderName.text = spannableText
}
//set order wait time
holder.orderWaitTime.text = formattedOrderDate.dateHours + ":" + formattedOrderDate.dateMinutes
//set order address
//holder.orderAddress.text = it.orderAddress
//set order date
holder.orderDate.text = formattedOrderDate.dateDay + "." + formattedOrderDate.dateMonth + "." + formattedOrderDate.dateYear
holder.orderContentLayout.removeAllViews()
//create layout for order items
for(orderItem in it.itemList){
val contentLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.ordered_list_item_layout, null, false)
fillItemView(contentLayout, orderItem, res, ctx)
holder.orderContentLayout.addView(contentLayout)
}
//create footer delivery
val deliveryLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.order_delivery_footer_layout, null, false)
fillDeliveryFooter(deliveryLayout, orderDeliveryType, orderDeliveryPrice, res, ctx)
holder.orderContentLayout.addView(deliveryLayout)
//create footer orderRepeat Button
val orderRepeatLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.order_repeat_order_layout, null, false)
holder.orderContentLayout.addView(orderRepeatLayout)
orderRepeatLayout.setOnClickListener {
fragment.switchToOrderCartActivity(orderItemList)
}
//expanding order view on click
val isExpanded = position == expandedPosition
holder.orderDetailsExpandable.visibility = if (isExpanded) View.VISIBLE else View.GONE
holder.itemView.isActivated = isExpanded
holder.orderLayout.setOnClickListener {
createLog("expPos ", position.toString())
orderList[position].let {
if(expandedPosition != position){
if(expandedPosition != -1){
val myLayout: View? = mRecyclerView.layoutManager.findViewByPosition(expandedPosition)
createLog("myLayout", myLayout.toString())
createLog("OrderExp", "Expanding layout")
if(myLayout != null){
myLayout.findViewById<LinearLayout>(R.id.orderDetails).visibility = View.GONE
}
}
createLog("expPosSet ", position.toString())
expandedPosition = position
} else {
expandedPosition = -1
}
notifyItemChanged(position)
scrollToTop(holder.itemView)
}
}
}
}
}
override fun getItemCount() = orderList.size
private fun scrollToTop(v: View) {
val itemToScroll = mRecyclerView.getChildAdapterPosition(v)
val centerOfScreen = mRecyclerView.width / 2 - v.width / 2
fragment.getRecyclerViewManager().scrollToPositionWithOffset(itemToScroll, centerOfScreen)
}
private fun fillItemView(contentLayout: View, orderItem: OrderedItem, res: Resources){
val orderItemImage: ImageView = contentLayout.findViewById(R.id.orderPartImage)
val orderItemName: TextView = contentLayout.findViewById(R.id.orderPartName)
val orderItemWeight: TextView = contentLayout.findViewById(R.id.orderPartWeight)
val orderPartPhoto: String? = orderItem.item.itemPhoto.optString(ctx.getString(R.string.part_photo))
setOrderedPartLogo(orderItemImage, res, orderPartPhoto)
val itemPrice: String = String.format("%.2f", orderItem.item.itemPrice)
val spanText = SpannableString(foodPrice + " €" + " " + "(" + orderItem.quantity.toString() + "x" + ")" + orderItem.item.itemName)
spanText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, itemPrice.length + 2, 0)
orderItemName.text = spanText
orderItemWeight.text = orderItem.item.itemWeigth
}
private fun fillDeliveryFooter(deliveryLayout: View, deliveryType: Int, deliveryPrice: Int, res: Resources, ctx: Context){
val deliveryImageIcon: ImageView = deliveryLayout.findViewById(R.id.deliveryIconImage)
val deliveryPriceTextView: TextView = deliveryLayout.findViewById(R.id.deliveryLabelText)
//set delivery icon
when(deliveryType){
1 -> deliveryImageIcon.setImageResource(R.drawable.ico_summary_delivery)
2 -> deliveryImageIcon.setImageResource(R.drawable.ico_summary_pickup)
else -> deliveryImageIcon.setImageResource(R.drawable.restauracia_no_photo)
}
//set delivery price, name label
val deliveryPriceString: String = convertCentsToFloat(deliveryPrice)
val deliverySpannable = SpannableString(deliveryPriceString + " € Doprava / Vyzdvihnutie")
deliverySpannable.setSpan(ForegroundColorSpan(res.getColor(R.color.colorPrice)), 0, deliveryPriceString.length + 2, 0)
deliveryPriceTextView.text = deliverySpannable
}
private fun setOrderedPartLogo(orderImageView: ImageView, resources: Resources, imageURL: String?) {
val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.no_photo)
val roundedBitMapDrawable: RoundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap)
roundedBitMapDrawable.isCircular = true
val requestOptions: RequestOptions = RequestOptions()
.circleCrop()
.placeholder(roundedBitMapDrawable)
.error(roundedBitMapDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
Glide.with(orderImageView.context)
.load(imageURL)
.apply(requestOptions)
.into(orderImageView)
}
private fun convertCentsToFloat(centPrice: Int): String {
val centOnlyPrice: Int = centPrice % 100
val euroPrice: Int = (centPrice - centOnlyPrice) / 100
if (centOnlyPrice < 10) {
val finalPrice: String = euroPrice.toString() + ".0" + centOnlyPrice.toString()
return finalPrice
} else {
val finalPrice: String = euroPrice.toString() + "." + centOnlyPrice.toString()
return finalPrice
}
}
private fun getOrderDate(date: String): OrderDate{
val rawDate: List<String> = date.split("T")
val dateOnly: String = rawDate[0]
val dateFormat: List<String> = dateOnly.split("-")
val timeOnly: String = rawDate[1]
val timeFormat: List<String> = timeOnly.split(":")
val finalDate = OrderDate(timeFormat[0], timeFormat[1], dateFormat[2], dateFormat[1], dateFormat[0])
return finalDate
}
fun createLog(tag: String, msg: String){
Log.i(tag, msg)
}
fun refreshOrder(orderListRefreshed: ArrayList<Order>){
orderList = orderListRefreshed
notifyDataSetChanged()
if(AndroidAssets.getInstance(mActivity).orderList.isEmpty()){
mRecyclerView.visibility = View.GONE
fragment.showFooterLayout()
} else{
mRecyclerView.visibility = View.VISIBLE
fragment.hideFooterLayout()
}
fragment.hideProgressBar()
}
}
来自AndroidProfiler的“内存使用情况”图片(内存使用情况增加了25秒-这是我开始滚动RecyclerView ...的位置,然后它就掉了)。
更新:经过更好的分析,我发现一个SubItem的内存为2.5MB。如果我有5个订单,每个订单包含20个项目,它将在RAM中分配250MB的空间。这就是Glide缓存图像。
更新2:有什么方法只能加载可见视图?因此,当用户滚动时,它将加载新视图,并且将不在内存中的前一个视图将从内存中删除。我以为recyclerview会默认通过回收项目布局视图来做到这一点。
更新3:我为内部列表实现了新的recyclerview和适配器初始化。当在onBindViewHolder()中的视图标记为展开时,并且在未展开RV且Adapter设置为null时,将初始化此rv和适配器。因此,我实现了Nested RecyclerView。问题是我的内部recyclerview根本没有滚动。我必须设置滚动并将RV高度设置为固定大小(例如400dp),因为如果我将其保留为match_parent或wrap_content,如果里面有20多个项目,它将抛出OutOfMemoryError->这不会回收视图。如何实现两个recyclerview垂直滚动?
布局可视化:
答案 0 :(得分:0)
您的组(每个组包含1000个项目)实在太大了。
您应该展平层次结构-也就是说,将所有项目单独暴露给RecyclerView。您的适配器将需要重写getItemViewType()以针对不同的视图类型返回不同的值,并且您将需要根据每个位置的项目返回不同类型的视图持有者。
这样,您将只为屏幕增加尽可能多的视图(再加上RecyclerView要求的其他一些视图,以避免滚动时出现过多的膨胀)。