项目重构

---恢复内容开始---

项目分为四个层级:模拟层,接口层,核心层,界面层。关系图:

modle

在Studio分别对应四个模块(Module):model,api,core,app;

model:为模拟层,api为接口层,core为核心层,app为界面层

四个模块之间的依赖设置为:model没有任何依赖,接口层依赖模拟层,核心层依赖了模拟层和接口层,界面层依赖楼核心层和模拟层

步骤:

一。创建项目

默认创建了app模块,查看模块的build。gradle,看到:apply pulgin:‘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’

三。建立模块之间的依赖关系:有两种方式

  1.通过右键模块,然后Open Module Settings ,选择模块的Dependencies,点击左下角的➕,选择Module dependency,最后选择要依赖的模块

1

  2.直接在模块的build。gradle设置。打开build。gradle,在最后的dependencies一项里面添加新的一行:compile project(‘:ModuleName’),比如app模块添加对model模块和core模块依赖之后的dependencies:

1
2
3
4
5
6
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:

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

api:

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

core:

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

app:

1
2
3
4
5
6
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个接口:一个发送验证码的接口,一个注册的接口,一个登录的接口,一个获取券列表的接口。

1.发送验证码的接口:

URL:http://uat.b.quancome.com/platform/api
参数: 

 

参数名描述类型
appKey ANDROID_KCOUPON String
method service.sendSmsCode4Register String
phoneNum 手机号码 String

 输出样式: {“event”:“0”,“msg”,“success”}

2.注册接口:

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”}

3.登录接口

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”}

4.券列表接口:

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数据有三种固定结构:

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

 因此可以封装成实体类,代码:

public class ApiResponseT> {
    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 ApiResponseVoid> sendSmsCode4Register(String phoneNum);
 
    /**
     * 注册
     *
     * @param phoneNum 手机号码
     * @param code     验证码
     * @param password MD5加密的密码
     * @return 成功时返回:{ "event": "0", "msg":"success" }
     */
    public ApiResponseVoid> 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 ApiResponseVoid> loginByApp(String loginName, String password, String imei, int loginOS);
 
    /**
     * 券列表
     *
     * @param currentPage 当前页数
     * @param pageSize    每页显示数量
     * @return 成功时返回:{ "event": "0", "msg":"success", "objList":[...] }
     */
    public ApiResponseListCouponBO>> 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 ApiResponseVoid> sendSmsCode4Register(String phoneNum) {
        MapString, String> paramMap = new HashMapString, String>();
        paramMap.put("appKey", APP_KEY);
        paramMap.put("method", SEND_SMS_CODE);
        paramMap.put("phoneNum", phoneNum);
 
        Type type = new TypeTokenApiResponseVoid>>(){}.getType();
        try {
            return httpEngine.postHandle(paramMap, type);
        } catch (IOException e) {
            return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
        }
    }
 
    @Override
    public ApiResponseVoid> registerByPhone(String phoneNum, String code, String password) {
        MapString, String> paramMap = new HashMapString, 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 TypeTokenApiResponseListCouponBO>>>(){}.getType();
        try {
            return httpEngine.postHandle(paramMap, type);
        } catch (IOException e) {
            return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
        }
    }
 
    @Override
    public ApiResponseVoid> loginByApp(String loginName, String password, String imei, int loginOS) {
        MapString, String> paramMap = new HashMapString, 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 TypeTokenApiResponseListCouponBO>>>(){}.getType();
        try {
            return httpEngine.postHandle(paramMap, type);
        } catch (IOException e) {
            return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
        }
    }
 
    @Override
    public ApiResponseListCouponBO>> listNewCoupon(int currentPage, int pageSize) {
        MapString, String> paramMap = new HashMapString, 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 TypeTokenApiResponseListCouponBO>>>(){}.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> T postHandle(MapString, 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) {
            // 获取响应的输入流对象
            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(MapString, 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);
    }
}
 

至此,接口层的封装就完成了。接下来再往上看看核心层吧。

 

六,核心层逻辑

移动层处于接口层和界面层之间,向下调用Api,向上提供Action,,它的核心任务是处理复杂的业务逻辑。先开口问对Action的定义:

public interface AppAction {
    // 发送手机验证码
    public void sendSmsCode(String phoneNum, ActionCallbackListenerVoid> listener);
    // 注册
    public void register(String phoneNum, String code, String password, ActionCallbackListenerVoid> listener);
    // 登录
    public void login(String loginName, String password, ActionCallbackListenerVoid> listener);
    // 按分页获取券列表
    public void listCoupon(int currentPage, ActionCallbackListenerListCouponBO>> listener);
}
首先,和Api接口对比就会发现,参数并不一样,登录并没有iemi喝liginOS的参数,获取券列表的参数也少了pageSize。这是因为,这几个参数,跟界面其实并没有直接关系。Action只要定义好跟界面相关的就可以,其他需要的参数,在具体实现获取。
另外,大部分Action的处理都是异步的,因此,添加了回调监听器ActionCallbackListener,回调监听器的范型则是返回的对象数据类型,例如获取券列表,返回的数据类型就是list,没有对象数据则为Void,回调监听器只定义了成功和失败的方法:
public interface ActionCallbackListenerT> {
    /**
     * 成功时调用
     *
     * @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 ActionCallbackListenerVoid> 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 AsyncTaskVoid, Void, ApiResponseVoid>>() {
            @Override
            protected ApiResponseVoid> doInBackground(Void... voids) {
                return api.sendSmsCode4Register(phoneNum);
            }
 
            @Override
            protected void onPostExecute(ApiResponseVoid> 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, finalActionCallbackListenerVoid> 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 AsyncTaskVoid, Void, ApiResponseVoid>>() {
            @Override
            protected ApiResponseVoid> doInBackground(Void... voids) {
                return api.registerByPhone(phoneNum, code, password);
            }
 
            @Override
            protected void onPostExecute(ApiResponseVoid> 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 ActionCallbackListenerVoid> 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 AsyncTaskVoid, Void, ApiResponseVoid>>() {
            @Override
            protected ApiResponseVoid> 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(ApiResponseVoid> 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 ActionCallbackListenerListCouponBO>> listener) {
        // 参数检查
        if (currentPage  0) {
            if (listener != null) {
                listener.onFailure(ErrorEvent.PARAM_ILLEGAL, "当前页数小于零");
            }
        }
 
        // TODO 添加缓存
 
        // 请求Api
        new AsyncTaskVoid, Void, ApiResponseListCouponBO>>>() {
            @Override
            protected ApiResponseListCouponBO>> doInBackground(Void... voids) {
                return api.listNewCoupon(currentPage, PAGE_SIZE);
            }
 
            @Override
            protected void onPostExecute(ApiResponseListCouponBO>> response) {
                if (listener != null & response != null) {
                    if (response.isSuccess()) {
                        listener.onSuccess(response.getObjList());
                    } else {
                        listener.onFailure(response.getEvent(), response.getMsg());
                    }
                }
            }
        }.execute();
    }
 
 

 七,界面层

在这个Demo中,只要3个页面:登录页面,注册页,券列表页面;也会遵循3个基本原则:规范性,单一性,简洁性。
首先页面需要调用核心层Acition,而这会在整个应用级别都会用到,因此,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;
    }
}
另外,一个Action的基类也是有必要的,基类代码如下:
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 ActionCallbackListenerVoid>() {
            @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);
            }
        });
    }
}
 
Login——Layout:
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/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都是edit开头,而在Activity里面的空间变量名则以Edit结尾。按钮的onClick也统一用toXXX的命名方式,明确表明这是一个将要做的动作。还有String,dimen也都是统一在相应的资源文件中按照相应的规范去定义。
注册和登录差不多,略过。
券列表页面用到了ListView,下面是Adapter类:
public abstract class KBaseAdapterT> extends BaseAdapter {
 
    protected Context context;
    protected LayoutInflater inflater;
    protected ListT> itemList = new ArrayListT>();
 
    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(ListT> itemList) {
        this.itemList.addAll(itemList);
        notifyDataSetChanged();
    }
 
    /**
     * 设置为新的数据,旧数据会被清空
     *
     * @param itemList
     */
    public void setItems(ListT> 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 KBaseAdapterCouponBO> {
 
    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 ActionCallbackListenerListCouponBO>>() {
            @Override
            public void onSuccess(ListCouponBO> 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();
    }
}
 
 
 
 

 

posted @ 2015-11-20 16:13  冷月舞痕  阅读(365)  评论(0编辑  收藏  举报