适配器模式与自定义控件

适配器模式在开发中使用频率极高,在使用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();

    }
}

单选多选具体的思路:

  1. 通过外部adapter传入,然后在onAttachedToWindow时实例化子View。buidlChildViews
  2. 另外一点是点击选中和点击取消。通过map存储点击状态,在onClick时候判断,并决定调用choosen还是cancel,之后通知更改子view视图样式。

为了实现对每个item都能够定制,把每个item的View通过Adapter来获取(如果数据量大也可以通过adapter的缓存保证流畅)

写在最后

如果我们需要对更好的复用之前的代码,可以使用适配器模式,对于小修小补通过这模式可以避免大的重构,但如果频繁大量使用适配器模式......凡事都要有个度......

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 我的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?...
    justCode_阅读 1,449评论 0 5
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,885评论 25 709
  • 我的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?...
    justCode_阅读 775评论 0 1
  • 前言:设计模式之结构型模式软件模式与具体的应用领域无关,也就是说无论你从事的是移动应用开发、桌面应用开发、Web ...
    markfork阅读 2,500评论 4 13
  • 早晨。六点半。晨光熹微。 偶尔吹来一阵清风,这个点,属于夏末的燥热感还未来袭。 她背着书包走在校园里,果然起来早还...
    陈落雪阅读 533评论 3 2