1. 程式人生 > >Android進階之通用RecyclerView介面卡打造方法

Android進階之通用RecyclerView介面卡打造方法

一、引言

從事android開發已經快三年了,相信大家和我一樣,寫了無數列表View的介面卡,不知大家是否厭倦了這些重複的流程和程式碼?反正我是早厭倦了。本篇旨在從一段司空見慣的RecyclerView介面卡程式碼開始,一步一步抽取程式碼的重複部分,打造和ListView通用介面卡類似的RecyclerView通用介面卡,一來為大家提供RecyclerView通用介面卡,提高開發效率,二來分享一種程式碼抽取的思路。記得之前帶我的師傅曾說過:框架不是一下子空想出來的,是”演化”而來的,就像大家熟知的Imageloarder,1.0版本絕對不是現在這個樣子。。。廢話就扯到這裡,下面先看看一段讓人熟悉到吐的介面卡程式碼:

package com.star.xadapter;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

/**
 * Created by kakaxicm on 16/7/7.
 */
public class TestAdapter extends RecyclerView.Adapter<RecyclerView
.ViewHolder>{
private List<String> mData; public TestAdapter(List<String> data){ mData = data; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new TestViewHolder(View.inflate(parent.getContext(),R.layout.item_test, null
)); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { TestViewHolder tHolder = (TestViewHolder) holder; tHolder.tv.setText(mData.get(position)); } @Override public int getItemCount() { return mData == null ? 0 : mData.size(); } private class TestViewHolder extends RecyclerView.ViewHolder{ TextView tv; public TestViewHolder(View itemView) { super(itemView); tv = (TextView) itemView.findViewById(R.id.tv); } } }

當每個頁面的RecyclerView的Model和View不同時,都得另寫一個介面卡,流程都是標準化的:
1.覆寫getItemCount、onCreateViewHolder、onBindViewHolder方法,多型別支援還要覆寫getItemViewType方法;
2.根據每個位置返回的itemview型別,構造不同的ViewHolder,ViewHolder和佈局檔案通過onBindViewHolder建立繫結關係。
既然流程是標準化的,那麼這些公共的流程就可以抽取出來,具體的實現細節暴露給呼叫者實現。下面就來一步一步來“演化”程式碼,打造通用的介面卡。

二、通用介面卡初步演化

從第一節的分析,我們可以把公共的部分抽取出來,具體的細節通過介面暴露給呼叫者實現。那麼公共部分有哪些呢?哪些細節是和業務相關的呢?
1.上面提到的四個方法規定了公共的流程,可以抽取出來;
2.資料層是一個列表,支援的Model型別由使用者決定,所以可以做泛型支援;
3.View層即ViewHolder,也可以通過介面由使用者構造。當然介面卡在多type情況下支援不同的ViewHolder,也需要做泛型支援;
4.從以上3點的分析,那麼對外暴露的介面就應該是這樣:暴露兩個個方法,一個方法繫結資料入參是model和viewholder,另外一個方法構造viewholder。介面程式碼如下:

   /**
     * 繫結資料的介面
     * @param <T> model
     * @param <H> viewholder
     */
    interface OnBindDataInterface<T, H extends RecyclerView.ViewHolder>{
        void onBindData(T model, H holder, int viewType);
        H getViewHolder(ViewGroup parent, int viewType);
    }

結合標準流程的方法,初步演化的介面卡程式碼如下:

package com.star.xadapter;

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

import java.util.List;

/**
 * Created by kakaxicm on 16/7/6.
 */
public class UniversalAdapterBeta<T, H extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<H>{
    private List<T> mData;

    public UniversalAdapterBeta(List<T> data , OnBindDataInterface<T, H> bindInterface){
        mData = data;
        mOnBindDataInterface = bindInterface;
    }

    /**
     * 繫結資料的介面
     * @param <T> model
     * @param <H> viewholder
     */
    interface OnBindDataInterface<T, H extends RecyclerView.ViewHolder>{
        void onBindData(T model, H holder, int viewType);
        H getViewHolder(ViewGroup parent, int viewType);
    }

    private OnBindDataInterface<T, H> mOnBindDataInterface;

    @Override
    public H onCreateViewHolder(ViewGroup parent, int viewType) {
        //構建ViewHolder 入參 parant viewType layoutid
        return mOnBindDataInterface.getViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(H holder, int position) {
        mOnBindDataInterface.onBindData(mData.get(position), holder, getItemViewType(position));
    }

    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }

}

用例:

 UniversalAdapterBeta<String, UniversalViewHolderBeta> adapter = new UniversalAdapterBeta<>(mData, new UniversalAdapterBeta.OnBindDataInterface<String, UniversalViewHolderBeta>() {
            @Override
            public void onBindData(String model, UniversalViewHolderBeta holder, int type) {
                holder.mTv.setText(model);
            }

            @Override
            public UniversalViewHolderBeta getViewHolder(ViewGroup parent, int viewType) {
                return new UniversalViewHolderBeta(parent, R.layout.item_test);
            }
        });

可以看到,使用者只需要構造viewholder和繫結資料就行,而不必關心流程是怎麼走的。初步演化的思想是把View層的viewholder(H) 和 model(T)分開,把model和viewholder都暴露給使用者來構造。

三、通用ViewHolder的封裝

初步演化出來的介面卡需要呼叫者構造model和holder,細想一下,viewholder的構造最關鍵的只有一個引數:佈局檔案id,而這個佈局檔案中有哪些view元素,都交給viewholder封裝即可。至於多種型別的item對應多種viewholder,也是對應不同的layoutid和它的子view。這樣就可以抽取出不同ViewHolder所共有的元素與方法,實現內部構造ViewHolder,進而把介面中ViewHolder構造的方法隱藏起來。那麼這個通用的ViewHolder有哪些共有屬性和方法呢?
1.佈局檔案id
2.子View元素集合
3.子View元素的訪問方法
UniversalViewHolder具體程式碼:

package com.star.xadapter;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by kakaxicm on 16/7/6.
 *
 */
public class UniversalViewHolder extends RecyclerView.ViewHolder{
    private SparseArray<View> mViews;//子View元素集合
    private View mContentView;//itemView,用於查詢子View
    public static UniversalViewHolder getViewHolder(ViewGroup parent, int layoutId)
    {
        return new UniversalViewHolder(View.inflate(parent.getContext(), layoutId, null));
    }

    public UniversalViewHolder(View itemView) {
        super(itemView);
        mViews = new SparseArray<>();
        mContentView = itemView;
    }

    /**
     * 子View元素的訪問方法
     * @param viewId 
     * @param <T>
     * @return
     */
    public <T extends View> T getSubView(int viewId){
        View view = mViews.get(viewId);
        if(view == null){
            view = mContentView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T)view;
    }

}

對使用者暴露的介面就變成了這樣:

    /**
     * 繫結資料的介面
     * @param <T> model
     */
    interface OnBindDataInterface<T>{
        void onBindData(T model, UniversalViewHolder hodler, int type);
        int getItemLayoutId(int viewType);
    }

方法getItemLayoutId就是使用者構造不同的UniversalViewHolder的。於是通用的介面卡就出來了:

package com.star.xadapter;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

import java.util.List;

/**
 * Created by kakaxicm on 16/7/6.
 */
public class UniversalAdapter<T> extends RecyclerView.Adapter<UniversalViewHolder>{
    private List<T> mData;

    public UniversalAdapter(List<T> data , OnBindDataInterface<T> bindInterface){
        mData = data;
        mOnBindDataInterface = bindInterface;
    }

    public UniversalAdapter(List<T> data , OnMultiTypeBindDataInterface<T> bindInterface){
        mData = data;
        mOnMultiTypeBindDataInterface = bindInterface;
        mOnBindDataInterface = bindInterface;
    }

    /**
     * 繫結資料的介面
     * @param <T> model
     */
    interface OnBindDataInterface<T>{
        void onBindData(T model, UniversalViewHolder hodler, int type);
        int getItemLayoutId(int viewType);
    }

    /**
     * 多型別支援
     * @param <T>
     */
    interface OnMultiTypeBindDataInterface<T> extends OnBindDataInterface<T>{
        int getItemViewType(int postion);
    }

    private OnBindDataInterface<T> mOnBindDataInterface;
    private OnMultiTypeBindDataInterface<T> mOnMultiTypeBindDataInterface;

    @Override
    public int getItemViewType(int position) {
        if(mOnMultiTypeBindDataInterface != null){
            return mOnMultiTypeBindDataInterface.getItemViewType(position);
        }
        return 0;
    }

    @Override
    public UniversalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //構建ViewHolder 入參 parant viewType layoutid
        int layoutId = mOnBindDataInterface.getItemLayoutId(viewType);
        UniversalViewHolder holder = UniversalViewHolder.getViewHolder(parent, layoutId);
        return holder;
    }

    @Override
    public void onBindViewHolder(UniversalViewHolder holder, int position) {
        mOnBindDataInterface.onBindData(mData.get(position), holder, getItemViewType(position));
    }

    @Override
    public int getItemCount() {
        return mData == null? 0 : mData.size();
    }

}

說明:
1.這個演化將ViewHolder的構造交給通用的ViewHolder處理,ViewHolder持有子View的id和物件的對映關係。與第一個版本相比,使用者不用構造新的ViewHolder類,只需要設定layout即可;
2.為了支援多型別item,這裡引入OnBindDataInterface的子介面OnMultiTypeBindDataInterface,getItemViewType方法返回item型別,使用者根據返回型別在onBindData方法裡做不同處理。用例如下:

UniversalAdapter<String> adapter = new UniversalAdapter<>(mData, new UniversalAdapter.OnMultiTypeBindDataInterface<String>() {
            @Override
            public int getItemViewType(int postion) {
                if(postion % 2 == 0){
                    return 0;
                }
                if(postion % 3 == 0){
                    return 2;
                }
                return 1;
            }

            @Override
            public void onBindData(String model, UniversalViewHolder holder, int type) {
                switch (type){
                    case 0:
                        TextView tv = holder.getSubView(R.id.tv);
                        tv.setText(model);
                        break;
                    case 1:
                        break;
                    case 2:
                        Button btn = holder.getSubView(R.id.btn);
                        btn.setText(model);
                        break;
                    default:
                        break;
                }

            }

            @Override
            public int getItemLayoutId(int viewType) {
                switch (viewType){
                    case 0:
                        return R.layout.item_test;
                    case 1:
                        return R.layout.item_test2;
                    case 2:
                        return R.layout.item_test3;
                }
                return 0;
            }
        });

補充:UniversalViewHolder持有子View的引用,可以新增一些常用輔助方法,如setImageSrc(int viewId, int drawid)等方法,方便使用者呼叫。
總結:逐步抽取程式碼構建通用框架的設計思想就是典型的模板方法設計模式,它由公用的模板規定流程,通過子類或者介面實現具體每個流程的細節。希望大家從這篇部落格中得到的不僅僅是一個通用的介面卡,更重要的是”重複的事情簡單做”的框架演化思想。

相關推薦

Android通用RecyclerView介面卡打造方法

一、引言 從事android開發已經快三年了,相信大家和我一樣,寫了無數列表View的介面卡,不知大家是否厭倦了這些重複的流程和程式碼?反正我是早厭倦了。本篇旨在從一段司空見慣的RecyclerView介面卡程式碼開始,一步一步抽取程式碼的重複部分,打造和Li

AndroidToast重複顯示解決方法

1 解決思路 不用計算Toast的時間之類的,就是定義一個全域性的成員變數Toast, 這個Toast不為null的時候才去make,否則直接setText.為了按返回鍵後立即使Toast不再顯示,重寫父類Activity的onBackPressed()方法裡

我的Android旅------&gt;Android關於Log的一個簡單封裝

eight ron track 100% oid content amp 嚴重 center android.util.Log類,能夠方便地用於在編碼調試過程中打印日誌。可是在公布後的產品中,假設有太多的日誌打印。則會嚴重地影響性能。對android.util.Lo

我的Android旅------&gt;Android二級ListView列表的實現

cat -i gravity 寫真 技術分享 家政服務 個性 韓國 服務 實現例如以下圖所看到的的二級列表效果 首先是在布局文件裏,布局兩個ListView,代碼例如以下: <LinearLayout xmlns:android="htt

我的Android旅------&gt;Android編譯錯誤java.util.zip.ZipException: duplicate entry的解決方法

image bsp failed 進階 技術分享 san col get cep 今天在Android Studio中把另外一個項目引入當前項目,編譯的時候出現了java.util.zip.ZipException: duplicate entry錯誤。 錯誤例如以下

我的Android旅------&gt;Androidandroid:windowSoftInputMode的使用方法

onf ecif 標簽 ear evel per another not 行為 面試題:怎樣在顯示某個Activity時馬上彈出軟鍵盤? 答案:在AndroidManifest.xml文件裏設置<activity>標簽的android:win

我的Android旅------&gt;怎樣在多個LinearLayout中加入分隔線

lai tex pre draw white 方法 utf == 技術分享 假設要適合於全部的Android版本號,能夠在多個LinearLayout放置用於顯示分隔線的View。比如,放一個ImageView組件。然後將其背景設為分隔線的顏色或圖像,分隔線

我的Android旅------&gt;MIME類型大全

blog cocoa [1] wid htm proxy strong main post 今天在實現一個安裝apk的代碼中看到一段代碼為:application/vnd.android.package-archive。不知其意。所以百度了一下,了解到這是一種M

【我的Android旅】解決Android Studio 運行gradle命令時報錯: 錯誤: 編碼GBK的不可映射字符

定義 編碼 string pretty 出現 mage watermark build issue 原文:【我的Android進階之旅】解決Android Studio 運行gradle命令時報錯: 錯誤: 編碼GBK的不可映射字符 1、問題描述 最近在負責公司基礎

AndroidFragment與Activity之間的數據交互

|| int tails support 實例 onclick interface rod sel 1 為什麽 因為Fragment和Activity一樣是具有生命周期,不是一般的bean通過構造函數傳值,會造成異常。 2 Activity把值傳遞給Fragment 2.1

Android路 - 常見小問題

此篇以後主要記錄我在開發中遇到的問題,用於自我筆記~ APP崩潰,導致一級頁面的fargmentUI層疊 解決方式: /**重寫依賴的activity內onSaveInstanceState 方法 ; 但是格外注意: 註釋呼叫父類方法(如文內的super.

【我的Android旅】關於解析json資料中自帶轉義符號的正確操作

一、問題描述 在實現一個功能的時候,需要伺服器以json格式返回一段功能配置,比如如下所示的json配置。 1.1 我希望的json格式 { "k": "ClientFilterTrace_android", "isVisible": true

【我的Android旅】使用Retrofit進行Post請求報錯: @Field parameters can only be used with form encoding.

一、問題描述 今天隨手寫一個post請求的時候,報錯了,如下所示: 09-28 11:11:28.155 10547-10547/com.xtc.watch E/Fatal: {CrashHandler.saveAndPrintLog-58} jav

【我的Android旅】git 分支的使用

幾乎所有的版本控制系統都以某種形式支援分支。 使用分支意味著你可以把你的工作從開發主線上分離開來,以免影響開發主線。 在很多版本控制系統中,這是一個略微低效的過程——常常需要完全建立一個原始碼目錄的副本。對於大專案來說,這樣的過程會耗費很多時間。 有人把 G

【我的Android旅】自定義ContentProvider

引言 我們知道Android有四大元件,ContentProvider是其中之一,顧名思義:內容提供者。什麼是內容提供者呢?一個抽象類,可以暴露應用的資料給其他應用。應用裡的資料通常說的是資料庫,事實上普通的檔案,甚至是記憶體中的物件,也可以作為內容提供者暴露的

【我的Android旅】解決編譯出現錯誤:Error: In DataSet 'main', no data file for changedFile.

一、問題描述 晚上在Android Studio上,切換分支之後,重新編譯執行的時候,報錯了,錯誤資訊如下所示: 錯誤資訊文字: FAILURE: Build failed with an excep

AndroidAS如何處理不同引用庫(module)裡jar的重複引起問題

1.問題 Error:Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'. 2.原因 根據stackoverflow上面的回答,這個錯誤原因是因為包引用重複

【我的Android旅】自定義控制元件使用ViewPager實現可以預覽的畫廊效果,並且自定義畫面切換的動畫效果的切換時間

我們來看下效果 在這裡,我們實現的是,一個ViewPager來顯示圖片列表。這裡一個頁面,ViewPage展示了前後的預覽,我們讓預覽頁進行Y軸的壓縮,並設定透明度為0.5f,所有我們看到gif最後,左右兩邊的圖片有點朦朧感。讓預覽頁和主頁面有主從感。我們用分

Android

此篇文章隨就業時間而成長~ 1.特殊字元轉義 單引號 ' &apos; 雙引號 " &quot; 大於號 > &gt; 小於號 &l

我的Android旅------>Android自定義View來實現解析lrc歌詞並同步滾動、上下拖動、縮放歌詞的功能

前言 最近有個專案有關於播放音樂時候,關於歌詞有以下幾個功能: 1、實現歌詞同步滾動的功能,即歌曲播放到哪句歌詞,就高亮地顯示出正在播放的這個歌詞; 2、實現上下拖動歌詞時候,可以拖動播放器的進度。即可以不停地上下拖動歌詞,