【关键词】
通用系列
ListView
【问题】
- 在使用ListView的过程中,总是不断的重复写Adapter,ViewHolder等代码,很繁琐;
- 想个办法,不论是简单的Item布局还是复杂的Item布局,都可以用同一种方案快速实现出来;
【效果图】
【分析】
/**
* 特色功能:
* ★ 通用(使用ListView和GridView的地方都可以用此类来简化)
* ★ 简单,不需要重写BaseAdapter;
* ★ 删除伴随着动画(不突兀);
* ★ 数据和ListView都托管给了LouAdapter;
* ★ 强制使用ViewHolder缓存;
* ★ 局部刷新;
* ★ 多选模式/单选模式的控制;(需要给Item布局设置selector才可以在视觉上区分是否为选中模式)
* ★ 绑定ListView到this(即在使用的时候不用给ListView 设置Adapter);
* ★ 以前在使用ListView的时候需要做这些处理:
* {@code
* // 查找ListView;
* // 设置Adapter;
* // 如果布局复杂的话还需要重新Adapter;
* // 绑定数据;
* }
* ★ 现在只需要简单分配参数,即使是复杂的布局也可以很快实现出来:
* {@code
* ListView listView = new ListView(mContext);
* int layoutId = android.R.layout.simple_list_item_1;
* // <> 中的内容是你的Bean元素;
* // 第一个参数(listView):被托管的ListView;
* // 第二个参数(layoutId):ListView的Item布局;
* // 方法assign:用来分配Bean和Item布局里视图的对应关系;
* LouAdapter<String> adapter = new LouAdapter<String>(listView, layoutId) { @Override protected void assign(ViewHolder holder, String s) {holder.putText(android.R.id.text1, s);}};
* }
*/
【解决方案】
- 将ListView和数据都托管给LouAdapter,后期修改数据和更新视图的过程中,只需引用LouAdapter的实例即可;
换句话说,只在实例化LouAdapter的时候用到了ListView,其他时候的数据操作都只是对LouAdapter的简单引用; - 至于视图和Bean的映射,通过assign方法分配即可;
- 修改某个Item的数据后,通过获取对应的Item视图进行单独刷新;
【代码】
「效果图的代码」
package com.lou.as.lou;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.lyloou.android.activity.LouActivity;
import com.lyloou.android.adapter.LouAdapter;
import com.lyloou.android.util.Utoast;
import java.util.Arrays;
public class DemoAdapterActivity extends LouActivity {
private Activity mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(initListView());
}
private View initListView() {
final LouAdapter<String> adapter = new LouAdapter<String>(new ListView(mContext), android.R.layout.simple_list_item_1) {
@Override
protected void assign(ViewHolder holder, String s) {
// 用视图显示数据
holder.putText(android.R.id.text1, s);
}
};
// 初始化列表
adapter.initList(Arrays.asList("添加元素", "长按更新元素"));
// 添加点击事件
adapter.getBindView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (adapter.getItem(position).equals("添加元素")) {
// 添加元素
adapter.addItem("我是新元素" + adapter.getCount());
} else if (adapter.getItem(position).equals("删除我呀")) {
// 删除元素(有删除动画)
adapter.deleteItemWithAnim(position);
} else if (adapter.getItem(position).contains("我是新元素")) {
adapter.getList().set(position, "删除我呀");
adapter.updateView(position);
} else if (adapter.getItem(position).equals("好吧,我投降")) {
adapter.deleteItemWithAnim(position);
}
}
});
adapter.getBindView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (adapter.getItem(position).equals("长按更新元素")) {
adapter.getList().set(position, "已被更新的元素");
adapter.updateView(position); // 局部更新
} else if (adapter.getItem(position).equals("已被更新的元素")) {
adapter.getList().set(position, "都更新过了,还按我做啥");
adapter.updateView(position);
} else if (adapter.getItem(position).equals("都更新过了,还按我做啥")) {
adapter.getList().set(position, "再按我,我就消失");
adapter.updateView(position);
} else if (adapter.getItem(position).equals("再按我,我就消失")) {
adapter.getList().set(position, "好吧,我投降");
adapter.updateView(position);
} else if (adapter.getItem(position).equals("添加元素")) {
Utoast.show(mContext, "滚犊子,连我也想删?\n活得不耐烦了!");
return true;
}
return false;
}
});
return adapter.getBindView();
}
}
「通用源代码」
package com.lyloou.android.adapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.lyloou.android.util.Uscreen;
import com.lyloou.android.util.Uview;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Lou on 2016/3/23.
* Updated By Lou on 2016/05/07.
*/
/**
* 特色功能:
* ★ 通用(使用ListView和GridView的地方都可以用此类来简化)
* ★ 简单,不需要重写BaseAdapter;
* ★ 删除伴随着动画(不突兀);
* ★ 动态添加元素;
* ★ 强制使用ViewHolder缓存;
* ★ 局部刷新;
* ★ 多选模式/单选模式的控制;
* ★ 绑定ListView到this(即在使用的时候不用给ListView 设置Adapter);
* ★ 以前在使用ListView的时候需要做这些处理:
* {@code
* // 查找ListView;
* // 设置Adapter;
* // 如果布局复杂的话还需要重新Adapter;
* // 绑定数据;
* }
* ★ 现在只需要简单分配参数,即使是复杂的布局也可以很快实现出来:
* {@code
* ListView listView = new ListView(mContext);
* int layoutId = android.R.layout.simple_list_item_1;
* // <> 中的内容是你的Bean元素;
* // 第一个参数(listView):被托管的ListView;
* // 第二个参数(layoutId):ListView的Item布局;
* // 方法assign:用来分配Bean和Item布局里视图的对应关系;
* LouAdapter<String> adapter = new LouAdapter<String>(listView, layoutId) { @Override protected void assign(ViewHolder holder, String s) {holder.putText(android.R.id.text1, s);}};
* }
*/
public abstract class LouAdapter<T> extends BaseAdapter {
private ArrayList<T> mLists;
private Context mContext;
private AbsListView mListView;
private int mLayoutId;
public LouAdapter(AbsListView listView, int layoutId) {
mContext = listView.getContext();
mListView = listView;
mLayoutId = layoutId;
mLists = new ArrayList<T>();
mListView.setAdapter(this);
}
@Override
public int getCount() {
return mLists.size();
}
@Override
public T getItem(int position) {
return mLists.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return updateView(position, convertView);
}
public AbsListView getBindView() {
return mListView;
}
// -------- 添加 choice调用(2016.03.25)
public void clearChoice() {
mListView.clearChoices();
updateChange();
// selectAllChoice(false);
mListView.postDelayed(new Runnable() {
@Override
public void run() {
// 注意需要使用Runnable才能生效;
// 参考资料[ListView selection remains persistent after exiting choice mode]
// (http://stackoverflow.com/questions/9754170/listview-selection-remains-persistent-after-exiting-choice-mode)
mListView.setChoiceMode(AbsListView.CHOICE_MODE_NONE);
}
}, 160);
}
public void setChoiceMode(int mode) {
mListView.setChoiceMode(mode);
}
public int getChoiceMode() {
return mListView.getChoiceMode();
}
public void deleteChoicedItem() {
if (mListView.getChoiceMode() != AbsListView.CHOICE_MODE_NONE) {
// 获取被选中的ITEM;
SparseBooleanArray sparseBooleanArray = mListView.getCheckedItemPositions();
ArrayList<T> deleteLists = new ArrayList<T>();
for (int i = 0; i < sparseBooleanArray.size(); i++) {
if (sparseBooleanArray.valueAt(i)) {
deleteLists.add(mLists.get(sparseBooleanArray.keyAt(i)));
}
}
mLists.removeAll(deleteLists);
mListView.clearChoices();
updateChange();
}
}
public void selectAllChoice(boolean selectAll) {
for (int i = 0; i < mListView.getCount(); i++) {
mListView.setItemChecked(i, selectAll);
}
updateChange();
}
// ~~~~~~~~~~~~
/**
* contain 来表示是否已经包含元素;(可能需要重写,如果允许重复的话,就不必要重写了);
*
* @param o 要判断的元素
* @return 返回的结果
*/
protected boolean contain(T o) {
if (mLists.contains(o)) {
return true;
}
return false;
}
/**
* 用数据来更新视图;
*
* @param position
* @param convertView
* @return
*/
private View updateView(int position, View convertView) {
ViewHolder holder = ViewHolder.getInstance(mContext, mLayoutId, convertView, position);
assign(holder, getItem(position));
return holder.getConvertView();
}
/**
* 当更改了某一个Item之后,可以通过updateView(position);的方式只更新这一个Item;
*
* @param position
*/
public void updateView(int position) {
View convertView = getIndexView(position);
updateView(position, convertView);
}
/**
* @param holder
* @param t
*/
protected abstract void assign(ViewHolder holder, T t);
/**
* 获取可见元素的View;
*
* @param position
* @return
*/
public View getIndexView(int position) {
int currentIndex = position - mListView.getFirstVisiblePosition();
if (currentIndex < 0 || currentIndex >= mLists.size()) {
return null;
}
return mListView.getChildAt(currentIndex);
}
public void addItem(T o, boolean filter) {
if (filter && contain(o)) {
return;
}
mLists.add(o);
updateChange();
}
/**
* 默认过滤添加的元素;
*
* @param o
*/
public void addItem(T o) {
addItem(o, true);
}
public List<T> getList() {
return mLists;
}
/**
* 初始化元素
*
* @param list
*/
public void initList(List<T> list) {
mLists.clear();
mLists.addAll(list);
updateChange();
}
public void deleteItem(T o) {
mLists.remove(o);
updateChange();
}
public void deleteItem(int position) {
mLists.remove(position);
updateChange();
}
/**
* 高度变为0后删除元素;
*
* @param position
*/
public void deleteItemWithAnim(final int position) {
final View view = getIndexView(position);
final int initHeight = view.getMeasuredHeight();
Animation anim = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
if (mLists.size() > 0 && interpolatedTime == 1) {
// 高度为0时删除元素,并更新 adapter
if (view.getTag() instanceof ViewHolder) {
((ViewHolder) view.getTag()).mark = ViewHolder.MARK.DELETE;
deleteItem(position);
}
} else {
// 不断的改变高度,直到高度为0;
view.getLayoutParams().height = initHeight
- (int) (initHeight * interpolatedTime);
view.requestLayout();
}
}
};
anim.setDuration(300);
view.startAnimation(anim);
}
public void updateChange() {
notifyDataSetChanged();
}
public void clear() {
mLists.clear();
updateChange();
}
/**
* Created by Lou on 2016/3/23.
*/
public static class ViewHolder {
private SparseArray<View> mViews;
private View mConvertView;
private int mPosition;
MARK mark;
enum MARK {
NORMAL, DELETE
}
private ViewHolder(Context context, int layoutId, int position) {
mPosition = position;
mViews = new SparseArray<View>();
mConvertView = LayoutInflater.from(context).inflate(layoutId, null);
mConvertView.setTag(this);
}
// 权限:default 包级私有
static ViewHolder getInstance(Context context, int layoutId, View convertView, int position) {
boolean needInflate = convertView == null || ((ViewHolder) convertView.getTag()).mark == MARK.DELETE;
if (needInflate) {
return new ViewHolder(context, layoutId, position);
}
return (ViewHolder) convertView.getTag();
}
public int getPosition() {
return mPosition;
}
public View getConvertView() {
return mConvertView;
}
@SuppressWarnings("unchecked")
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 ViewHolder putText(int viewId, String text) {
View v = getView(viewId);
if (v instanceof TextView) {
((TextView) v).setText(text);
}
return this;
}
public ViewHolder putText(int viewId, int resId) {
View v = getView(viewId);
if (v instanceof TextView) {
((TextView) v).setText(resId);
}
return this;
}
public ViewHolder putImg(int viewId, int resId) {
View v = getView(viewId);
if (v instanceof ImageView) {
((ImageView) v).setImageResource(resId);
}
return this;
}
/**
* @param viewId 视图Id
* @param resId 图片资源Id
* @param roundShape 是否是圆角图片
* @return
*/
public ViewHolder putImg(int viewId, int resId, boolean roundShape) {
if (roundShape) {
View v = getView(viewId);
if (v instanceof ImageView) {
Bitmap bitmap = Uview.getBitmapByXfermode(v.getContext(),
resId,
Color.parseColor("#993382"),
Uscreen.dp2Px(v.getContext(), 48),
Uscreen.dp2Px(v.getContext(), 48),
PorterDuff.Mode.SRC_IN);
((ImageView) v).setImageBitmap(bitmap);
}
} else {
return putImg(viewId, resId);
}
return this;
}
public ViewHolder putImg(int viewId, Bitmap bitmap) {
View v = getView(viewId);
if (v instanceof ImageView) {
((ImageView) v).setImageBitmap(bitmap);
}
return this;
}
public ViewHolder putImg(int viewId, Drawable drawable) {
View v = getView(viewId);
if (v instanceof ImageView) {
((ImageView) v).setImageDrawable(drawable);
}
return this;
}
public void putCbtn(int viewId, int status, boolean clickable) {
View v = getView(viewId);
if (v instanceof CompoundButton) {
CompoundButton cb = (CheckBox) v;
if (status != 0) {
cb.setChecked(true);
} else {
cb.setChecked(false);
}
cb.setClickable(clickable);
}
}
}
}