listview深度巢狀介面卡頓問題解讀
事情源於我們正在寫的一個app,效果圖:
整個介面搭建的就是一個listview,內部的item有gridview 有viewpager等等。當小夥伴寫好的時候就出現了listview滑動卡頓的情況。
網上百度瞭解決辦法:
1..Adapter的getView方法裡面convertView沒有使用setTag和getTag方式;
2.在getView方法裡面ViewHolder初始化後的賦值或者是多個控制元件的顯示狀態和背景的顯示沒有優化好,抑或是裡面含有複雜的計算和耗時操作;
3.在getView方法裡面 inflate的row 巢狀太深(佈局過於複雜)或者是佈局裡面有大圖片或者背景所致;
4.Adapter多餘或者不合理的notifySetDataChanged;
5.listview 被多層巢狀,多次的onMessure導致卡頓,如果多層巢狀無法避免,建議把listview的高和寬設定為fill_parent. 如果是程式碼繼承的listview,那麼也請你別忘記為你的繼承類新增上LayoutPrams,注意高和寬都是fill_parent的;
但是這並沒有完全的解決我的辦法。所以就從以下分析了;
1.資料解析:
把getview中所有要進行json解析的資料都解析好了再放進去。
2.Viewholder優化:
出於偷懶,我們之前用的萬能viewholder
當我再viewholder==null 下面打斷點的時候,發現介面載入後都會過這個斷點,故有的viewholder沒有真正的settag和gettag;所有就對viewholder進行優化,老辦法,listview 裡面多建立幾個holder 我們是十多個不同的viewholder;public class ViewHolder { @SuppressWarnings("unchecked") public static <T extends View> T get(View convertView, int id) { SparseArray<View> viewHolder = (SparseArray<View>) convertView.getTag(); if (viewHolder == null) { viewHolder = new SparseArray<View>(); convertView.setTag(viewHolder); } View childView = viewHolder.get(id); if (childView == null) { childView = convertView.findViewById(id); viewHolder.put(id, childView); } return (T) childView; } }
int templantCode = getItemViewType(i); Holder1 holder1 = null; Holder2 holder2 = null; Holder3 holder3 = null; Holder4 holder4 = null; Holder5 holder5 = null; Holder6 holder6 = null; Holder7 holder7 = null; Holder8 holder8 = null; Holder9 holder9 = null; Holder10 holder10 = null;
3.重寫以下方法
getViewTypeCount
getItemViewType
4.getview中settag的優化(真正卡頓的所在):
以往的規矩是我們在
if(view == null){這兒裡面進行控制元件的初始化而在最外面進行控制元件的賦值。比如:
if(view == null){ view = LayoutInflater.from(context).inflate(R.layout.viewholder1, null); holder4 = new Holder4(view); view.setTag(holder4); }else{ holder4 = (Holder4) view.getTag(); } holder4.tvType3.setText(videoInfoBeen.get(i).getName());
viewHolder3Adapter = new ViewHolder3Adapter(context, appPecialColumnResourcesArrayList3); viewHolder3Adapter.setCountNum(8); holder4.gridView3.setAdapter(viewHolder3Adapter);
當我們複用的時候 直接走的else和下面的方法,所以這兒我們會每次都繫結控制元件和new adapter;
因此我把初始化和繫結控制元件的方法放在了view==null裡面
if(view == null){ view = LayoutInflater.from(context).inflate(R.layout.viewholder1, null); holder4 = new Holder4(view); view.setTag(holder4); viewHolder3Adapter = new ViewHolder3Adapter(context, appPecialColumnResourcesArrayList3); viewHolder3Adapter.setCountNum(8); holder4.gridView3.setAdapter(viewHolder3Adapter); }else{ holder4 = (Holder4) view.getTag(); }這樣就完美的解決了卡頓了現象。
總結:初始化的方法和繫結adapter都放在view==null 的判定中,這樣才能真正的形成佈局複用。多item時多要重寫getviewtype和getviewtypecount方法。深度巢狀時內部的listview或者gridview記得測量高度比如:
public class NonScrollGridView extends GridView { public NonScrollGridView(Context context, AttributeSet attrs){ super(context, attrs); } public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ int mExpandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, mExpandSpec); } }小竅門:getviewtypecount 返回個100W(10W好像也行) 執行程式直接會記憶體溢位,並不會報錯。需要整蠱別人的朋友可以試試。
/********************************華麗翻身******************************/
鑑於評論的朋友說了不能重新整理和資料重複。
我有對這個佈局做了點小優化。就可以重新整理和不存在資料重複了。
思路:將adapter 和內部列表控制元件都封裝在viewholder。
比如這個專案ViewHolder:
class UserCommentHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private OnItemClickListener onItemClick;
private CircleImageView img;
private TextView tv_nick_name,tv_commend_time,tv_commend_reply,tv_commend_content,tv_shop_name,tv_shop_address,tv_shop_tags;
private AppCompatRatingBar rating_bar;
private NonScrollGridView gv_img;
private ArrayList<ImgListEntity> result=new ArrayList<>();
private ImageAdapter imageAdapter;
private View layout_reply;
private ImageView img_shop;
public UserCommentHolder(View itemView,OnItemClickListener onItemClickListener) {
super(itemView);
img= (CircleImageView) itemView.findViewById(R.id.img);
tv_nick_name= (TextView) itemView.findViewById(R.id.tv_nick_name);
tv_shop_name= (TextView) itemView.findViewById(R.id.tv_shop_name);
tv_shop_address= (TextView) itemView.findViewById(R.id.tv_shop_address);
tv_shop_tags= (TextView) itemView.findViewById(R.id.tv_shop_tags);
tv_commend_time= (TextView) itemView.findViewById(R.id.tv_commend_time);
tv_commend_content= (TextView) itemView.findViewById(R.id.tv_commend_content);
tv_commend_reply= (TextView) itemView.findViewById(R.id.tv_commend_reply);
layout_reply= (View) itemView.findViewById(R.id.layout_reply);
img_shop= (ImageView) itemView.findViewById(R.id.img_shop);
gv_img= (NonScrollGridView) itemView.findViewById(R.id.gv_img);
rating_bar= (AppCompatRatingBar) itemView.findViewById(R.id.rating_bar);
imageAdapter=new ImageAdapter(result);
gv_img.setAdapter(imageAdapter);
this.onItemClick = onItemClickListener;
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (onItemClick != null)
onItemClick.onItemClick(view, getPosition()-1);
}
}
然後在繫結資料的onBindViewHolder/getview 方法裡:
holder.result.clear();
holder.result.addAll(“資料來源”);
holder.imageAdapter.notifyDataSetChanged();
這樣就能跟隨主佈局一起重新整理了。當item複用的時候就不會出現重複的view了。