Repository: keeganlee/kandroid Branch: master Commit: b5d7a52353af Files: 75 Total size: 123.4 KB Directory structure: gitextract_wub9ezo5/ ├── .gitignore ├── .idea/ │ ├── .name │ ├── compiler.xml │ ├── copyright/ │ │ ├── Copyright_Header.xml │ │ └── profiles_settings.xml │ ├── dictionaries/ │ │ └── keegan.xml │ ├── gradle.xml │ ├── inspectionProfiles/ │ │ ├── Project_Default.xml │ │ └── profiles_settings.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── KAndroid.iml ├── README.md ├── api/ │ ├── .gitignore │ ├── api.iml │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── keeganlee/ │ │ └── kandroid/ │ │ └── api/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── me/ │ │ └── keeganlee/ │ │ └── kandroid/ │ │ └── api/ │ │ ├── Api.java │ │ ├── ApiImpl.java │ │ ├── ApiResponse.java │ │ ├── net/ │ │ │ └── HttpEngine.java │ │ └── utils/ │ │ └── EncryptUtil.java │ └── res/ │ └── values/ │ └── strings.xml ├── app/ │ ├── .gitignore │ ├── app.iml │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── keeganlee/ │ │ └── kandroid/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── me/ │ │ └── keeganlee/ │ │ └── kandroid/ │ │ ├── KApplication.java │ │ ├── activity/ │ │ │ ├── CouponListActivity.java │ │ │ ├── KBaseActivity.java │ │ │ ├── LoginActivity.java │ │ │ └── RegisterActivity.java │ │ ├── adapter/ │ │ │ ├── CouponListAdapter.java │ │ │ └── KBaseAdapter.java │ │ └── util/ │ │ └── CouponPriceUtil.java │ └── res/ │ ├── layout/ │ │ ├── activity_coupon_list.xml │ │ ├── activity_login.xml │ │ ├── activity_register.xml │ │ └── item_list_coupon.xml │ ├── values/ │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── core/ │ ├── .gitignore │ ├── build.gradle │ ├── core.iml │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── keeganlee/ │ │ └── kandroid/ │ │ └── core/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── me/ │ │ └── keeganlee/ │ │ └── kandroid/ │ │ └── core/ │ │ ├── ActionCallbackListener.java │ │ ├── AppAction.java │ │ ├── AppActionImpl.java │ │ └── ErrorEvent.java │ └── res/ │ └── values/ │ └── strings.xml ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── model/ │ ├── .gitignore │ ├── build.gradle │ ├── model.iml │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── keeganlee/ │ │ └── kandroid/ │ │ └── model/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── me/ │ │ └── keeganlee/ │ │ └── kandroid/ │ │ └── model/ │ │ └── CouponBO.java │ └── res/ │ └── values/ │ └── strings.xml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: .idea/.name ================================================ KAndroid ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/Copyright_Header.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/dictionaries/keegan.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/inspectionProfiles/Project_Default.xml ================================================ ================================================ FILE: .idea/inspectionProfiles/profiles_settings.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ Java Security issuesJava Serialization issuesJava SerializableHasSerialVersionUIDField ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: KAndroid.iml ================================================ ================================================ FILE: README.md ================================================ # kandroid KAndroid是一个Android的简单的架构搭建的学习项目。架构上分为了四个层级:模型层、接口层、核心层和应用层。 四个层级对应的module为:model、api、core和app。 详细的内容可查看博客文章: [Android项目重构之路:架构篇](http://keeganlee.me/post/android/20150605) [Android项目重构之路:界面篇](http://keeganlee.me/post/android/20150619) [Android项目重构之路:实现篇](http://keeganlee.me/post/android/20150629) ================================================ FILE: api/.gitignore ================================================ /build ================================================ FILE: api/api.iml ================================================ ================================================ FILE: api/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion 22 buildToolsVersion "22.0.0" defaultConfig { minSdkVersion 11 targetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile 'com.google.code.gson:gson:2.3' compile project(':model') } ================================================ FILE: api/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/keegan/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: api/src/androidTest/java/me/keeganlee/kandroid/api/ApplicationTest.java ================================================ package me.keeganlee.kandroid.api; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: api/src/main/AndroidManifest.xml ================================================ ================================================ FILE: api/src/main/java/me/keeganlee/kandroid/api/Api.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.api; import me.keeganlee.kandroid.model.CouponBO; import java.util.List; /** * Api接口 * * @author Keegan小钢 * @date 15/6/21 * @version 1.0 */ 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 成功时返回:{ "code": 0, "msg":"success" } */ public ApiResponse sendSmsCode4Register(String phoneNum); /** * 注册 * * @param phoneNum 手机号码 * @param code 验证码 * @param password MD5加密的密码 * @return 成功时返回:{ "code": 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 成功时返回:{ "code": 0, "msg":"success" } */ public ApiResponse loginByApp(String loginName, String password, String imei, int loginOS); /** * 券列表 * * @param currentPage 当前页数 * @param pageSize 每页显示数量 * @return 成功时返回:{ "code": 0, "msg":"success", "objList":[...] } */ public ApiResponse> listNewCoupon(int currentPage, int pageSize); } ================================================ FILE: api/src/main/java/me/keeganlee/kandroid/api/ApiImpl.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.api; import com.google.gson.reflect.TypeToken; import me.keeganlee.kandroid.api.net.HttpEngine; import me.keeganlee.kandroid.api.utils.EncryptUtil; import me.keeganlee.kandroid.model.CouponBO; import java.io.IOException; import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Api实现类 * * @author Keegan小钢 * @date 15/6/21 * @version 1.0 */ 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 = "连接服务器失败"; 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); } } } ================================================ FILE: api/src/main/java/me/keeganlee/kandroid/api/ApiResponse.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.api; /** * Api响应结果的封装类. * * @author Keegan小钢 * @date 15/6/21 * @version 1.0 */ 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; // 总页数 public ApiResponse(String event, String msg) { this.event = event; this.msg = msg; } public boolean isSuccess() { return event.equals("0"); } public String getEvent() { return event; } public void setEvent(String event) { this.event = event; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } public T getObjList() { return objList; } public void setObjList(T objList) { this.objList = objList; } public int getCurrentPage() { return currentPage; } public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getMaxCount() { return maxCount; } public void setMaxCount(int maxCount) { this.maxCount = maxCount; } public int getMaxPage() { return maxPage; } public void setMaxPage(int maxPage) { this.maxPage = maxPage; } } ================================================ FILE: api/src/main/java/me/keeganlee/kandroid/api/net/HttpEngine.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.api.net; import android.util.Log; import com.google.gson.Gson; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.Map; /** * Http引擎处理类 * * @author Keegan小钢 * @date 15/6/21 * @version 1.0 */ public class HttpEngine { private final static String TAG = "HttpEngine"; private final static String SERVER_URL = "http://domain.com/platform/api"; private final static String REQUEST_MOTHOD = "POST"; private final static String ENCODE_TYPE = "UTF-8"; private final static int TIME_OUT = 20000; 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); // 打印出请求 Log.i(TAG, "request: " + data); 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()); // 打印出结果 Log.i(TAG, "response: " + result); Gson gson = new Gson(); return gson.fromJson(result, typeOfT); } else { connection.disconnect(); return null; } } // 获取connection 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); } } ================================================ FILE: api/src/main/java/me/keeganlee/kandroid/api/utils/EncryptUtil.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.api.utils; import java.math.BigInteger; import java.security.MessageDigest; /** * 加密工具类 * * @author Keegan小钢 * @date 15/6/21 * @version 1.0 */ public class EncryptUtil { // MD5加密 public static String makeMD5(String password) { try { // 生成一个MD5加密计算摘要 MessageDigest md = MessageDigest.getInstance("MD5"); // 计算md5函数 md.update(password.getBytes()); // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符 // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值 return new BigInteger(1, md.digest()).toString(16); } catch (Exception e) { e.printStackTrace(); } return password; } } ================================================ FILE: api/src/main/res/values/strings.xml ================================================ api ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/app.iml ================================================ ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 22 buildToolsVersion "22.0.0" defaultConfig { applicationId "me.keeganlee.kandroid" minSdkVersion 14 targetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':model') compile project(':core') compile 'com.android.support:appcompat-v7:22.0.0' compile 'com.google.android.gms:play-services:6.1.71' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/keegan/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: app/src/androidTest/java/me/keeganlee/kandroid/ApplicationTest.java ================================================ package me.keeganlee.kandroid; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/me/keeganlee/kandroid/KApplication.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid; import android.app.Application; import me.keeganlee.kandroid.core.AppAction; import me.keeganlee.kandroid.core.AppActionImpl; /** * Application类,应用级别的操作都放这里 * * @version 1.0 创建时间:15/6/25 */ public class KApplication extends Application { private AppAction appAction; @Override public void onCreate() { super.onCreate(); appAction = new AppActionImpl(this); } public AppAction getAppAction() { return appAction; } } ================================================ FILE: app/src/main/java/me/keeganlee/kandroid/activity/CouponListActivity.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.activity; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.widget.ListView; import android.widget.Toast; import me.keeganlee.kandroid.R; import me.keeganlee.kandroid.adapter.CouponListAdapter; import me.keeganlee.kandroid.core.ActionCallbackListener; import me.keeganlee.kandroid.model.CouponBO; import java.util.List; /** * 券列表 * * @version 1.0 创建时间:15/6/28 */ 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(); } } ================================================ FILE: app/src/main/java/me/keeganlee/kandroid/activity/KBaseActivity.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import me.keeganlee.kandroid.KApplication; import me.keeganlee.kandroid.core.AppAction; /** * Activity抽象基类 * * @version 1.0 创建时间:15/6/26 */ 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(); } } ================================================ FILE: app/src/main/java/me/keeganlee/kandroid/activity/LoginActivity.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import me.keeganlee.kandroid.R; import me.keeganlee.kandroid.core.ActionCallbackListener; /** * 登录 * * @version 1.0 创建时间:15/6/26 */ 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(); } // 初始化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); } }); } // 进入注册页 public void toRegister(View view) { Intent intent = new Intent(this, RegisterActivity.class); startActivity(intent); } } ================================================ FILE: app/src/main/java/me/keeganlee/kandroid/activity/RegisterActivity.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.activity; import android.content.Intent; import android.os.Bundle; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import me.keeganlee.kandroid.R; import me.keeganlee.kandroid.core.ActionCallbackListener; /** * 注册 * * @version 1.0 创建时间:15/6/26 */ public class RegisterActivity extends KBaseActivity { private EditText phoneEdit; private EditText codeEdit; private EditText passwordEdit; private Button sendCodeBtn; private Button registerBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_register); getActionBar().setDisplayHomeAsUpEnabled(true); initViews(); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == android.R.id.home) { finish(); } return super.onOptionsItemSelected(item); } private void initViews() { phoneEdit = (EditText) findViewById(R.id.edit_phone); codeEdit = (EditText) findViewById(R.id.edit_code); passwordEdit = (EditText) findViewById(R.id.edit_password); sendCodeBtn = (Button) findViewById(R.id.btn_send_code); registerBtn = (Button) findViewById(R.id.btn_register); } // 准备发送验证码 public void toSendCode(View view) { String phoneNum = phoneEdit.getText().toString(); sendCodeBtn.setEnabled(false); this.appAction.sendSmsCode(phoneNum, new ActionCallbackListener() { @Override public void onSuccess(Void data) { Toast.makeText(context, R.string.toast_code_has_sent, Toast.LENGTH_SHORT).show(); sendCodeBtn.setEnabled(true); } @Override public void onFailure(String errorEvent, String message) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); sendCodeBtn.setEnabled(true); } }); } // 准备注册 public void toRegister(View view) { String phoneNum = phoneEdit.getText().toString(); String code = codeEdit.getText().toString(); String password = passwordEdit.getText().toString(); registerBtn.setEnabled(false); this.appAction.register(phoneNum, code, password, new ActionCallbackListener() { @Override public void onSuccess(Void data) { Toast.makeText(context, R.string.toast_register_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(); registerBtn.setEnabled(true); } }); } } ================================================ FILE: app/src/main/java/me/keeganlee/kandroid/adapter/CouponListAdapter.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.adapter; import android.content.Context; import android.text.SpannableString; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import me.keeganlee.kandroid.R; import me.keeganlee.kandroid.model.CouponBO; import me.keeganlee.kandroid.util.CouponPriceUtil; /** * 券列表的Adapter * * @version 1.0 创建时间:15/6/28 */ 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; } } ================================================ FILE: app/src/main/java/me/keeganlee/kandroid/adapter/KBaseAdapter.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.adapter; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import java.util.ArrayList; import java.util.List; /** * Adapter抽象基类 * * @version 1.0 创建时间:15/6/28 */ 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); } ================================================ FILE: app/src/main/java/me/keeganlee/kandroid/util/CouponPriceUtil.java ================================================ /** * Copyright (C) 2015. Keegan小钢(http://keeganlee.me) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.keeganlee.kandroid.util; import android.content.Context; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.style.AbsoluteSizeSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StrikethroughSpan; import java.text.DecimalFormat; /** * 处理券价格的拼接 * * @version 1.0 创建时间:15/6/28 */ public class CouponPriceUtil { /** * 自动处理double数据,保留非0的2位小数 */ public static String handleDouble(double price) { DecimalFormat decimalFormat = new DecimalFormat("##.##"); return decimalFormat.format(price); } /** * sp 转 px */ public static int sp2px(Context context, float spValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); } /** * 现金券显示价格样式 */ public static SpannableString getCashPrice(Context context, double oldPrice, double newPrice) { StringBuilder builder = new StringBuilder(); builder.append(handleDouble(newPrice)).append("元").append(" ").append(handleDouble(oldPrice)).append("元"); int start = 0; int middle = builder.indexOf(" ") + 1; int end = builder.length(); SpannableString string = new SpannableString(builder); /*改变文字的大小*/ string.setSpan(new AbsoluteSizeSpan(sp2px(context, 20)), start, middle, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new AbsoluteSizeSpan(sp2px(context, 14)), middle, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); /*给文字设置删除线*/ string.setSpan(new StrikethroughSpan(), middle, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); /*改变文字的颜色*/ int textOrange = context.getResources().getColor(android.R.color.holo_red_light); int textGray = context.getResources().getColor(android.R.color.darker_gray); string.setSpan(new ForegroundColorSpan(textOrange), start, middle, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textGray), middle, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return string; } /** * 抵用券显示样式 */ public static SpannableString getVoucherPrice(Context context, double voucher, double miniAmount) { StringBuilder builder = new StringBuilder(); int textOrange = context.getResources().getColor(android.R.color.holo_red_light); int textGray = context.getResources().getColor(android.R.color.darker_gray); SpannableString string; if (miniAmount > 0) { builder.append("满").append(handleDouble(miniAmount)).append("元减").append(handleDouble(voucher)).append("元"); int index = builder.indexOf("元") + 1; string = new SpannableString(builder); /*改变文字的颜色*/ int size = string.length(); string.setSpan(new ForegroundColorSpan(textGray), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textOrange), 1, index, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textGray), index, index + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textOrange), index + 1, size, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else { builder.append("立减").append(handleDouble(voucher)).append("元"); string = new SpannableString(builder); /*改变文字的颜色*/ int size = string.length(); string.setSpan(new ForegroundColorSpan(textGray), 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textOrange), 2, size, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } return string; } /** * 折扣券显示样式 */ public static SpannableString getDiscountPrice(Context context, double discount, double miniAmount) { discount = discount * 0.1; StringBuilder builder = new StringBuilder(); int textOrange = context.getResources().getColor(android.R.color.holo_red_light); int textGray = context.getResources().getColor(android.R.color.darker_gray); SpannableString string; if (miniAmount > 0) { builder.append("满").append(handleDouble(miniAmount)).append("元享").append(handleDouble(discount)).append("折"); int index = builder.indexOf("元") + 1; string = new SpannableString(builder); /*改变文字的颜色*/ int size = string.length(); string.setSpan(new ForegroundColorSpan(textGray), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textOrange), 1, index, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textGray), index, index + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textOrange), index + 1, size - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textGray), size - 1, size, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else { builder.append(handleDouble(discount)).append("折"); string = new SpannableString(builder); /*改变文字的颜色*/ int size = string.length(); string.setSpan(new ForegroundColorSpan(textOrange), 0, size - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); string.setSpan(new ForegroundColorSpan(textGray), size - 1, size, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } return string; } } ================================================ FILE: app/src/main/res/layout/activity_coupon_list.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_login.xml ================================================