編輯:關於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 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();
}
}
解決Android Studio導入項目非常慢的辦法
前言大家都知道Android Studio目前已經更新到2.0 Preview 6了,作為Google大力推崇的開發工具,相對於Eclipse ADT有著不可比擬的優勢。
Android字體大小怎麼自適應不同分辨率?
今天有人問我,android系統不同分辨率,不同大小的手機,字體大小怎麼去適應呢?其實字體的適應和圖片的適應是一個道理的。 一、 原理如下: 假設需要適應320x240,
自定義ImageLoader
先上幾張效果圖:在加載多圖片時,我們采用後進先出策略(即滑動到哪裡就先加載哪裡的圖片),節省了內存的使用,也有了更好的用戶體驗。接著我們就先定義自己的ImageLoade
Android中設置RadioButton在文字右邊的方法實例
<?xml version=1.0 encoding=utf-8?> <LinearLayout xmlns:android=http: