Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android項目重構之路:實現篇分析

Android項目重構之路:實現篇分析

編輯:關於Android編程

項目搭建

根據架構篇所講的,將項目分為了四個層級:模型層、接口層、核心層、界面層。四個層級之間的關系如下圖所示:

\

實現上,在Android Studio分為了相應的四個模塊(Module):model、api、core、app
model為模型層,api為接口層,core為核心層,app為界面層。
model、api、core這三個模塊的類型為library,app模塊的類型為application。
四個模塊之間的依賴設置為:model沒有任何依賴,接口層依賴了模型層,核心層依賴了模型層和接口層,界面層依賴了核心層和模型層。
項目搭建的步驟如下:

創建新項目,項目名稱為KAndroid,包名為com.keegan.kandroid。默認已創建了app模塊,查看下app模塊下的build.gradle,會看到第一行為:

apply plugin: 'com.android.application'
 

這行表明了app模塊是application類型的。

分別新建模塊model、api、core,Module Type都選為Android Library,在Add an activity to module頁面選擇Add No Activity,這三個模塊做為庫使用,並不需要界面。創建完之後,查看相應模塊的build.gradle,會看到第一行為:

apply plugin: 'com.android.library'
 

建立模塊之間的依賴關系。有兩種方法可以設置:
第一種:通過右鍵模塊,然後Open Module Settings,選擇模塊的Dependencies,點擊左下方的加號,選擇Module dependency,最後選擇要依賴的模塊,下圖為api模塊添加了model依賴;

\

第二種:直接在模塊的build.gradle設置。打開build.gradle,在最後的dependencies一項裡面添加新的一行:compile project(':ModuleName'),比如app模塊添加對model模塊和core模塊依賴之後的dependencies如下:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile project(':model')
    compile project(':core')
}
 

通過上面兩種方式的任意一種,創建了模塊之間的依賴關系之後,每個模塊的build.gradle的dependencies項的結果將會如下:
model:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
}    
 

api:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile project(':model')
}    
 

core:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile project(':model')
    compile project(':api')
}    
 

app:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile project(':model')
    compile project(':core')
}    
 

創建業務對象模型

業務對象模型統一存放於model模塊,是對業務數據的封裝,大部分都是從接口傳過來的對象,因此,其屬性也與接口傳回的對象屬性相一致。在這個Demo裡,只有一個業務對象模型,封裝了券的基本信息,以下是該實體類的代碼:

/**
 * 券的業務模型類,封裝了券的基本信息。
 * 券分為了三種類型:現金券、抵扣券、折扣券。
 * 現金券是擁有固定面值的券,有固定的售價;
 * 抵扣券是滿足一定金額後可以抵扣的券,比如滿100減10元;
 * 折扣券是可以打折的券。
 *
 * @version 1.0 創建時間: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為現金券,2為抵扣券,3為折扣券
    private double faceValue;      // 現金券的面值
    private double estimateAmount; // 現金券的售價
    private double debitAmount;    // 抵扣券的抵扣金額
    private double discount;       // 折扣券的折扣率(0-100)
    private double miniAmount;     // 抵扣券和折扣券的最小使用金額

    // TODO 所有屬性的getter和setter
}
 

接口層的封裝

在這個Demo裡,提供了4個接口:一個發送驗證碼的接口、一個注冊接口、一個登錄接口、一個獲取券列表的接口。這4個接口具體如下:

  1. 發送驗證碼接口
    URL:http://uat.b.quancome.com/platform/api
    參數:

    參數名 描述 類型 appKey ANDROID_KCOUPON String method service.sendSmsCode4Register String phoneNum 手機號碼 String

    輸出樣例:

    { "event": "0", "msg": "success" }
    
     

    注冊接口
    URL:http://uat.b.quancome.com/platform/api
    參數:

    參數名 描述 類型 appKey ANDROID_KCOUPON String method customer.registerByPhone String phoneNum 手機號碼 String code 驗證碼 String password MD5加密密碼 String

    輸出樣例:

    { "event": "0", "msg": "success" }
    
     

    登錄接口
    URL:http://uat.b.quancome.com/platform/api
    其他參數:

    參數名 描述 類型 appKey ANDROID_KCOUPON String method customer.loginByApp String loginName 登錄名(手機號) String password MD5加密密碼 String imei 手機imei串號 String loginOS 系統,android為1 int

    輸出樣例:

    { "event": "0", "msg": "success" }
    
     

    券列表
    URL:http://uat.b.quancome.com/platform/api
    其他參數:

    參數名 描述 類型 appKey ANDROID_KCOUPON String method issue.listNewCoupon String currentPage 當前頁數 int pageSize 每頁顯示數量 int

    輸出樣例:

    { "event": "0", "msg": "success", "maxCount": 125, "maxPage": 7, "currentPage": 1, "pageSize": 20, "objList":[
        {"id": 1, "name": "測試現金券", "modelType": 1, ...}, 
        {...}, 
        ...
    ]}
    
     

    在架構篇已經講過,接口返回的json數據有三種固定結構:

    {"event": "0", "msg": "success"}
    {"event": "0", "msg": "success", "obj":{...}}
    {"event": "0", "msg": "success", "objList":[{...}, {...}], "currentPage": 1, "pageSize": 20, "maxCount": 2, "maxPage": 1}
    
     

    因此可以封裝成實體類,代碼如下:

    public class ApiResponse {
        private String event;    // 返回碼,0為成功
        private String msg;      // 返回信息
        private T obj;           // 單個對象
        private T objList;       // 數組對象
        private int currentPage; // 當前頁數
        private int pageSize;    // 每頁顯示數量
        private int maxCount;    // 總條數
        private int maxPage;     // 總頁數
    
        // 構造函數,初始化code和msg
        public ApiResponse(String event, String msg) {
            this.event = event;
            this.msg = msg;
        }
    
        // 判斷結果是否成功
        public boolean isSuccess() {
            return event.equals("0");
        }
    
        // TODO 所有屬性的getter和setter
    }
    
     

    上面4個接口,URL和appKey都是一樣的,用來區別不同接口的則是method字段,因此,URL和appKey可以統一定義,method則根據不同接口定義不同常量。而除去appKey和method,剩下的參數才是每個接口需要定義的參數。因此,對上面4個接口的定義如下:

    public interface Api {
        // 發送驗證碼
        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";
    
        /**
         * 發送驗證碼
         *
         * @param phoneNum 手機號碼
         * @return 成功時返回:{ "event": "0", "msg":"success" }
         */
        public ApiResponse sendSmsCode4Register(String phoneNum);
    
        /**
         * 注冊
         *
         * @param phoneNum 手機號碼
         * @param code     驗證碼
         * @param password MD5加密的密碼
         * @return 成功時返回:{ "event": "0", "msg":"success" }
         */
        public ApiResponse registerByPhone(String phoneNum, String code, String password);
    
        /**
         * 登錄
         *
         * @param loginName 登錄名(手機號)
         * @param password  MD5加密的密碼
         * @param imei      手機IMEI串號
         * @param loginOS   Android為1
         * @return 成功時返回:{ "event": "0", "msg":"success" }
         */
        public ApiResponse loginByApp(String loginName, String password, String imei, int loginOS);
    
        /**
         * 券列表
         *
         * @param currentPage 當前頁數
         * @param pageSize    每頁顯示數量
         * @return 成功時返回:{ "event": "0", "msg":"success", "objList":[...] }
         */
        public ApiResponse> listNewCoupon(int currentPage, int pageSize);
    }
    
     

    Api的實現類則是ApiImpl了,實現類需要封裝好請求數據並向服務器發起請求,並將響應結果的數據轉為ApiResonse返回。而向服務器發送請求並將響應結果返回的處理則封裝到http引擎類去處理。另外,這裡引用了gson將json轉為對象。ApiImpl的實現代碼如下:

    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 = "連接服務器失敗";
        // http引擎
        private HttpEngine httpEngine;
    
        public ApiImpl() {
            httpEngine = HttpEngine.getInstance();
        }
    
        @Override
        public ApiResponse sendSmsCode4Register(String phoneNum) {
            Map paramMap = new HashMap();
            paramMap.put("appKey", APP_KEY);
            paramMap.put("method", SEND_SMS_CODE);
            paramMap.put("phoneNum", phoneNum);
    
            Type type = new TypeToken>(){}.getType();
            try {
                return httpEngine.postHandle(paramMap, type);
            } catch (IOException e) {
                return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
            }
        }
    
        @Override
        public ApiResponse registerByPhone(String phoneNum, String code, String password) {
            Map paramMap = new HashMap();
            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>>(){}.getType();
            try {
                return httpEngine.postHandle(paramMap, type);
            } catch (IOException e) {
                return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
            }
        }
    
        @Override
        public ApiResponse loginByApp(String loginName, String password, String imei, int loginOS) {
            Map paramMap = new HashMap();
            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>>(){}.getType();
            try {
                return httpEngine.postHandle(paramMap, type);
            } catch (IOException e) {
                return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
            }
        }
    
        @Override
        public ApiResponse> listNewCoupon(int currentPage, int pageSize) {
            Map paramMap = new HashMap();
            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>>(){}.getType();
            try {
                return httpEngine.postHandle(paramMap, type);
            } catch (IOException e) {
                return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
            }
        }
    
    }
    
     

    而http引擎類的實現如下:

    public class HttpEngine {
        private final static String SERVER_URL = "http://uat.b.quancome.com/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 postHandle(Map 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) {
                // 獲取響應的輸入流對象
                InputStream is = connection.getInputStream();
                // 創建字節輸出流對象
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // 定義讀取的長度
                int len = 0;
                // 定義緩沖區
                byte buffer[] = new byte[1024];
                // 按照緩沖區的大小,循環讀取
                while ((len = is.read(buffer)) != -1) {
                    // 根據讀取的長度寫入到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 {
                // 根據地址創建URL對象
                URL url = new URL(SERVER_URL);
                // 根據URL對象打開鏈接
                connection = (HttpURLConnection) url.openConnection();
                // 設置請求的方式
                connection.setRequestMethod(REQUEST_MOTHOD);
                // 發送POST請求必須設置允許輸入,默認為true
                connection.setDoInput(true);
                // 發送POST請求必須設置允許輸出
                connection.setDoOutput(true);
                // 設置不使用緩存
                connection.setUseCaches(false);
                // 設置請求的超時時間
                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 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);
        }
    }
    
     

    至此,接口層的封裝就完成了。接下來再往上看看核心層吧。

    核心層的邏輯

    核心層處於接口層和界面層之間,向下調用Api,向上提供Action,它的核心任務就是處理復雜的業務邏輯。先看看我對Action的定義:

    public interface AppAction {
        // 發送手機驗證碼
        public void sendSmsCode(String phoneNum, ActionCallbackListener listener);
        // 注冊
        public void register(String phoneNum, String code, String password, ActionCallbackListener listener);
        // 登錄
        public void login(String loginName, String password, ActionCallbackListener listener);
        // 按分頁獲取券列表
        public void listCoupon(int currentPage, ActionCallbackListener> listener);
    }
    
     

    首先,和Api接口對比就會發現,參數並不一致。登錄並沒有iemi和loginOS的參數,獲取券列表的參數裡也少了pageSize。這是因為,這幾個參數,跟界面其實並沒有直接關系。Action只要定義好跟界面相關的就可以了,其他需要的參數,在具體實現時再去獲取。
    另外,大部分action的處理都是異步的,因此,添加了回調監聽器ActionCallbackListener,回調監聽器的泛型則是返回的對象數據類型,例如獲取券列表,返回的數據類型就是List,沒有對象數據時則為Void。回調監聽器只定義了成功和失敗的方法,如下:

    public interface ActionCallbackListener {
        /**
         * 成功時調用
         *
         * @param data 返回的數據
         */
        public void onSuccess(T data);
    
        /**
         * 失敗時調用
         *
         * @param errorEvemt 錯誤碼
         * @param message    錯誤信息
         */
        public void onFailure(String errorEvent, String message);
    }
    
     

    接下來再看看Action的實現。首先,要獲取imei,那就需要傳入一個Context;另外,還需要loginOS和pageSize,這定義為常量就可以了;還有,要調用接口層,所以還需要Api實例。而接口的實現分為兩步,第一步做參數檢查,第二步用異步任務調用Api。具體實現如下:

    public class AppActionImpl implements AppAction {
        private final static int LOGIN_OS = 1; // 表示Android
        private final static int PAGE_SIZE = 20; // 默認每頁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 listener) {
            // 參數為空檢查
            if (TextUtils.isEmpty(phoneNum)) {
                if (listener != null) {
                    listener.onFailure(ErrorEvent.PARAM_NULL, "手機號為空");
                }
                return;
            }
            // 參數合法性檢查
            Pattern pattern = Pattern.compile("1\\d{10}");
            Matcher matcher = pattern.matcher(phoneNum);
            if (!matcher.matches()) {
                if (listener != null) {
                    listener.onFailure(ErrorEvent.PARAM_ILLEGAL, "手機號不正確");
                }
                return;
            }
    
            // 請求Api
            new AsyncTask>() {
                @Override
                protected ApiResponse doInBackground(Void... voids) {
                    return api.sendSmsCode4Register(phoneNum);
                }
    
                @Override
                protected void onPostExecute(ApiResponse 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 listener) {
            // 參數為空檢查
            if (TextUtils.isEmpty(phoneNum)) {
                if (listener != null) {
                    listener.onFailure(ErrorEvent.PARAM_NULL, "手機號為空");
                }
                return;
            }
            if (TextUtils.isEmpty(code)) {
                if (listener != null) {
                    listener.onFailure(ErrorEvent.PARAM_NULL, "驗證碼為空");
                }
                return;
            }
            if (TextUtils.isEmpty(password)) {
                if (listener != null) {
                    listener.onFailure(ErrorEvent.PARAM_NULL, "密碼為空");
                }
                return;
            }
    
            // 參數合法性檢查
            Pattern pattern = Pattern.compile("1\\d{10}");
            Matcher matcher = pattern.matcher(phoneNum);
            if (!matcher.matches()) {
                if (listener != null) {
                    listener.onFailure(ErrorEvent.PARAM_ILLEGAL, "手機號不正確");
                }
                return;
            }
    
            // TODO 長度檢查,密碼有效性檢查等
    
            // 請求Api
            new AsyncTask>() {
                @Override
                protected ApiResponse doInBackground(Void... voids) {
                    return api.registerByPhone(phoneNum, code, password);
                }
    
                @Override
                protected void onPostExecute(ApiResponse 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 listener) {
            // 參數為空檢查
            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>() {
                @Override
                protected ApiResponse 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 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> listener) {
            // 參數檢查
            if (currentPage < 0) {
                if (listener != null) {
                    listener.onFailure(ErrorEvent.PARAM_ILLEGAL, "當前頁數小於零");
                }
            }
    
            // TODO 添加緩存
    
            // 請求Api
            new AsyncTask>>() {
                @Override
                protected ApiResponse> doInBackground(Void... voids) {
                    return api.listNewCoupon(currentPage, PAGE_SIZE);
                }
    
                @Override
                protected void onPostExecute(ApiResponse> response) {
                    if (listener != null && response != null) {
                        if (response.isSuccess()) {
                            listener.onSuccess(response.getObjList());
                        } else {
                            listener.onFailure(response.getEvent(), response.getMsg());
                        }
                    }
                }
            }.execute();
        }
    }
    
     

    簡單的實現代碼就是這樣,其實,這還有很多地方可以優化,比如,將參數為空的檢查、手機號有效性的檢查、數字型范圍的檢查等等,都可以抽成獨立的方法,從而減少重復代碼的編寫。異步任務裡的代碼也一樣,都是可以通過重構優化的。另外,需要擴展時,比如添加緩存,那就在調用Api之前處理。
    核心層的邏輯就是這樣了。最後就到界面層了。

    界面層

    在這個Demo裡,只有三個頁面:登錄頁、注冊頁、券列表頁。在這裡,也會遵循界面篇提到的三個基本原則:規范性、單一性、簡潔性。
    首先,界面層需要調用核心層的Action,而這會在整個應用級別都用到,因此,Action的實例最好放在Application裡。代碼如下:

    public class KApplication extends Application {
    
        private AppAction appAction;
    
        @Override
        public void onCreate() {
            super.onCreate();
            appAction = new AppActionImpl(this);
        }
    
        public AppAction getAppAction() {
            return appAction;
        }
    }
    
     

    另外,一個Activity的基類也是很有必要的,可以減少很多重復的工作。基類的代碼如下:

    public abstract class KBaseActivity extends FragmentActivity {
        // 上下文實例
        public Context context;
        // 應用全局的實例
        public KApplication application;
        // 核心層的Action實例
        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);
        }
    
        // 准備登錄
        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() {
                @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);
                }
            });
        }
    }
    
     

    登錄頁的布局文件則如下:

    
    
        
    
        
    
    
     
    

    可以看到,EditText的id命名統一以edit開頭,而在Activity裡的控件變量名則以Edit結尾。按鈕的onClick也統一用toXXX的方式命名,明確表明這是一個將要做的動作。還有,string,dimen也都統一在相應的資源文件裡按照相應的規范去定義。
    注冊頁和登陸頁差不多,這裡就不展示代碼了。主要再看看券列表頁,因為用到了ListView,ListView需要添加適配器。實際上,適配器很多代碼都是可以復用的,因此,我抽象了一個適配器的基類,代碼如下:

    public abstract class KBaseAdapter extends BaseAdapter {
    
        protected Context context;
        protected LayoutInflater inflater;
        protected List itemList = new ArrayList();
    
        public KBaseAdapter(Context context) {
            this.context = context;
            inflater = LayoutInflater.from(context);
        }
    
        /**
         * 判斷數據是否為空
         *
         * @return 為空返回true,不為空返回false
         */
        public boolean isEmpty() {
            return itemList.isEmpty();
        }
    
        /**
         * 在原有的數據上添加新數據
         *
         * @param itemList
         */
        public void addItems(List itemList) {
            this.itemList.addAll(itemList);
            notifyDataSetChanged();
        }
    
        /**
         * 設置為新的數據,舊數據會被清空
         *
         * @param itemList
         */
        public void setItems(List itemList) {
            this.itemList.clear();
            this.itemList = itemList;
            notifyDataSetChanged();
        }
    
        /**
         * 清空數據
         */
        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);
    }
    
     

    這個抽象基類集成了設置數據的方法,每個具體的適配器類只要再實現各自的getView方法就可以了。本Demo的券列表的適配器如下:

    public class CouponListAdapter extends KBaseAdapter {
    
        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;
            // 根據不同的券類型展示不同的價格顯示方式
            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簡單實現如下:

    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>() {
                @Override
                public void onSuccess(List data) {
                    if (!data.isEmpty()) {
                        if (currentPage == 1) { // 第一頁
                            listAdapter.setItems(data);
                        } else { // 分頁數據
                            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() {
            // 需要重置當前頁為第一頁,並且清掉數據
            currentPage = 1;
            listAdapter.clearItems();
            getData();
        }
    }
       
    1. 上一頁:
    2. 下一頁:
    熱門文章
    閱讀排行版
    Copyright © Android教程網 All Rights Reserved