原創(chuàng)文章,轉(zhuǎn)載請注明:轉(zhuǎn)載自Keegan小鋼 Android項(xiàng)目重構(gòu)之路:架構(gòu)篇 前兩篇文章Android項(xiàng)目重構(gòu)之路:架構(gòu)篇和Android項(xiàng)目重構(gòu)之路:界面篇已經(jīng)講了我的項(xiàng)目開始搭建時(shí)的架構(gòu)設(shè)計(jì)和界面設(shè)計(jì),這篇就講講具體怎么實(shí)現(xiàn)的,以實(shí)現(xiàn)最小化可用產(chǎn)品(MVP)的目標(biāo),用最簡單的方式來搭建架構(gòu)和實(shí)現(xiàn)代碼。
根據(jù)架構(gòu)篇所講的,將項(xiàng)目分為了四個(gè)層級:模型層、接口層、核心層、界面層。四個(gè)層級之間的關(guān)系如下圖所示: 實(shí)現(xiàn)上,在Android Studio分為了相應(yīng)的四個(gè)模塊(Module):model、api、core、app。
業(yè)務(wù)對象模型統(tǒng)一存放于model模塊,是對業(yè)務(wù)數(shù)據(jù)的封裝,大部分都是從接口傳過來的對象,因此,其屬性也與接口傳回的對象屬性相一致。在這個(gè)Demo里,只有一個(gè)業(yè)務(wù)對象模型,封裝了券的基本信息,以下是該實(shí)體類的代碼: /** * 券的業(yè)務(wù)模型類,封裝了券的基本信息。 * 券分為了三種類型:現(xiàn)金券、抵扣券、折扣券。 * 現(xiàn)金券是擁有固定面值的券,有固定的售價(jià); * 抵扣券是滿足一定金額后可以抵扣的券,比如滿100減10元; * 折扣券是可以打折的券。 * * @version 1.0 創(chuàng)建時(shí)間:15/6/21 */ public class CouponBO implements Serializable { private static final long serialVersionUID = -8022957276104379230L; private int id; // 券id private String name; // 券名稱 private String introduce; // 券簡介 private int modelType; // 券類型,1為現(xiàn)金券,2為抵扣券,3為折扣券 private double faceValue; // 現(xiàn)金券的面值 private double estimateAmount; // 現(xiàn)金券的售價(jià) private double debitAmount; // 抵扣券的抵扣金額 private double discount; // 折扣券的折扣率(0-100) private double miniAmount; // 抵扣券和折扣券的最小使用金額 // TODO 所有屬性的getter和setter }
在這個(gè)Demo里,提供了4個(gè)接口:一個(gè)發(fā)送驗(yàn)證碼的接口、一個(gè)注冊接口、一個(gè)登錄接口、一個(gè)獲取券列表的接口。這4個(gè)接口具體如下:
在架構(gòu)篇已經(jīng)講過,接口返回的json數(shù)據(jù)有三種固定結(jié)構(gòu): {'event': '0', 'msg': 'success'} {'event': '0', 'msg': 'success', 'obj':{...}} {'event': '0', 'msg': 'success', 'objList':[{...}, {...}], 'currentPage': 1, 'pageSize': 20, 'maxCount': 2, 'maxPage': 1} 因此可以封裝成實(shí)體類,代碼如下: public class ApiResponse<T> { private String event; // 返回碼,0為成功 private String msg; // 返回信息 private T obj; // 單個(gè)對象 private T objList; // 數(shù)組對象 private int currentPage; // 當(dāng)前頁數(shù) private int pageSize; // 每頁顯示數(shù)量 private int maxCount; // 總條數(shù) private int maxPage; // 總頁數(shù) // 構(gòu)造函數(shù),初始化code和msg public ApiResponse(String event, String msg) { this.event = event; this.msg = msg; } // 判斷結(jié)果是否成功 public boolean isSuccess() { return event.equals('0'); } // TODO 所有屬性的getter和setter } 上面4個(gè)接口,URL和appKey都是一樣的,用來區(qū)別不同接口的則是method字段,因此,URL和appKey可以統(tǒng)一定義,method則根據(jù)不同接口定義不同常量。而除去appKey和method,剩下的參數(shù)才是每個(gè)接口需要定義的參數(shù)。因此,對上面4個(gè)接口的定義如下: public interface Api { // 發(fā)送驗(yàn)證碼 public final static String SEND_SMS_CODE = 'service.sendSmsCode4Register'; // 注冊 public final static String REGISTER = 'customer.registerByPhone'; // 登錄 public final static String LOGIN = 'customer.loginByApp'; // 券列表 public final static String LIST_COUPON = 'issue.listNewCoupon'; /** * 發(fā)送驗(yàn)證碼 * * @param phoneNum 手機(jī)號碼 * @return 成功時(shí)返回:{ 'event': '0', 'msg':'success' } */ public ApiResponse<Void> sendSmsCode4Register(String phoneNum); /** * 注冊 * * @param phoneNum 手機(jī)號碼 * @param code 驗(yàn)證碼 * @param password MD5加密的密碼 * @return 成功時(shí)返回:{ 'event': '0', 'msg':'success' } */ public ApiResponse<Void> registerByPhone(String phoneNum, String code, String password); /** * 登錄 * * @param loginName 登錄名(手機(jī)號) * @param password MD5加密的密碼 * @param imei 手機(jī)IMEI串號 * @param loginOS Android為1 * @return 成功時(shí)返回:{ 'event': '0', 'msg':'success' } */ public ApiResponse<Void> loginByApp(String loginName, String password, String imei, int loginOS); /** * 券列表 * * @param currentPage 當(dāng)前頁數(shù) * @param pageSize 每頁顯示數(shù)量 * @return 成功時(shí)返回:{ 'event': '0', 'msg':'success', 'objList':[...] } */ public ApiResponse<List<CouponBO>> listNewCoupon(int currentPage, int pageSize); } Api的實(shí)現(xiàn)類則是ApiImpl了,實(shí)現(xiàn)類需要封裝好請求數(shù)據(jù)并向服務(wù)器發(fā)起請求,并將響應(yīng)結(jié)果的數(shù)據(jù)轉(zhuǎn)為ApiResonse返回。而向服務(wù)器發(fā)送請求并將響應(yīng)結(jié)果返回的處理則封裝到http引擎類去處理。另外,這里引用了gson將json轉(zhuǎn)為對象。ApiImpl的實(shí)現(xiàn)代碼如下: public class ApiImpl implements Api { private final static String APP_KEY = 'ANDROID_KCOUPON'; private final static String TIME_OUT_EVENT = 'CONNECT_TIME_OUT'; private final static String TIME_OUT_EVENT_MSG = '連接服務(wù)器失敗'; // http引擎 private HttpEngine httpEngine; public ApiImpl() { httpEngine = HttpEngine.getInstance(); } @Override public ApiResponse<Void> sendSmsCode4Register(String phoneNum) { Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put('appKey', APP_KEY); paramMap.put('method', SEND_SMS_CODE); paramMap.put('phoneNum', phoneNum); Type type = new TypeToken<ApiResponse<Void>>(){}.getType(); try { return httpEngine.postHandle(paramMap, type); } catch (IOException e) { return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG); } } @Override public ApiResponse<Void> registerByPhone(String phoneNum, String code, String password) { Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put('appKey', APP_KEY); paramMap.put('method', REGISTER); paramMap.put('phoneNum', phoneNum); paramMap.put('code', code); paramMap.put('password', EncryptUtil.makeMD5(password)); Type type = new TypeToken<ApiResponse<List<CouponBO>>>(){}.getType(); try { return httpEngine.postHandle(paramMap, type); } catch (IOException e) { return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG); } } @Override public ApiResponse<Void> loginByApp(String loginName, String password, String imei, int loginOS) { Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put('appKey', APP_KEY); paramMap.put('method', LOGIN); paramMap.put('loginName', loginName); paramMap.put('password', EncryptUtil.makeMD5(password)); paramMap.put('imei', imei); paramMap.put('loginOS', String.valueOf(loginOS)); Type type = new TypeToken<ApiResponse<List<CouponBO>>>(){}.getType(); try { return httpEngine.postHandle(paramMap, type); } catch (IOException e) { return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG); } } @Override public ApiResponse<List<CouponBO>> listNewCoupon(int currentPage, int pageSize) { Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put('appKey', APP_KEY); paramMap.put('method', LIST_COUPON); paramMap.put('currentPage', String.valueOf(currentPage)); paramMap.put('pageSize', String.valueOf(pageSize)); Type type = new TypeToken<ApiResponse<List<CouponBO>>>(){}.getType(); try { return httpEngine.postHandle(paramMap, type); } catch (IOException e) { return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG); } } } 而http引擎類的實(shí)現(xiàn)如下: public class HttpEngine { private final static String SERVER_URL = 'http://uat.b./platform/api'; private final static String REQUEST_MOTHOD = 'POST'; private final static String ENCODE_TYPE = 'UTF-8'; private final static int TIME_OUT = 15000; private static HttpEngine instance = null; private HttpEngine() { } public static HttpEngine getInstance() { if (instance == null) { instance = new HttpEngine(); } return instance; } public <T> T postHandle(Map<String, String> paramsMap, Type typeOfT) throws IOException { String data = joinParams(paramsMap); HttpUrlConnection connection = getConnection(); connection.setRequestProperty('Content-Length', String.valueOf(data.getBytes().length)); connection.connect(); OutputStream os = connection.getOutputStream(); os.write(data.getBytes()); os.flush(); if (connection.getResponseCode() == 200) { // 獲取響應(yīng)的輸入流對象 InputStream is = connection.getInputStream(); // 創(chuàng)建字節(jié)輸出流對象 ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 定義讀取的長度 int len = 0; // 定義緩沖區(qū) byte buffer[] = new byte[1024]; // 按照緩沖區(qū)的大小,循環(huán)讀取 while ((len = is.read(buffer)) != -1) { // 根據(jù)讀取的長度寫入到os對象中 baos.write(buffer, 0, len); } // 釋放資源 is.close(); baos.close(); connection.disconnect(); // 返回字符串 final String result = new String(baos.toByteArray()); Gson gson = new Gson(); return gson.fromJson(result, typeOfT); } else { connection.disconnect(); return null; } } private HttpURLConnection getConnection() { HttpURLConnection connection = null; // 初始化connection try { // 根據(jù)地址創(chuàng)建URL對象 URL url = new URL(SERVER_URL); // 根據(jù)URL對象打開鏈接 connection = (HttpURLConnection) url.openConnection(); // 設(shè)置請求的方式 connection.setRequestMethod(REQUEST_MOTHOD); // 發(fā)送POST請求必須設(shè)置允許輸入,默認(rèn)為true connection.setDoInput(true); // 發(fā)送POST請求必須設(shè)置允許輸出 connection.setDoOutput(true); // 設(shè)置不使用緩存 connection.setUseCaches(false); // 設(shè)置請求的超時(shí)時(shí)間 connection.setReadTimeout(TIME_OUT); connection.setConnectTimeout(TIME_OUT); connection.setRequestProperty('Content-Type', 'application/x-www-form-urlencoded'); connection.setRequestProperty('Connection', 'keep-alive'); connection.setRequestProperty('Response-Type', 'json'); connection.setChunkedStreamingMode(0); } catch (IOException e) { e.printStackTrace(); } return connection; } private String joinParams(Map<String, String> paramsMap) { StringBuilder stringBuilder = new StringBuilder(); for (String key : paramsMap.keySet()) { stringBuilder.append(key); stringBuilder.append('='); try { stringBuilder.append(URLEncoder.encode(paramsMap.get(key), ENCODE_TYPE)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } stringBuilder.append('&'); } return stringBuilder.substring(0, stringBuilder.length() - 1); } } 至此,接口層的封裝就完成了。接下來再往上看看核心層吧。
核心層處于接口層和界面層之間,向下調(diào)用Api,向上提供Action,它的核心任務(wù)就是處理復(fù)雜的業(yè)務(wù)邏輯。先看看我對Action的定義: public interface AppAction { // 發(fā)送手機(jī)驗(yàn)證碼 public void sendSmsCode(String phoneNum, ActionCallbackListener<Void> listener); // 注冊 public void register(String phoneNum, String code, String password, ActionCallbackListener<Void> listener); // 登錄 public void login(String loginName, String password, ActionCallbackListener<Void> listener); // 按分頁獲取券列表 public void listCoupon(int currentPage, ActionCallbackListener<List<CouponBO>> listener); } 首先,和Api接口對比就會發(fā)現(xiàn),參數(shù)并不一致。登錄并沒有iemi和loginOS的參數(shù),獲取券列表的參數(shù)里也少了pageSize。這是因?yàn)椋@幾個(gè)參數(shù),跟界面其實(shí)并沒有直接關(guān)系。Action只要定義好跟界面相關(guān)的就可以了,其他需要的參數(shù),在具體實(shí)現(xiàn)時(shí)再去獲取。 public interface ActionCallbackListener<T> { /** * 成功時(shí)調(diào)用 * * @param data 返回的數(shù)據(jù) */ public void onSuccess(T data); /** * 失敗時(shí)調(diào)用 * * @param errorEvemt 錯(cuò)誤碼 * @param message 錯(cuò)誤信息 */ public void onFailure(String errorEvent, String message); } 接下來再看看Action的實(shí)現(xiàn)。首先,要獲取imei,那就需要傳入一個(gè)Context;另外,還需要loginOS和pageSize,這定義為常量就可以了;還有,要調(diào)用接口層,所以還需要Api實(shí)例。而接口的實(shí)現(xiàn)分為兩步,第一步做參數(shù)檢查,第二步用異步任務(wù)調(diào)用Api。具體實(shí)現(xiàn)如下: public class AppActionImpl implements AppAction { private final static int LOGIN_OS = 1; // 表示Android private final static int PAGE_SIZE = 20; // 默認(rèn)每頁20條 private Context context; private Api api; public AppActionImpl(Context context) { this.context = context; this.api = new ApiImpl(); } @Override public void sendSmsCode(final String phoneNum, final ActionCallbackListener<Void> listener) { // 參數(shù)為空檢查 if (TextUtils.isEmpty(phoneNum)) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_NULL, '手機(jī)號為空'); } return; } // 參數(shù)合法性檢查 Pattern pattern = Pattern.compile('1\\d{10}'); Matcher matcher = pattern.matcher(phoneNum); if (!matcher.matches()) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_ILLEGAL, '手機(jī)號不正確'); } return; } // 請求Api new AsyncTask<Void, Void, ApiResponse<Void>>() { @Override protected ApiResponse<Void> doInBackground(Void... voids) { return api.sendSmsCode4Register(phoneNum); } @Override protected void onPostExecute(ApiResponse<Void> response) { if (listener != null && response != null) { if (response.isSuccess()) { listener.onSuccess(null); } else { listener.onFailure(response.getEvent(), response.getMsg()); } } } }.execute(); } @Override public void register(final String phoneNum, final String code, final String password, final ActionCallbackListener<Void> listener) { // 參數(shù)為空檢查 if (TextUtils.isEmpty(phoneNum)) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_NULL, '手機(jī)號為空'); } return; } if (TextUtils.isEmpty(code)) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_NULL, '驗(yàn)證碼為空'); } return; } if (TextUtils.isEmpty(password)) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_NULL, '密碼為空'); } return; } // 參數(shù)合法性檢查 Pattern pattern = Pattern.compile('1\\d{10}'); Matcher matcher = pattern.matcher(phoneNum); if (!matcher.matches()) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_ILLEGAL, '手機(jī)號不正確'); } return; } // TODO 長度檢查,密碼有效性檢查等 // 請求Api new AsyncTask<Void, Void, ApiResponse<Void>>() { @Override protected ApiResponse<Void> doInBackground(Void... voids) { return api.registerByPhone(phoneNum, code, password); } @Override protected void onPostExecute(ApiResponse<Void> response) { if (listener != null && response != null) { if (response.isSuccess()) { listener.onSuccess(null); } else { listener.onFailure(response.getEvent(), response.getMsg()); } } } }.execute(); } @Override public void login(final String loginName, final String password, final ActionCallbackListener<Void> listener) { // 參數(shù)為空檢查 if (TextUtils.isEmpty(loginName)) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_NULL, '登錄名為空'); } return; } if (TextUtils.isEmpty(password)) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_NULL, '密碼為空'); } return; } // TODO 長度檢查,密碼有效性檢查等 // 請求Api new AsyncTask<Void, Void, ApiResponse<Void>>() { @Override protected ApiResponse<Void> doInBackground(Void... voids) { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); String imei = telephonyManager.getDeviceId(); return api.loginByApp(loginName, password, imei, LOGIN_OS); } @Override protected void onPostExecute(ApiResponse<Void> response) { if (listener != null && response != null) { if (response.isSuccess()) { listener.onSuccess(null); } else { listener.onFailure(response.getEvent(), response.getMsg()); } } } }.execute(); } @Override public void listCoupon(final int currentPage, final ActionCallbackListener<List<CouponBO>> listener) { // 參數(shù)檢查 if (currentPage < 0) { if (listener != null) { listener.onFailure(ErrorEvent.PARAM_ILLEGAL, '當(dāng)前頁數(shù)小于零'); } } // TODO 添加緩存 // 請求Api new AsyncTask<Void, Void, ApiResponse<List<CouponBO>>>() { @Override protected ApiResponse<List<CouponBO>> doInBackground(Void... voids) { return api.listNewCoupon(currentPage, PAGE_SIZE); } @Override protected void onPostExecute(ApiResponse<List<CouponBO>> response) { if (listener != null && response != null) { if (response.isSuccess()) { listener.onSuccess(response.getObjList()); } else { listener.onFailure(response.getEvent(), response.getMsg()); } } } }.execute(); } } 簡單的實(shí)現(xiàn)代碼就是這樣,其實(shí),這還有很多地方可以優(yōu)化,比如,將參數(shù)為空的檢查、手機(jī)號有效性的檢查、數(shù)字型范圍的檢查等等,都可以抽成獨(dú)立的方法,從而減少重復(fù)代碼的編寫。異步任務(wù)里的代碼也一樣,都是可以通過重構(gòu)優(yōu)化的。另外,需要擴(kuò)展時(shí),比如添加緩存,那就在調(diào)用Api之前處理。
在這個(gè)Demo里,只有三個(gè)頁面:登錄頁、注冊頁、券列表頁。在這里,也會遵循界面篇提到的三個(gè)基本原則:規(guī)范性、單一性、簡潔性。 public class KApplication extends Application { private AppAction appAction; @Override public void onCreate() { super.onCreate(); appAction = new AppActionImpl(this); } public AppAction getAppAction() { return appAction; } } 另外,一個(gè)Activity的基類也是很有必要的,可以減少很多重復(fù)的工作?;惖拇a如下: public abstract class KBaseActivity extends FragmentActivity { // 上下文實(shí)例 public Context context; // 應(yīng)用全局的實(shí)例 public KApplication application; // 核心層的Action實(shí)例 public AppAction appAction; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); context = getApplicationContext(); application = (KApplication) this.getApplication(); appAction = application.getAppAction(); } } 再看看登錄的Activity: public class LoginActivity extends KBaseActivity { private EditText phoneEdit; private EditText passwordEdit; private Button loginBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); // 初始化View initViews(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_login, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); // 如果是注冊按鈕 if (id == R.id.action_register) { Intent intent = new Intent(this, RegisterActivity.class); startActivity(intent); return true; } return super.onOptionsItemSelected(item); } // 初始化View private void initViews() { phoneEdit = (EditText) findViewById(R.id.edit_phone); passwordEdit = (EditText) findViewById(R.id.edit_password); loginBtn = (Button) findViewById(R.id.btn_login); } // 準(zhǔn)備登錄 public void toLogin(View view) { String loginName = phoneEdit.getText().toString(); String password = passwordEdit.getText().toString(); loginBtn.setEnabled(false); this.appAction.login(loginName, password, new ActionCallbackListener<Void>() { @Override public void onSuccess(Void data) { Toast.makeText(context, R.string.toast_login_success, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(context, CouponListActivity.class); startActivity(intent); finish(); } @Override public void onFailure(String errorEvent, String message) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); loginBtn.setEnabled(true); } }); } } 登錄頁的布局文件則如下: <LinearLayout xmlns:android='http://schemas./apk/res/android' xmlns:tools='http://schemas./tools' android:layout_width='match_parent' android:layout_height='match_parent' android:orientation='vertical' android:paddingBottom='@dimen/activity_vertical_margin' android:paddingLeft='@dimen/activity_horizontal_margin' android:paddingRight='@dimen/activity_horizontal_margin' android:paddingTop='@dimen/activity_vertical_margin' tools:context='com.keegan.kandroid.activity.LoginActivity'> <EditText android:id='@ id/edit_phone' android:layout_width='match_parent' android:layout_height='wrap_content' android:layout_marginTop='@dimen/edit_vertical_margin' android:layout_marginBottom='@dimen/edit_vertical_margin' android:hint='@string/hint_phone' android:inputType='phone' android:singleLine='true' /> <EditText android:id='@ id/edit_password' android:layout_width='match_parent' android:layout_height='wrap_content' android:layout_marginTop='@dimen/edit_vertical_margin' android:layout_marginBottom='@dimen/edit_vertical_margin' android:hint='@string/hint_password' android:inputType='textPassword' android:singleLine='true' /> <Button android:id='@ id/btn_login' android:layout_width='match_parent' android:layout_height='wrap_content' android:layout_marginTop='@dimen/btn_vertical_margin' android:layout_marginBottom='@dimen/btn_vertical_margin' android:onClick='toLogin' android:text='@string/btn_login' /> </LinearLayout> 可以看到,EditText的id命名統(tǒng)一以edit開頭,而在Activity里的控件變量名則以Edit結(jié)尾。按鈕的onClick也統(tǒng)一用toXXX的方式命名,明確表明這是一個(gè)將要做的動(dòng)作。還有,string,dimen也都統(tǒng)一在相應(yīng)的資源文件里按照相應(yīng)的規(guī)范去定義。 public abstract class KBaseAdapter<T> extends BaseAdapter { protected Context context; protected LayoutInflater inflater; protected List<T> itemList = new ArrayList<T>(); public KBaseAdapter(Context context) { this.context = context; inflater = LayoutInflater.from(context); } /** * 判斷數(shù)據(jù)是否為空 * * @return 為空返回true,不為空返回false */ public boolean isEmpty() { return itemList.isEmpty(); } /** * 在原有的數(shù)據(jù)上添加新數(shù)據(jù) * * @param itemList */ public void addItems(List<T> itemList) { this.itemList.addAll(itemList); notifyDataSetChanged(); } /** * 設(shè)置為新的數(shù)據(jù),舊數(shù)據(jù)會被清空 * * @param itemList */ public void setItems(List<T> itemList) { this.itemList.clear(); this.itemList = itemList; notifyDataSetChanged(); } /** * 清空數(shù)據(jù) */ public void clearItems() { itemList.clear(); notifyDataSetChanged(); } @Override public int getCount() { return itemList.size(); } @Override public Object getItem(int i) { return itemList.get(i); } @Override public long getItemId(int i) { return i; } @Override abstract public View getView(int i, View view, ViewGroup viewGroup); } 這個(gè)抽象基類集成了設(shè)置數(shù)據(jù)的方法,每個(gè)具體的適配器類只要再實(shí)現(xiàn)各自的getView方法就可以了。本Demo的券列表的適配器如下: public class CouponListAdapter extends KBaseAdapter<CouponBO> { public CouponListAdapter(Context context) { super(context); } @Override public View getView(int i, View view, ViewGroup viewGroup) { ViewHolder holder; if (view == null) { view = inflater.inflate(R.layout.item_list_coupon, viewGroup, false); holder = new ViewHolder(); holder.titleText = (TextView) view.findViewById(R.id.text_item_title); holder.infoText = (TextView) view.findViewById(R.id.text_item_info); holder.priceText = (TextView) view.findViewById(R.id.text_item_price); view.setTag(holder); } else { holder = (ViewHolder) view.getTag(); } CouponBO coupon = itemList.get(i); holder.titleText.setText(coupon.getName()); holder.infoText.setText(coupon.getIntroduce()); SpannableString priceString; // 根據(jù)不同的券類型展示不同的價(jià)格顯示方式 switch (coupon.getModelType()) { default: case CouponBO.TYPE_CASH: priceString = CouponPriceUtil.getCashPrice(context, coupon.getFaceValue(), coupon.getEstimateAmount()); break; case CouponBO.TYPE_DEBIT: priceString = CouponPriceUtil.getVoucherPrice(context, coupon.getDebitAmount(), coupon.getMiniAmount()); break; case CouponBO.TYPE_DISCOUNT: priceString = CouponPriceUtil.getDiscountPrice(context, coupon.getDiscount(), coupon.getMiniAmount()); break; } holder.priceText.setText(priceString); return view; } static class ViewHolder { TextView titleText; TextView infoText; TextView priceText; } } 而券列表的Activity簡單實(shí)現(xiàn)如下: public class CouponListActivity extends KBaseActivity implements SwipeRefreshLayout.OnRefreshListener { private SwipeRefreshLayout swipeRefreshLayout; private ListView listView; private CouponListAdapter listAdapter; private int currentPage = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_coupon_list); initViews(); getData(); // TODO 添加上拉加載更多的功能 } private void initViews() { swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout); swipeRefreshLayout.setOnRefreshListener(this); listView = (ListView) findViewById(R.id.list_view); listAdapter = new CouponListAdapter(this); listView.setAdapter(listAdapter); } private void getData() { this.appAction.listCoupon(currentPage, new ActionCallbackListener<List<CouponBO>>() { @Override public void onSuccess(List<CouponBO> data) { if (!data.isEmpty()) { if (currentPage == 1) { // 第一頁 listAdapter.setItems(data); } else { // 分頁數(shù)據(jù) listAdapter.addItems(data); } } swipeRefreshLayout.setRefreshing(false); } @Override public void onFailure(String errorEvent, String message) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); swipeRefreshLayout.setRefreshing(false); } }); } @Override public void onRefresh() { // 需要重置當(dāng)前頁為第一頁,并且清掉數(shù)據(jù) currentPage = 1; listAdapter.clearItems(); getData(); } }
終于寫完了,代碼也終于放上了github,為了讓人更容易理解,因此很多都比較簡單,沒有再進(jìn)行擴(kuò)展。 掃描以下二維碼即可關(guān)注訂閱號。 |
|