因为项目的需要我们不可避免的需要使用类似的布局方案,我之前写过的一篇文章总结ScrollView嵌套ListView的解决方法,提出了相应的解决方案。但是却陷入了一个性能的大坑:因为之前的解决方案都是以计算出ListView控件的总高度并固定,那么自然就破坏了LisView内置的特性,造成了Adapter中的 getView会被疯狂的调用。(这里就不贴代码了,用过的童鞋应该都懂)
来,让我们直接开启优化模式。
同样是得自定义View,不过我们继承的不再是ListView,而是LinearLayout。(感谢@张旭童同志的封装,文末贴出源码及源地址)
下面是自定义NestFullListView代码
<span style="font-size:12px;">/**
* 介绍:完全伸展开的ListView(LinearLayout)
* 作者:zhangxutong
* 邮箱:zhangxutong@imcoming.com
* 时间: 2016/9/9.
*/
public class NestFullListView extends LinearLayout {
private LayoutInflater mInflater;
private List<NestFullViewHolder> mVHCahces;//缓存ViewHolder,按照add的顺序缓存,
public NestFullListView(Context context) {
this(context, null);
}
public NestFullListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NestFullListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mInflater = LayoutInflater.from(context);
mVHCahces = new ArrayList<NestFullViewHolder>();
//annotate by zhangxutong 2016 09 23 for 让本控件能支持水平布局,项目的意外收获= =
//setOrientation(VERTICAL);
}
private NestFullListViewAdapter mAdapter;
/**
* 外部调用 同时刷新视图
*
* @param mAdapter
*/
public void setAdapter(NestFullListViewAdapter mAdapter) {
this.mAdapter = mAdapter;
updateUI();
}
public void updateUI() {
if (null != mAdapter) {
if (null != mAdapter.getDatas() && !mAdapter.getDatas().isEmpty()) {
//数据源有数据
if (mAdapter.getDatas().size() > getChildCount()) {//数据源大于现有子View不清空
} else if (mAdapter.getDatas().size() < getChildCount()) {//数据源小于现有子View,删除后面多的
removeViews(mAdapter.getDatas().size(), getChildCount() - mAdapter.getDatas().size());
//删除View也清缓存
while (mVHCahces.size() > mAdapter.getDatas().size()) {
mVHCahces.remove(mVHCahces.size() - 1);
}
}
for (int i = 0; i < mAdapter.getDatas().size(); i++) {
NestFullViewHolder holder;
if (mVHCahces.size() - 1 >= i) {//说明有缓存,不用inflate,否则inflate
holder = mVHCahces.get(i);
} else {
holder = new NestFullViewHolder(getContext(), mInflater.inflate(mAdapter.getItemLayoutId(), this, false));
mVHCahces.add(holder);//inflate 出来后 add进来缓存
}
mAdapter.onBind(i, holder);
//如果View没有父控件 添加
if (null == holder.getConvertView().getParent()) {
this.addView(holder.getConvertView());
}
}
} else {
removeAllViews();//数据源没数据 清空视图
}
} else {
removeAllViews();//适配器为空 清空视图
}
}
}</span>
代码解析:代码增加一个变量 private List<NestFullViewHolder> mVHCahces;//缓存ViewHolder,按照add的顺序缓存
每次updateUI()时,如果是异常情况:适配器为空 清空视图,数据源没数据 清空视图
那么数据源有数据的情况下,比较数据源的size 和现在子View(ItemView)的size:
1、如果数据源大于现有子View,说明屏幕上的View不够用,当然不remove子View,也不用清缓存。
2、如果数据源小于现有子View,删除尾部多的子View,清理多余缓存的ItemView。
遍历数据源,比较i(postion)和viewCaches的size:
1、如果缓存不够就inflate一个新View。
2、如果缓存有,就取出缓存的View。
回调Adapter的onBind方法,
判断这个View有没有父控件,
如果View没有父控件 才addView()。
在上面的代码中已经尽可能的避免了View的inflate,addView()操作。 可是我们都知道,findViewById()的操作也是很费时的,所以在上面的代码中
使用了ViewHolder,下面就来看看这个ViewHolder,代码有点长,作者 考虑的比较细致,封装一些常用的方法,例如setText、setImageResource
等,供外部调用使用,同时还包括一些监听事件。
下面是NestFullViewHolder代码
/**
* NestFullListView 的ViewHolder ,使用者无需关心
* Created by zhangxutong .
* Date: 16/03/11
*/
public class NestFullViewHolder {
private SparseArray<View> mViews;
private View mConvertView;
private Context mContext;
public NestFullViewHolder(Context context, View view) {
mContext = context;
this.mViews = new SparseArray<View>();
mConvertView = view;
}
/**
* 通过viewId获取控件
*
* @param viewId
* @return
*/
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
public View getConvertView() {
return mConvertView;
}
public NestFullViewHolder setSelected(int viewId, boolean flag) {
View v = getView(viewId);
v.setSelected(flag);
return this;
}
/**
* 设置TextView的值
*
* @param viewId
* @param text
* @return
*/
public NestFullViewHolder setText(int viewId, String text) {
TextView tv = getView(viewId);
tv.setText(text);
return this;
}
public NestFullViewHolder setImageResource(int viewId, int resId) {
ImageView view = getView(viewId);
view.setImageResource(resId);
return this;
}
public NestFullViewHolder setImageBitmap(int viewId, Bitmap bitmap) {
ImageView view = getView(viewId);
view.setImageBitmap(bitmap);
return this;
}
public NestFullViewHolder setImageDrawable(int viewId, Drawable drawable) {
ImageView view = getView(viewId);
view.setImageDrawable(drawable);
return this;
}
public NestFullViewHolder setBackgroundColor(int viewId, int color) {
View view = getView(viewId);
view.setBackgroundColor(color);
return this;
}
public NestFullViewHolder setBackgroundRes(int viewId, int backgroundRes) {
View view = getView(viewId);
view.setBackgroundResource(backgroundRes);
return this;
}
public NestFullViewHolder setTextColor(int viewId, int textColor) {
TextView view = getView(viewId);
view.setTextColor(textColor);
return this;
}
public NestFullViewHolder setTextColorRes(int viewId, int textColorRes) {
TextView view = getView(viewId);
view.setTextColor(mContext.getResources().getColor(textColorRes));
return this;
}
@SuppressLint("NewApi")
public NestFullViewHolder setAlpha(int viewId, float value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getView(viewId).setAlpha(value);
} else {
// Pre-honeycomb hack to set Alpha value
AlphaAnimation alpha = new AlphaAnimation(value, value);
alpha.setDuration(0);
alpha.setFillAfter(true);
getView(viewId).startAnimation(alpha);
}
return this;
}
public NestFullViewHolder setVisible(int viewId, boolean visible) {
View view = getView(viewId);
view.setVisibility(visible ? View.VISIBLE : View.GONE);
return this;
}
public NestFullViewHolder linkify(int viewId) {
TextView view = getView(viewId);
Linkify.addLinks(view, Linkify.ALL);
return this;
}
public NestFullViewHolder setTypeface(Typeface typeface, int... viewIds) {
for (int viewId : viewIds) {
TextView view = getView(viewId);
view.setTypeface(typeface);
view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
}
return this;
}
public NestFullViewHolder setProgress(int viewId, int progress) {
ProgressBar view = getView(viewId);
view.setProgress(progress);
return this;
}
public NestFullViewHolder setProgress(int viewId, int progress, int max) {
ProgressBar view = getView(viewId);
view.setMax(max);
view.setProgress(progress);
return this;
}
public NestFullViewHolder setMax(int viewId, int max) {
ProgressBar view = getView(viewId);
view.setMax(max);
return this;
}
public NestFullViewHolder setRating(int viewId, float rating) {
RatingBar view = getView(viewId);
view.setRating(rating);
return this;
}
public NestFullViewHolder setRating(int viewId, float rating, int max) {
RatingBar view = getView(viewId);
view.setMax(max);
view.setRating(rating);
return this;
}
public NestFullViewHolder setTag(int viewId, Object tag) {
View view = getView(viewId);
view.setTag(tag);
return this;
}
public NestFullViewHolder setTag(int viewId, int key, Object tag) {
View view = getView(viewId);
view.setTag(key, tag);
return this;
}
public NestFullViewHolder setChecked(int viewId, boolean checked) {
Checkable view = (Checkable) getView(viewId);
view.setChecked(checked);
return this;
}
/**
* 关于事件的
*/
public NestFullViewHolder setOnClickListener(int viewId,
View.OnClickListener listener) {
View view = getView(viewId);
view.setOnClickListener(listener);
return this;
}
public NestFullViewHolder setOnTouchListener(int viewId,
View.OnTouchListener listener) {
View view = getView(viewId);
view.setOnTouchListener(listener);
return this;
}
public NestFullViewHolder setOnLongClickListener(int viewId,
View.OnLongClickListener listener) {
View view = getView(viewId);
view.setOnLongClickListener(listener);
return this;
}
}
代码解析:
利用 private SparseArray<View> mViews ,以viewId为key,存储ItemView里的各种View。(这里简单解释一下SparseArray是Android 特有的类似于Java的HashMap的键值对存储方式,只不过SparseArray必须以<Integer, E>>类型来的使用,效率更高,本文正好可以用上)
通过public T getView(int viewId)方法,以viewId为key,获取ItemView里的各种View,
该方法是先从mViews的缓存里寻找View,如果找到了直接返回,
如果没找到就view = mConvertView.findViewById(viewId);执行findViewById,得到这个View,并放入mViews的缓存里,这样下次就不用执行findViewById方法。
好了,最后我们来构造我们的NestFullListViewAdapter
/**
* 介绍:完全伸展开的ListView的适配器
* 作者:zhangxutong
* 邮箱:mcxtzhang@163.com
* CSDN:
* 时间: 16/09/09.
*/
public abstract class NestFullListViewAdapter<T> {
private int mItemLayoutId;//看名字
private List<T> mDatas;//数据源
public NestFullListViewAdapter(int mItemLayoutId, List<T> mDatas) {
this.mItemLayoutId = mItemLayoutId;
this.mDatas = mDatas;
}
/**
* 被FullListView调用
*
* @param i
* @param holder
*/
public void onBind(int i, NestFullViewHolder holder) {
//回调bind方法,多传一个data过去
onBind(i, mDatas.get(i), holder);
}
/**
* 数据绑定方法
*
* @param pos 位置
* @param t 数据
* @param holder ItemView的ViewHolder
*/
public abstract void onBind(int pos, T t, NestFullViewHolder holder);
public int getItemLayoutId() {
return mItemLayoutId;
}
public void setItemLayoutId(int mItemLayoutId) {
this.mItemLayoutId = mItemLayoutId;
}
public List<T> getDatas() {
return mDatas;
}
public void setDatas(List<T> mDatas) {
this.mDatas = mDatas;
}
}
核心代码主要就在onBind()方法中,如果对这种封装方式 不是很清楚的 的童鞋强烈建议有时间去看看 鸿洋大神的文章 鸿洋:打造万能适配器
好了,最重要的代码我们已经完成了剩下的就是调用了,So easy!
首先我们得在我们需要使用Xml文件中写入类似的代码(我们继承的是LinearLayout,所以理所应当线性布局的属性我们都能够使用拉!哈哈,想到这儿是不是发现还能来个orientatiion="horizonal"呢?当然拉,有兴趣的童鞋可以试试。)
<xxx.xxx.xxx.NestFullListView
android:id="@+id/cstFullShowListView" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:showDividers="middle"/>
最后在我们需要使用的地方这样调用一下
nestFullListView = (NestFullListView) findViewById(R.id.cstFullShowListView);
nestFullListView.setAdapter(new NestFullListViewAdapter<TestBean>(R.layout.item_lv, mDatas) {
@Override
public void onBind(int pos, TestBean testBean, NestFullViewHolder holder) {
holder.setText(R.id.tv, testBean.getName());//按照这种方式写起来是很爽的吧A_V;
}
});
好啦相对而言性能优化方案已经出来啦。这里有原作者的Demo
github传送门:
https://github.com/mcxtzhang/NestFullListView
复制FullListView包下三个文件(NestFullListView NestFullListViewAdapter NestFullViewHolder)即可畅快使用