前言:
水平:自学Android十五天,以前有过混日子的编程经验。
目标: 《第一行代码》学完之后,总想写个APP,天气的APP写了个初版,后面再说,今天演示的是制作快递查询APP的整个经过。
适合人群:新手
工具:Android Studio 2.2.2
- 新建一个工程,选择Empty Actvity即可,其他默认。
1.布局
actvity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.blade.kuaidisearch.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="@string/input_postid" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="45dp">
<EditText
android:id="@+id/postid"
android:layout_width="0dp"
android:layout_weight="1"
android:background="@layout/edit_bg"
android:hint="@string/input_your_courier_number"
android:layout_height="35dp" />
<!--android:entries="@array/kdcompany"-->
<Spinner
android:id="@+id/spinner"
android:entries="@array/kdcompany"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Spinner>
</LinearLayout>
<Button
android:id="@+id/query"
android:text="@string/btn_query"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
涉及到的相应要修改的文件,res->value->strings.xml文件
<resources>
<string name="app_name">KuaiDISearch</string>
<string name="input_postid">请输入快递单号:</string>
<string name="btn_query">查询</string>
<string name="input_your_courier_number">input your Courier number</string>
</resources>
看默认的输入框不爽,OK,我们来修改一下样式。在layout文件下新建一个名为edit_bg.xml的文件。
然后天剑EditText的android:background=”@layout/edit_bg”
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="#EFEFEF"/>
<corners
android:radius="3dip"
/>
<stroke
android:width="0.5px"
android:color="#505050"/>
</shape>
</item>
</layer-list>
- 下拉框的数据绑定,本文是通过xml文件。
在values下新建一个名为arrays.xml的文件,
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="kdcompany">
<item>申通</item>
<item>EMS</item>
<item>顺丰</item>
<item>圆通</item>
<item>中通</item>
<item>韵达</item>
<item>天天</item>
<item>汇通</item>
<item>全峰</item>
<item>德邦</item>
<item>宅急送</item>
</string-array>
</resources>
我只找到十一家,其他的我暂时还没看。
然后设置Spinner的entries属性。
android:entries=”@array/kdcompany”
最后我们还要设置每一个item的样式。
在layout下新建一个名为item_list.xml文件。(csdn的markdown编辑器不知道为啥不显示我在markdownpad2里编辑内容,大家直接看源码吧。)
整体布局就是垂直方向线性布局,在第二层,嵌套一个水平方向的线性布局。
涉及到的控件:一个TextView,EditText,Spinner,Button,ListView
知识点:
- LinearLayout,vertical,horizontal,layout_weight
- 效果图:
2.ListView使用方法
听说ListView是安卓开发常用的控件,看起来好像是。
我们的第一版的需求不要太复杂,把快递查询的结果分成两列,一列显示时间,一列显示内容(快递到哪儿了)
- 运行一下程序,结果报错了:
Your content must have a ListView whose id attribute is ‘android.R.id.list’
原来在layout_main.xml中ListView的id需要使用系统的。
这一步需要注意的问题是ListView 控件的id要使用Android系统内置的 android:id=”@android:id/list”
忘了说了,我们让我们的MainActivity继承ListActivity,即extends ListActivity。
3.接口数据
昨天夜里一点逛知乎,看到了一个快递的免费接口,这让我马上想到做出这个应用。
http://www.kuaidi100.com/query?type=快递公司代号&postid=快递单号
快递公司代号:申通=”shentong” EMS=”ems” 顺丰=”shunfeng” 圆通=”yuantong” 中通=”zhongtong” 韵达=”yunda” 天天=”tiantian” 汇通=”huitongkuaidi” 全峰=”quanfengkuaidi” 德邦=”debangwuliu” 宅急送=”zhaijisong”
示例:http://www.kuaidi100.com/query?type=tiantian&postid=XXXXXXXXXXX
网站返回的是JSON数据,为了更好地阅读JSON数据,可以把返回的数据复制到以下网址,可以格式化JSON数据。
导入了org.apache.http.legacy还是无效呢,因为有两个版本的。
还要设置targetSdkVersion与legacy包相匹配。
4.解析JSON数据
4.1 Button的点击事件
先声明:
private Button query;
在onCreate方法里赋值:
query = (Button)findViewById(R.id.query);
用匿名内部类来写点击事件(其他写法,可以参考我的博文Button点击事件的四种写法)
query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendRequest();
}
});
点击按钮之后,我们想服务器(接口所在的)发送请求,sendRequest方法就是干这个的。
//发送Http请求
private void sendRequest(){
new Thread(new Runnable() {
@Override
public void run() {
try{
HttpClient httpClient = new DefaultHttpClient();
//处理快递单号
HttpGet httpGet = new HttpGet(http://www.kuaidi100.com/query?type=shunfeng&postid=XXXXXXX);//XXXXXX处填写快递单号
HttpResponse httpResponse = httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode() == 200){
//请求和响应都成功了
HttpEntity entity = httpResponse.getEntity();
String response = EntityUtils.toString(entity,"utf-8");
parseJSON(response);
}
}catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}
在这里补一句,HttpClient需要自己在libs添加一个jar包。自己通过在windows上搜索,找到org.apache.http.legacy.jar 还要注意一个版本的问题,你的build.gradle(Module:app)里的targetSdkVersion是24,那么你就在例如D:\Android\sdk\platforms\android-24\optional文件夹下找。
android-24,我之前找了个android-23的,出不来
接受到的JSON数据如下:
"message": "ok",
"nu": "XXXXXXXXXXX",
"ischeck": "0",
"condition": "00",
"com": "tiantian",
"status": "200",
"state": "0",
"data": [
{
"time": "2016-12-18 18:57:54",
"ftime": "2016-12-18 18:57:54",
"context": "快件已签收,签收人是乡镇代理点,签收网点是XXXXXX",
"location": ""
},
- 先声明一个list和一个HashMap,哈希表是按照键值对来储存数据了。
ArrayList
4.2 到了关键部分了,就是解析JSON数据,有很多种方式,其他方式可以上网搜一搜。
//解析JSON数据
private void parseJSON(String jsonData){
try{
//我们需要解析数组data的数据,其中time和context就是需要的字段
JSONArray jsonArray = new JSONObject(jsonData).getJSONArray("data");
for (int i = 0;i<jsonArray.length();i++){
JSONObject jsonObject = jsonArray.getJSONObject(i);
String time = jsonObject.getString("time");
String context = jsonObject.getString("context");
Log.d("data","time is " + time);
Log.d("context","context is " + context);
map = new HashMap<String,String>();
map.put("time",time);
map.put("context",context);
list.add(map);
}
//在子线程向ListView里添加数据
new Thread(){
public void run(){
runOnUiThread(new Runnable() {
@Override
public void run() {
SimpleAdapter simpleAdapter = new SimpleAdapter(MainActivity.this,list,
R.layout.item_list,form,to);
setListAdapter(simpleAdapter);
//假如没有数据,提醒用户
if(lv.getCount() == 0){
Toast.makeText(MainActivity.this,"还没有数据",Toast.LENGTH_LONG).show();
}
}
});
}
}.start();
}catch (Exception e)
{
e.printStackTrace();
}
}
- 假如没有在子线程更新view,就会报错,如下:
Only the original thread that created a view hierarchy can touch its views.
5.下拉框
到这里,应该可以查询到单条数据了,
回到上面的sendRequest方法。可以看到我们刚才发送请求的地址是:
//处理快递单号
HttpGet httpGet = new HttpGet(http://www.kuaidi100.com/query?
type=shunfeng&postid=XXXXXXX);//XXXXXX处填写快递单号
- 现在我们要实现输入框和下拉框的功能,为了能让大家看得更直观,我就先只传了一个具体的链接。
- 需要发送的链接里包含两个参数,一个是快递公司的代号,我们可以获得下拉框选中的值得到,另一个是快递单号,可以通过获取输入框的值得到。
声明:
private EditText postid;
private Spinner spinner;
赋值
postid = (EditText)findViewById(R.id.postid);
spinner = (Spinner)findViewById(R.id.spinner);
需要修改一下sendRequest方法。
//发送Http请求
private void sendRequest(){
new Thread(new Runnable() {
@Override
public void run() {
try{
HttpClient httpClient = new DefaultHttpClient();
//处理快递单号
String queryString = checkPostid();
HttpGet httpGet = new HttpGet(queryString);
HttpResponse httpResponse = httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode() == 200){
//请求和响应都成功了
HttpEntity entity = httpResponse.getEntity();
String response = EntityUtils.toString(entity,"utf-8");
parseJSON(response);
}
}catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}
checkPostid方法就是通过识别快递单号是哪家的快递,给服务器传递响应的请求
//识别快递单号是哪家的快递,给服务器传递响应的请求
private String checkPostid(){
//基础链接
String baseStr = "http://www.kuaidi100.com/query?type=";
String type= "";
String postidNumber = postid.getText().toString().trim();
String kuaidiCompany = spinner.getSelectedItem().toString().trim();
Log.d("post id",postidNumber);
Log.d("kuaidiCompany id",kuaidiCompany);
if(kuaidiCompany.equals("申通")){
type = "shentong";
}else if(kuaidiCompany.equals("EMS")){
type = "ems";
}else if(kuaidiCompany.equals("顺丰")){
type = "shunfeng";
}else if(kuaidiCompany.equals("圆通")){
type = "yuantong";
}else if(kuaidiCompany.equals("中通")){
type = "zhongtong";
}else if (kuaidiCompany.equals("韵达")){
type = "yunda";
}else if (kuaidiCompany.equals("天天")){
type = "tiantian";
}else if (kuaidiCompany.equals("汇通")){
type = "huitongkuaidi";
}else if (kuaidiCompany.equals("全峰")){
type = "quanfengkuaidi";
}else if (kuaidiCompany.equals("德邦")){
type = "debangwuliu";
}else if(kuaidiCompany.equals("宅急送")){
type = "zhaijisong";
}
Log.d("url",baseStr + type + "&postid=" + postidNumber);
return baseStr + type + "&postid=" + postidNumber;
}
- 不要吐槽我的英文,想半天也不知道怎么取个合适的方法名。连工程名也写错了意思。/(ㄒoㄒ)/~~
6.测试
天天,顺丰,中通,韵达都测试过,没有问题。
为了有更好的用户体验,在没有数据的时候,给一个Toast提示一下用户。
用listview的getCount方法,假如是0,说明没有数据。
//假如没有数据,提醒用户 if(lv.getCount() == 0){ Toast.makeText(MainActivity.this,"还没有数据",Toast.LENGTH_LONG).show(); }
有时候出现这种情况,那么要去了解status为201是什么意思。今天就算了,都凌晨了。
{“message”:”快递公司参数异常:单号不存在或者已经过期”,”nu”:”“,”ischeck”:”0”,”condition”:”“,”com”:”“,”status”:”201”,”state”:”0”,”data”:[]}
其他的查询快递的APP都是直接输快递单号的,为啥这个不行,因为我在网上找不到快递公司单号的规则啊,就是怎么通过快递单号码来知道它是哪家公司的,这个不像身份证一样那么有规律。
7.问题
- android没有标题栏
- 清空android ListView控件的内容
8.后期版本
- 扫二维码(不过,还是多看看接口的文档,快递单号的问题没解决,扫码暂时也没啥用)
- 输入单号自动识别快递公司
- 美化控件和界面
- 标题栏的问题
- 菜单栏(先解决标题栏的问题)
9.参考资料以及扩展
- 接口文档
https://www.kuaidi100.com/openapi/api_post.shtml
- 美化界面
http://blog.sina.com.cn/s/blog_7256fe8f0101ddmd.html
http://blog.csdn.net/badboy007/article/details/19034609
- 给EditText设置边框
http://www.cnblogs.com/johnsonwei/p/5785055.html
http://www.jb51.net/article/46652.htm
- EditText自定义样式
http://blog.csdn.net/jdsjlzx/article/details/25063169
- layout的五大布局
http://www.tuicool.com/articles/3uUZbmu
- android开发教程之listview使用方法
http://www.jb51.net/article/46652.htm
- Android采用ListView实现数据列表显示
http://blog.csdn.net/furongkang/article/details/6819247
- Android适配器之ArrayAdapter、SimpleAdapter和BaseAdapter的简单用法与有用代码片段
http://blog.csdn.net/shakespeare001/article/details/7926783
- android Json解析详解(详细代码)
http://blog.csdn.net/onlyonecoder/article/details/8490924
- Android子线程中更新UI的3种方法
http://gqdy365.iteye.com/blog/2112471
- Android子线程真的不能更新UI么
http://www.cnblogs.com/lao-liang/p/5108745.html
- ListView之SimpleAdapter的使用
http://blog.csdn.net/abc5382334/article/details/13503475/
- 免费实用的API接口
https://zhuanlan.zhihu.com/p/21320392?refer=passer
10.结束语
感谢以上文章的作者,给了我做出第一款APP的资源。
时间已经到了00:43,我还是睡不着。
代码写的烂,需要改进的地方很多。
基础不扎实,还得好好搞搞java。
需要做的事很多,再也没有接口说不会了,说很难了。因为你可以做到的。
一个曾经写过程序又没写了,现在又写了的菜鸟程序员实在是有点啰嗦。
程序的源码我整理一下再上传,方便大家参考,我怕自己没写清楚。
地址(设置了1分的鼓励分):http://download.csdn.net/detail/a1b2c300/9724067