适配器模式在开发中使用频率极高,在使用ListView,RecyclerView等控件的时候经常会编写Adapter相关代码,最近做一个单选多选控件也用到了。本文主要通过自定义View来介绍和实战适配器模式
定义
将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。wiki
适配器模式有两种实现,一种是对象适配器模式,另一种是类适配器模式。
类适配模式
类适配器模式通过多重继承来提供methodA()。Android的BaseAdapter是通过实现ListAdapter和SpinnerAdapter接口将不兼容的接口实现为兼容接口。
自定义View中使用Adapter
以我最近做的多选单选自定义控件为例吧。
public class Choice extends ViewGroup {
//...省略...
private ChoiceAdapter mAdapter;
private OnItemClickListener mOnItemClickListener;
private Map<Integer, Boolean> mClickState = new HashMap<>();
public Choice(Context context, AttributeSet attrs) {
super(context, attrs);
//省略...
array.recycle();
}
public void setAdapter(ChoiceAdapter adapter) {
this.mAdapter = adapter;
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.mOnItemClickListener = listener;
}
private void buildChildViews() {
for (int i = 0; i< mAdapter.getCount(); ++i) {
View child = buildChildView(i);
addView(child);
}
}
private View buildChildView(final int postion) {
View child = mAdapter.getView(postion, null, this);
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mOnItemClickListener != null) {
if (mClickState.get(postion) == null
|| mClickState.get(postion) == false) {
//未点击过
mClickState.put(postion, true);
mOnItemClickListener.onChoosen(view, postion);
notifyStateChanged(true,postion);
} else {
//确认点击过
mClickState.put(postion, false);
mOnItemClickListener.onCancle(view, postion);
notifyStateChanged(false,postion);
}
}
}
});
return child;
}
private void notifyStateChanged(final boolean b,final int postion) {
//更新状态
if (mIsSingle) {
//单选
mClickState.clear();
mClickState.put(postion, b);
} else {
//多选
mClickState.put(postion, b);
}
changeChildrenViews();
}
private void changeChildrenViews() {
final int count = getChildCount();
for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
if (mClickState.get(i) == null || mClickState.get(i) == false) {
child.setBackgroundColor(NOT_PRESSED_COLOR);
} else {
child.setBackgroundColor(mPressedColor);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//...省略...
setMeasuredDimension(resWidth, resHeight);
measureChildViews();
}
//测量子view
private void measureChildViews() {
final int count = getChildCount();
final int colSize = count <= mRowSize ? 1 : (count / mRowSize);
final int childWidth = mChildWidth = getMeasuredWidth() / mRowSize;
final int childHeight = mChildHeight =getMeasuredHeight() / colSize;
final int childMode = MeasureSpec.EXACTLY;
for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
int widthSpec = MeasureSpec.makeMeasureSpec(childWidth, childMode);
int heighSpec = MeasureSpec.makeMeasureSpec(childHeight, childMode);
child.measure(widthSpec, heighSpec);
}
}
@Override
protected void onLayout(boolean b, int i0, int i1, int i2, int i3) {
final int childCount = getChildCount();
int left = 0, top = 0;
for (int r = 0; r < childCount/mRowSize; ++r) {
for (int c = 0; c < mRowSize; ++c) {
int index = r * mRowSize + c;
final View child = getChildAt(index);
if (child.getVisibility() == GONE) {
continue;
}
left = c * mChildWidth;
top = r * mChildHeight;
child.layout(left, top, left + mChildWidth, top + mChildHeight);
}
}
}
@Override
protected void onAttachedToWindow() {
if (mAdapter != null) {
buildChildViews();
}
super.onAttachedToWindow();
}
}
单选多选具体的思路:
- 通过外部adapter传入,然后在onAttachedToWindow时实例化子View。buidlChildViews
- 另外一点是点击选中和点击取消。通过map存储点击状态,在onClick时候判断,并决定调用choosen还是cancel,之后通知更改子view视图样式。
为了实现对每个item都能够定制,把每个item的View通过Adapter来获取(如果数据量大也可以通过adapter的缓存保证流畅)
写在最后
如果我们需要对更好的复用之前的代码,可以使用适配器模式,对于小修小补通过这模式可以避免大的重构,但如果频繁大量使用适配器模式......凡事都要有个度......