編輯:關於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個接口具體如下:
發送驗證碼接口
URL:http://uat.b.quancome.com/platform/api
參數:
輸出樣例:
{ "event": "0", "msg": "success" }
注冊接口
URL:http://uat.b.quancome.com/platform/api
參數:
輸出樣例:
{ "event": "0", "msg": "success" }
登錄接口
URL:http://uat.b.quancome.com/platform/api
其他參數:
輸出樣例:
{ "event": "0", "msg": "success" }
券列表
URL:http://uat.b.quancome.com/platform/api
其他參數:
輸出樣例:
{ "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 ApiResponsesendSmsCode4Register(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 ApiResponsesendSmsCode4Register(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(); } }
前言大家都知道Android Studio目前已經更新到2.0 Preview 6了,作為Google大力推崇的開發工具,相對於Eclipse ADT有著不可比擬的優勢。
今天有人問我,android系統不同分辨率,不同大小的手機,字體大小怎麼去適應呢?其實字體的適應和圖片的適應是一個道理的。 一、 原理如下: 假設需要適應320x240,
先上幾張效果圖:在加載多圖片時,我們采用後進先出策略(即滑動到哪裡就先加載哪裡的圖片),節省了內存的使用,也有了更好的用戶體驗。接著我們就先定義自己的ImageLoade
<?xml version=1.0 encoding=utf-8?> <LinearLayout xmlns:android=http: