1. 程式人生 > >Android中的萬能介面卡——base-adapter-helper解析

Android中的萬能介面卡——base-adapter-helper解析

在Android開發中,我們經常會用到ListView、GridView,每次編碼的時候都需要為他們寫對應的Adapter,寫多了就感覺很煩躁了,因為基本的程式設計思想都是一樣的,但是每次都要重複去寫,所以我們能不能把它們抽象成一個通用的模板,這樣就不用每次都重複寫相同的程式碼了,直接重複使用,這樣不是更好,下面我們就來介紹介紹一個開源專案base-adapter-helper。

傳統Adapter的編碼思路,主要看Adapter中的getView方法。

public View getView(int pos, View convertView, ViewGroup parent){ 
    ViewHolder holder; 
    if (convertView == null) { 
        convertView = mInflater.inflate(R.layout.list_item, null); 
        holder = new ViewHolder();  
        holder.text = (TextView) convertView.findViewById(R.id.text)); 
        holder.icon = (ImageView) convertView.findViewButId(R.id.icon)); 
        convertView.setTag(holder); 
    } else { 
        holder = (ViewHolder) convertView.getTag(); 
    }  
    holder.text.setText(DATA[pos]); 
    holder.icon.setImageBitmap((pos & 1) == 1 ? mIcon1 : mIcon2); 
    return convertView; 
}

static class ViewHolder { 
    TextView text; 
    ImageView icon; 
}

上面使用了一個ViewHolder用來快取對應Item中的view,並且重用移出的Item,它對應的就是convertView。這樣注意為了節省資源,提高效率。這種寫法大家都應該很熟悉了。
下面來看看base-adapter-helper是怎樣對其進行抽象封裝的。首先來看看它的類繼承圖。


可以看到BaseQuickAdapter繼承自BaseAdapter,同樣我們重點關注它的getView函式。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (getItemViewType(position) == 0) {
        final H helper = getAdapterHelper(position, convertView, parent);
        T item = getItem(position);
        helper.setAssociatedObject(item);
        convert(helper, item);
        return helper.getView();
    }

    return createIndeterminateProgressView(convertView, parent);
}

private View createIndeterminateProgressView(View convertView, ViewGroup parent) {
    if (convertView == null) {
        FrameLayout container = new FrameLayout(context);
        container.setForegroundGravity(Gravity.CENTER);
        ProgressBar progress = new ProgressBar(context);
        container.addView(progress);
        convertView = container;
    }
    return convertView;
}

下面我們分析分析getView的程式碼:
  • 第3行程式碼就是獲取該postion的Item型別,上面定義了兩種型別的Item,一種是我們需要顯示的View的Item,一種是底部的載入的View的Item。當Item型別為0是就為需要顯示的Item,當Item型別為1是就為底部載入的Item,上面的第11行程式碼的createIndeterminateProgressView就是建立底部用來的載入的Item,可以看到它是一個ProgressBar。另外可以通過showIndeterminateProgress(boolean)來顯示或者隱藏這個item。

  • 第4行程式碼的getAdapterHelper獲取一個BaseAdapterHelper物件,我們可以看到,在BaseQuickAdapter類中,getAdapterHelper是一個抽象函式,所以我們來看看BaseQuickAdapter的子類QuickAdapter,在這個類中它實現了getAdapterHelper方法,執行的實質是BaseAdapterHelper的靜態get方法。另外需要提到的是,QuickAdapter類的建構函式有一個引數為layoutResId,它就是傳入我們要顯示Item的佈局檔案。
static BaseAdapterHelper get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
    if (convertView == null) {
        return new BaseAdapterHelper(context, parent, layoutId, position);
    }

    // Retrieve the existing helper and update its position
    BaseAdapterHelper existingHelper = (BaseAdapterHelper) convertView.getTag();
    existingHelper.position = position;
    return existingHelper;
}

從上面可以看到它也對convertView進行了重用,當convertView為null,那麼我們需要建立一個BaseAdapterHelper,傳入的就是要顯示的位置position以及layoutId佈局檔案,這個position為前面getView中的那個position引數,layoutId為建立QuickAdapter傳入的引數,就是Item的佈局檔案。
protected BaseAdapterHelper(Context context, ViewGroup parent, int layoutId, int position) {
    this.context = context;
    this.position = position;
    this.views = new SparseArray<View>();
    convertView = LayoutInflater.from(context) //
            .inflate(layoutId, parent, false);
    convertView.setTag(this);
}
在BaseAdapterHelper的建構函式裡面,定義了一個views,它其實就是傳統Adapter裡面的那個ViewHolder用來存放Item裡面的各個view。convertView為我們要顯示的Item的View,接著通過setTag函式將BaseAdapterHelper物件本身關聯到convertView上面,所以我們知道每個Item物件都關聯了一個BaseAdapterHelper物件。
當convertView不為null的時候,我們就直接可以複用這個convertView,首先從convertView中取出它關聯的BaseAdapterHelper物件,這樣就可以複用這個convertView對應的BaseAdapterHelper物件。
  • 接著分析最上面的程式碼,第5行程式碼getItem用來得到需要顯示的資料,這裡需要說明的是,我們應該把需要傳入的資料放在一個List中,我們可以看看QuickAdapter的建構函式
public QuickAdapter(Context context, int layoutResId, List<T> data) {
    super(context, layoutResId, data);
}
可以看到我們傳入Item佈局的layoutResId和要顯示的資料data,data是List型別的。
  • 第6行程式碼是將我們向顯示的資料項與BaseAdapterHelper物件關聯起來。
  • 第7行程式碼convert函式同樣是一個抽象函式,它需要我們進行實現,來具體設定對應的Item裡面的內容。
  • 第8行程式碼是當BaseAdapterHelper物件設定為Item裡面的內容之後,然後就可以得到這個Item的View物件進行返回。
下面我們來具體畫個圖來說明。假如我們要顯示的是一個ListView。
當剛開始顯示的時候,因為對應的convertView為null,所以會建立BaseAdapterHelper,核心程式碼如下:
protected BaseAdapterHelper(Context context, ViewGroup parent, int layoutId, int position) {
    this.context = context;
    this.position = position;
    this.views = new SparseArray<View>();
    convertView = LayoutInflater.from(context) //
            .inflate(layoutId, parent, false);
    convertView.setTag(this);
}

因為在BaseAdapterHelper物件中包含有對應的Item對應的convertView和對應的position以及convertView裡面的子view集合,因此我們可以直接通過BaseAdapterHelper物件來完成各項操作
當List向上滑動的時候,第一個Item移出List,底部就需要再顯示一個Item,這個時候getView裡面的convertView就是第一個移出的View,我們可以直接對它重用來顯示下一個Item,核心程式碼為:
BaseAdapterHelper existingHelper = (BaseAdapterHelper) convertView.getTag();
existingHelper.position = position;
他直接得到BaseAdapterHelper物件,然後重新設定它對應的位置postion,因為BaseAdapterHelper物件中引用到了重用的convertView,這樣就可以直接使用這個view的Item了。下面來舉個簡單的例子:
public class MainActivity extends AppCompatActivity {
    private ListView listView;

    private List<String> data = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        data.add("text1");
        data.add("text2");
        data.add("text3");
        data.add("text4");
        data.add("text5");
        data.add("text6");

        listView = (ListView) findViewById(R.id.listview);
        QuickAdapter adapter = new QuickAdapter<String>(this, R.layout.item, data) {
            @Override
            protected void convert(BaseAdapterHelper helper, String item) {
                helper.setText(R.id.textView, item);
            }
        };
        listView.setAdapter(adapter);
    }
    
}

也就是說我們只需要定義一個QuickAdapter重寫convert方法就可以了,在convert方法裡面我們直接使用BaseAdapterHelper物件完成對資料內容item的設定就可以了。

歡迎關注我的公眾號:DroidMind

精品內容,獨家釋出