适配器模式实现与研究 - bei1999/work GitHub Wiki

定义

将某个类的接口转换成客户端期望的另一个接口,目的是消除由于接口不匹配所造成的类的兼容性问题。

场景模拟

android 中的listview,recyclerview 中的adapter 的实现

实现

  • recyclerview 中的adapter
package com.bei.test.adapter;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Switch;
import android.widget.TextView;

import com.bei.test.R;
import com.bei.test.bean.NewsData;

import java.util.ArrayList;
import java.util.IllegalFormatCodePointException;
import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;

/**
 * Created by lzb on 16/12/8.
 */

public class CommandNewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private final Context mContext;
    private OnClickListener mOnClickListener;
    private List<NewsData> newsDataList;

    public CommandNewsAdapter(Context context) {
        this.mContext = context;
    }

    public CommandNewsAdapter(Context context, List<NewsData> newsDataList) {
        this.mContext = context;
        this.newsDataList = new ArrayList<NewsData>();
        if (newsDataList != null && !newsDataList.isEmpty()) {
            this.newsDataList.addAll(newsDataList);
        }
    }

    public void setOnClickListener(OnClickListener onClickListener) {
        this.mOnClickListener = onClickListener;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View listItem = inflater.inflate(R.layout.layout_common_news_list, parent, false);
        return new VHNormal(listItem);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof VHNormal) {
            VHNormal vhNormal = (VHNormal) holder;
            NewsData newsData = getItem(position);
            if (newsData != null) {
                vhNormal.textView2.setText(newsData.getName());
            }
        }

    }

    private NewsData getItem(int position) {
        return newsDataList.get(position);
    }

    @Override
    public int getItemCount() {
        return newsDataList.size();
    }

    class VHNormal extends RecyclerView.ViewHolder implements View.OnClickListener {
//        @Bind(R.id.button2)
//        Button button2;
//        @Bind(R.id.textView2)
//        TextView textView2;
        private Button button02;
        TextView textView2;

        public VHNormal(View itemView) {
            super(itemView);
//            ButterKnife.bind(itemView);
            button02 = (Button) itemView.findViewById(R.id.button2);
            textView2 = (TextView) itemView.findViewById(R.id.textView2);
            button02.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            if (mOnClickListener == null) {
                return;
            }
            //            int position = getAdapterPosition();
            //            if (position == RecyclerView.NO_POSITION) {
            //                return;
            //            }
            //            ProjectTemplateBean bean = getItem(position);
            switch (v.getId()) {
                case R.id.button2:
                    mOnClickListener.onActionLiveClick("xxx");
                    break;
                default:
                    break;
            }
        }
    }

    public interface OnClickListener {
        void onActionLiveClick(String temp);
    }
}

//下面是adapter中的代码实现

  * Base class for an Adapter
     *
     * <p>Adapters provide a binding from an app-specific data set to views that are displayed
     * within a {@link RecyclerView}.</p>
     */
    public static abstract class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        private boolean mHasStableIds = false;

        /**
         * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
         * an item.
         * <p>
         * This new ViewHolder should be constructed with a new View that can represent the items
         * of the given type. You can either create a new View manually or inflate it from an XML
         * layout file.
         * <p>
         * The new ViewHolder will be used to display items of the adapter using
         * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
         * different items in the data set, it is a good idea to cache references to sub views of
         * the View to avoid unnecessary {@link View#findViewById(int)} calls.
         *
         * @param parent The ViewGroup into which the new View will be added after it is bound to
         *               an adapter position.
         * @param viewType The view type of the new View.
         *
         * @return A new ViewHolder that holds a View of the given view type.
         * @see #getItemViewType(int)
         * @see #onBindViewHolder(ViewHolder, int)
         */
        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);

        /**
         * Called by RecyclerView to display the data at the specified position. This method should
         * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
         * position.
         * <p>
         * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
         * again if the position of the item changes in the data set unless the item itself is
         * invalidated or the new position cannot be determined. For this reason, you should only
         * use the <code>position</code> parameter while acquiring the related data item inside
         * this method and should not keep a copy of it. If you need the position of an item later
         * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
         * have the updated adapter position.
         *
         * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
         * handle efficient partial bind.
         *
         * @param holder The ViewHolder which should be updated to represent the contents of the
         *        item at the given position in the data set.
         * @param position The position of the item within the adapter's data set.
         */
        public abstract void onBindViewHolder(VH holder, int position);

        /**
         * Called by RecyclerView to display the data at the specified position. This method
         * should update the contents of the {@link ViewHolder#itemView} to reflect the item at
         * the given position.
         * <p>
         * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
         * again if the position of the item changes in the data set unless the item itself is
         * invalidated or the new position cannot be determined. For this reason, you should only
         * use the <code>position</code> parameter while acquiring the related data item inside
         * this method and should not keep a copy of it. If you need the position of an item later
         * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
         * have the updated adapter position.
         * <p>
         * Partial bind vs full bind:
         * <p>
         * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
         * {@link #notifyItemRangeChanged(int, int, Object)}.  If the payloads list is not empty,
         * the ViewHolder is currently bound to old data and Adapter may run an efficient partial
         * update using the payload info.  If the payload is empty,  Adapter must run a full bind.
         * Adapter should not assume that the payload passed in notify methods will be received by
         * onBindViewHolder().  For example when the view is not attached to the screen, the
         * payload in notifyItemChange() will be simply dropped.
         *
         * @param holder The ViewHolder which should be updated to represent the contents of the
         *               item at the given position in the data set.
         * @param position The position of the item within the adapter's data set.
         * @param payloads A non-null list of merged payloads. Can be empty list if requires full
         *                 update.
         */
        public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
            onBindViewHolder(holder, position);
        }

分析

从上面的部分代码可以知道,adapter 为抽象类,必然他的很多方法可以选择实现,通过oncreateview 传入自己的view,通过new vh方式进行了转换引入view,引入的view 内部将以vh类型继续向下使用,vh类猜测所做的事情就是对传入的view进行遍历获得view相关的位置,id等信息,下面的图也说明了这点

     public ViewHolder(View itemView) {
            if (itemView == null) {
                throw new IllegalArgumentException("itemView may not be null");
            }
            this.itemView = itemView;
        }

        void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
            addFlags(ViewHolder.FLAG_REMOVED);
            offsetPosition(offset, applyToPreLayout);
            mPosition = mNewPosition;
        }

        void offsetPosition(int offset, boolean applyToPreLayout) {
            if (mOldPosition == NO_POSITION) {
                mOldPosition = mPosition;
            }
            if (mPreLayoutPosition == NO_POSITION) {
                mPreLayoutPosition = mPosition;
            }
            if (applyToPreLayout) {
                mPreLayoutPosition += offset;
            }
            mPosition += offset;
            if (itemView.getLayoutParams() != null) {
                ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
            }
        }

至于如何将数据绑定到viewholder 上的,则是通过操作上面引入的viewholder 和对应位置上的数据进行手动绑定,最终包装好的vh对象将在adapter 中进行了返回和显示

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof VHNormal) {
            VHNormal vhNormal = (VHNormal) holder;
            NewsData newsData = getItem(position);
            if (newsData != null) {
                vhNormal.textView2.setText(newsData.getName());
            }
        }

    }

既然提到的recyclerview 那顺便看了下notifyDataSetChanged()为何能刷新数据呢

         * @see #notifyItemChanged(int)
         * @see #notifyItemInserted(int)
         * @see #notifyItemRemoved(int)
         * @see #notifyItemRangeChanged(int, int)
         * @see #notifyItemRangeInserted(int, int)
         * @see #notifyItemRangeRemoved(int, int)
         */
        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

//observable 为何物?
public static abstract class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
      
...
}

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

  ...
}

当view的数量和位置等改变的同时利用的java 的观察者模式进行了消息的分发通知

小结

系统的RecyclerView控件 包含view 和data 两部分,view 和data是绑定关联关系,使用的时候如果所有的实现逻辑全面放到RecyclerView 这个控件上,无用代码会增加,RecyclerView控件巧妙的地方在于将重心任务通过适配器模式产生一个抽象的adapter类,里面的方法用户可以选择实现,里面的绑定逻辑帮系统已经实现了,调用的工作重点在重载对应的方法,创建传入view和绑定view 的操作即可,简化开发。

  • java 中的实现 主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

下面以对象的适配器模式举例实现如下:

usb 现在市场分为usb 2.0 和usb 3.0 usb hub 所做的事情类似适配器,具体代码如下:

/**
 * author : lzb
 * e-mail : 
 * time   : 2017/12/11
 * desc   : usb 2.0
 * version: 1.0
 */
public class USB2 {
    public void tranferByTwo(){
        //todo 

    }
}

/**
 * author : lzb
 * e-mail : 
 * time   : 2017/12/11
 * desc   : usb 3.0
 * version: 1.0
 */
public class USB3 {

    public void tranferByTwo() {
    }

    public void tranferThree(){
        //todo

    }
}
/**
 * author : lzb
 * e-mail : 
 * time   : 2017/12/11
 * desc   : 转接接口
 * version: 1.0
 */
public interface HubActionI {
    void tranferByTwo();

    void tranferByThree();

}

/**
 * author : lzb
 * e-mail : 
 * time   : 2017/12/11
 * desc   : usb hub 兼容2.0,3.0
 * version: 1.0
 */
public class USBHubAdpter implements HubActionI{
    private USB2 usb2;
    private USB3 usb3;


    public USBHubAdpter(USB2 usb2,USB3 usb3) {
        usb2 = usb2;
    }
    @Override
    public void tranferByTwo() {
        usb2.tranferByTwo();

    }

    @Override
    public void tranferByThree() {
        usb3.tranferThree();

    }
}

/**
 * author : lzb
 * e-mail :
 * time   : 2017/12/11
 * desc   : 对象适配器模式
 * version: 1.0
 */
public class Test {


    public static void main(String[] args) {

        USB2 usb2 = new USB2();
        USB3 usb3 = new USB3();
        //构造适配器
        HubActionI hubActionI = new USBHubAdpter(usb2,usb3);
        //2.0传输
        hubActionI.tranferByTwo();
        //3.0传输
        hubActionI.tranferByThree();

    }
}

总结

一个对象转换成满足另一个新接口的对象时,创建一个Adapter类,持有原类的一个实例,在Adapter类的方法中,调用实例的方法。简单说上面的例子通过一个中间类进行了包装。

⚠️ **GitHub.com Fallback** ⚠️