本系列文章提供简单Android应用开发实例方法,文章步骤如下所示:
1 获取应用所需的数据源
数据源一般来源于互联网、个人搜集或者其他方式
2 应用UI设计
每个应用软件都需要有一个简单的UI设计草图,便于开发者更好的实现编码
3 应用实现
实现完整的Android应用
特此说明:本系列文章的数据源均采用互联网方式获取,仅作为示例演示
应用介绍
提供各个高校历届的分数线录取查询功能,作为高考学子填写志愿的参考应用。最终效果图:
一、信息加载
1 从Assets中获取到学校信息,学校存储格式为:
1#10001#北京大学
1#10002#中国人民大学
1#10003#清华大学
1#10004#北京交通大学
其中1代表省份ID,10001代表学校ID,最后的为学校名称
我们新建FileUtils类,提供获取Assets下文件的方法:
public static List<String> loadFileContentForList(String filePath, Context ctx) throws IOException {
InputStream is = ctx.getAssets().open(filePath);
BufferedReader br = new BufferedReader(new InputStreamReader(is, "gbk"));
ArrayList<String> results = new ArrayList<String>();
String readLine = null;
while((readLine = br.readLine()) != null) {
results.add(readLine);
}
return results;
}
以上方法将文件中的数据按行加载到列表中,由于在保存数据源时保存为ASCII格式,所以此处采用gbk加载。
2 从网络中获取高校查询结果
采用Android自带的HttpClient进行实现,我们首先封装一个简单的HttpUtils处理类,提供httpGet请求方法:
/**
* 获取指定URL请求的结果信息,以字符串方式返回请求内容
* @param url 请求地址
* @param param 请求所需参数
* @return
*/
public static String httpGet(String url, HashMap<String, Object> param) {
HttpParams httpParams = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
HttpConnectionParams.setSoTimeout(httpParams, 5000);
HttpClient mHttpClient = new DefaultHttpClient(httpParams);
String mUrl = url;
if(param != null && param.size() > 0) {
StringBuilder sb = new StringBuilder();
sb.append(url);
sb.append("?");
for(Entry<String, Object> m : param.entrySet()) {
sb.append(m.getKey());
sb.append("=");
sb.append(m.getValue());
sb.append("&");
}
mUrl = sb.substring(0, sb.length()-1);
}
HttpGet httpRequest = new HttpGet(mUrl);
try {
HttpResponse httpResponse = mHttpClient.execute(httpRequest);
if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
String str = EntityUtils.toString(httpResponse.getEntity());
return str;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpRequest.abort();
}
return null;
}
3 为了便于控件渲染数据,我们将所有数据均转为SimpleAdapter可识别的格式ArrayList<HashMap<String, Object>>
通过MUtils类提供各个数据的转化:
loadSchool方法用于提供所有学校信息数据转化,其中Map的key表示省份ID,value为学校列表
@SuppressLint("UseSparseArrays")
public static HashMap<Integer, ArrayList<HashMap<String, Object>>> loadSchool(Context ctx) {
HashMap<Integer, ArrayList<HashMap<String, Object>>> result = new HashMap<Integer, ArrayList<HashMap<String,Object>>>();
try {
List<String> contents = FileUtils.loadFileContentForList("m/data", ctx);
for(int i = 0, len = contents.size(); i < len; i++) {
String[] item = contents.get(i).split("#");
HashMap<String, Object> data = new HashMap<String, Object>();
data.put("id", item[1]);
data.put("name", item[2]);
ArrayList<HashMap<String, Object>> itemList = result.get(Integer.parseInt(item[0]));
if(itemList == null) {
itemList = new ArrayList<HashMap<String,Object>>();
result.put(Integer.parseInt(item[0]), itemList);
}
itemList.add(data);
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
loadResult方法用于将网络获取的查询结果信息进行转化,主要将返回的JSON格式进行处理
public static ArrayList<HashMap<String, Object>> loadResult(final String schoolId, final int type, final int prov2) throws Exception{
//从网络获取查询结果,返回格式为JSON
String netStr = HttpUtils.httpGet(BASE_URL, new HashMap<String, Object>(){{
put("_action", "collegescore");
put("num", "0");
put("provid", prov2);
put("wl", type);
put("collegeid", schoolId);
}});
if(netStr == null) {
//异常直接返回null
return null;
}
JSONArray resultArray = new JSONArray(netStr);
ArrayList<HashMap<String, Object>> result = new ArrayList<HashMap<String,Object>>();
for(int i = 0, len = resultArray.length(); i < len; i++) {
//依次处理每一条数据信息
JSONObject jobj = resultArray.getJSONObject(i);
HashMap<String, Object> d = new HashMap<String, Object>();
int tempInt;
d.put("year", jobj.getString("syear"));
tempInt = jobj.optInt("plan", -1);
d.put("plan", (tempInt <= 0 ? "--" : "" + tempInt));
tempInt = jobj.optInt("score_min", -1);
d.put("score_min", tempInt <= 0 ? "--" : "" + tempInt);
tempInt = jobj.optInt("score_avg", -1);
d.put("score_avg", (tempInt <= 0 ? "--" : "" + tempInt));
tempInt = jobj.optInt("score_td", -1);
d.put("score_td", (tempInt <= 0 ? "--" : "" + tempInt));
tempInt = jobj.optInt("score_max", -1);
d.put("score_max", (tempInt <= 0 ? "--" : "" + tempInt));
d.put("batch", batchMap.get(jobj.getString("batch")));
d.put("batch_diff", jobj.getString("batch_diff"));
result.add(d);
}
return result;
}
public static final HashMap<String, String> batchMap = new HashMap<String, String>(){{
put("00", "不详");
put("01", "本科提前批");
put("11", "本科一批");
put("12", "本科二批");
put("13", "本科三批");
put("123", "本科二、三批");
put("21", "专科");
}};
其中BASE_URL = "http://kaoshi.edu.sina.com.cn/iframe/i_collegescore.php", batchMap从原有数据网站中获取并初始化数据
二、Activity实现
Activity中所有变量定义如下所示:
//依次为省份选择ID,用户所在地ID,学校序号,文理科类型
int prov1, prov2, school, type;
ArrayList<HashMap<String, Object>> resultData; //查询录取结果
HashMap<Integer, ArrayList<HashMap<String, Object>>> datas; //保存省份下的学校信息
//依次为省份选择弹窗、学校选择弹窗和文理科选择弹窗
PopupWindow provWindow, schoolWindow, typeWindow;
TextView prov1Sel, schoolSel, typeSel, prov2Sel, tipView, resultTitleView;
ListView provList, schoolList, resultList;
LinearLayout resultLayout;
Button queryButton;
//依次为省份信息和文理科信息(从strings.xml中获取)
String[] provs, types;
省份弹窗实现如下所示:
/**
* 显示省份弹窗。type=0表示学校省份弹窗,=1表示用户所在地省份弹窗
* @param type
*/
public void showProvWindow(final int type) {
if (provWindow == null) {
View v = LayoutInflater.from(this).inflate(R.layout.pop_list, null);
provList = (ListView) v.findViewById(R.id.pop_list);
provList.setAdapter(new ArrayAdapter<String>(this,
R.layout.pop_list_item, R.id.pop_list_item_name, provs));
provWindow = new PopupWindow(v, Utils.screenWidth,
Utils.screenHeight / 2, true);
}
provList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> av, View arg1, int position,
long arg3) {
//ID为选择序号+1
if (type == 0) {
prov1 = position + 1;
prov1Sel.setText(provs[position]);
} else {
prov2 = position + 1;
prov2Sel.setText(provs[position]);
}
if (provWindow != null) {
provWindow.dismiss();
}
}
});
if (type == 0 && prov1 > 0) {
provList.setSelection(prov1 - 1);
} else if (type == 1 && prov2 > 0) {
provList.setSelection(prov2 - 1);
} else {
provList.setSelection(0);
}
provWindow.showAsDropDown(type == 0 ? prov1Sel : prov2Sel, 0, 0);
provWindow.update();
}
文理科类型弹窗实现如下所示:
public void showTypeWindow() {
if (typeWindow == null) {
View v = LayoutInflater.from(this).inflate(R.layout.pop_list, null);
ListView tList = (ListView) v.findViewById(R.id.pop_list);
tList.setAdapter(new ArrayAdapter<String>(this, R.layout.pop_list_item,
R.id.pop_list_item_name, types));
typeWindow = new PopupWindow(v, Utils.screenWidth / 2,
Utils.screenHeight / 2, true);
tList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> av, View arg1,
int position, long arg3) {
type = position;
typeSel.setText(types[position]);
if (typeWindow != null) {
typeWindow.dismiss();
}
}
});
}
typeWindow.showAsDropDown(typeSel, 0, 0);
typeWindow.update();
}
学校下拉弹窗实现如下所示:
public void showSchoolWindow() {
if (prov1 <= 0) {
Toast.makeText(this, "请先选择省份!", Toast.LENGTH_SHORT).show();
return;
}
if (schoolWindow == null) {
View v = LayoutInflater.from(this).inflate(R.layout.pop_list, null);
schoolList = (ListView) v.findViewById(R.id.pop_list);
schoolList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> av, View arg1,
int position, long arg3) {
school = position;
schoolSel.setText((String) datas.get(prov1).get(school)
.get("name"));
if (schoolWindow != null) {
schoolWindow.dismiss();
}
}
});
schoolWindow = new PopupWindow(v, Utils.screenWidth,
Utils.screenHeight / 2, true);
}
//每次点击都需要更新数据,防止省份改变
schoolList.setAdapter(new SimpleAdapter(this, datas.get(prov1),
R.layout.pop_list_item, new String[] { "name" },
new int[] { R.id.pop_list_item_name }));
schoolList.setSelection(school);
schoolWindow.showAsDropDown(schoolSel, 0, 0);
schoolWindow.update();
}
因为Http请求不能在主线程实现,我们通过新开线程,通过Handler实现UI刷新,请求查询并渲染结果如下所示:
@Override
public void run() {
try {
resultData = MUtils.loadResult((String) datas.get(prov1).get(school)
.get("id"), type + 1, prov2);
} catch (Exception e) {
resultData = null;
}
handler.sendEmptyMessage(0);
}
public void search() {
if (!Utils.canAccessNetwork(this)) {
tipView.setText("无法连接到网络!");
changeView(false);
return;
}
if (prov1 <= 0) {
tipView.setText("请选择学校!");
changeView(false);
return;
}
if (prov2 <= 0) {
tipView.setText("请选择您的所在地!");
changeView(false);
return;
}
tipView.setText("正在努力为您加载中...");
changeView(false);
new Thread(this).start();
}
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (resultData == null) {
tipView.setText("获取结果失败,请稍后再试!");
changeView(false);
} else {
StringBuilder sb = new StringBuilder();
sb.append("<html>");
sb.append("<font color='#006400'><b>");
sb.append(datas.get(prov1).get(school).get("name"));
sb.append("</b></font>");
sb.append("在");
sb.append("<font color='#006400'><b>");
sb.append(provs[prov2 - 1]);
sb.append("</b></font>");
sb.append("的");
sb.append(types[type]);
sb.append("录取线");
sb.append("</html>");
resultTitleView.setText(Html.fromHtml(sb.toString()));
resultList
.setAdapter(new SimpleAdapter(MainActivity.this,
resultData, R.layout.result_item, new String[] {
"year", "score_max", "score_avg",
"score_td", "plan", "batch",
"batch_diff" }, new int[] {
R.id.result_item_year,
R.id.result_item_max,
R.id.result_item_avg,
R.id.result_item_real,
R.id.result_item_persons,
R.id.result_item_pici,
R.id.result_item_xiancha }));
changeView(true);
}
};
};
public void changeView(boolean flag) {
if (flag) {
resultLayout.setVisibility(View.VISIBLE);
tipView.setVisibility(View.GONE);
} else {
resultLayout.setVisibility(View.GONE);
tipView.setVisibility(View.VISIBLE);
}
}
完整的Activity代码如下所示:
package com.gklq.zl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import com.my.lib.Utils;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.text.Html;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements OnClickListener, Runnable {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Utils.screenHeight <= 0) {
Utils.initSize(this);
}
if (datas == null) {
datas = MUtils.loadSchool(this);
}
setContentView(R.layout.activity_main);
prov1Sel = (TextView) findViewById(R.id.prov1_sel);
prov1Sel.setOnClickListener(this);
schoolSel = (TextView) findViewById(R.id.school_sel);
schoolSel.setOnClickListener(this);
typeSel = (TextView) findViewById(R.id.type_sel);
typeSel.setOnClickListener(this);
prov2Sel = (TextView) findViewById(R.id.prov2_sel);
prov2Sel.setOnClickListener(this);
queryButton = (Button) findViewById(R.id.query_btn);
queryButton.setOnClickListener(this);
tipView = (TextView) findViewById(R.id.text_tip);
resultLayout = (LinearLayout) findViewById(R.id.layout_result);
resultTitleView = (TextView) findViewById(R.id.text_result);
resultList = (ListView) findViewById(R.id.list_result);
provs = getResources().getStringArray(R.array.province);
types = getResources().getStringArray(R.array.type);
}
public void onClick(android.view.View v) {
switch (v.getId()) {
case R.id.prov1_sel:
showProvWindow(0);
break;
case R.id.prov2_sel:
showProvWindow(1);
break;
case R.id.type_sel:
showTypeWindow();
break;
case R.id.school_sel:
showSchoolWindow();
break;
case R.id.query_btn:
search();
break;
default:
break;
}
}
@Override
public void run() {
try {
resultData = MUtils.loadResult((String) datas.get(prov1).get(school)
.get("id"), type + 1, prov2);
} catch (Exception e) {
resultData = null;
}
handler.sendEmptyMessage(0);
}
public void search() {
if (!Utils.canAccessNetwork(this)) {
tipView.setText("无法连接到网络!");
changeView(false);
return;
}
if (prov1 <= 0) {
tipView.setText("请选择学校!");
changeView(false);
return;
}
if (prov2 <= 0) {
tipView.setText("请选择您的所在地!");
changeView(false);
return;
}
tipView.setText("正在努力为您加载中...");
changeView(false);
new Thread(this).start();
}
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (resultData == null) {
tipView.setText("获取结果失败,请稍后再试!");
changeView(false);
} else {
StringBuilder sb = new StringBuilder();
sb.append("<html>");
sb.append("<font color='#006400'><b>");
sb.append(datas.get(prov1).get(school).get("name"));
sb.append("</b></font>");
sb.append("在");
sb.append("<font color='#006400'><b>");
sb.append(provs[prov2 - 1]);
sb.append("</b></font>");
sb.append("的");
sb.append(types[type]);
sb.append("录取线");
sb.append("</html>");
resultTitleView.setText(Html.fromHtml(sb.toString()));
resultList
.setAdapter(new SimpleAdapter(MainActivity.this,
resultData, R.layout.result_item, new String[] {
"year", "score_max", "score_avg",
"score_td", "plan", "batch",
"batch_diff" }, new int[] {
R.id.result_item_year,
R.id.result_item_max,
R.id.result_item_avg,
R.id.result_item_real,
R.id.result_item_persons,
R.id.result_item_pici,
R.id.result_item_xiancha }));
changeView(true);
}
};
};
public void changeView(boolean flag) {
if (flag) {
resultLayout.setVisibility(View.VISIBLE);
tipView.setVisibility(View.GONE);
} else {
resultLayout.setVisibility(View.GONE);
tipView.setVisibility(View.VISIBLE);
}
}
public void showSchoolWindow() {
if (prov1 <= 0) {
Toast.makeText(this, "请先选择省份!", Toast.LENGTH_SHORT).show();
return;
}
if (schoolWindow == null) {
View v = LayoutInflater.from(this).inflate(R.layout.pop_list, null);
schoolList = (ListView) v.findViewById(R.id.pop_list);
schoolList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> av, View arg1,
int position, long arg3) {
school = position;
schoolSel.setText((String) datas.get(prov1).get(school)
.get("name"));
if (schoolWindow != null) {
schoolWindow.dismiss();
}
}
});
schoolWindow = new PopupWindow(v, Utils.screenWidth,
Utils.screenHeight / 2, true);
}
//每次点击都需要更新数据,防止省份改变
schoolList.setAdapter(new SimpleAdapter(this, datas.get(prov1),
R.layout.pop_list_item, new String[] { "name" },
new int[] { R.id.pop_list_item_name }));
schoolList.setSelection(school);
schoolWindow.showAsDropDown(schoolSel, 0, 0);
schoolWindow.update();
}
public void showTypeWindow() {
if (typeWindow == null) {
View v = LayoutInflater.from(this).inflate(R.layout.pop_list, null);
ListView tList = (ListView) v.findViewById(R.id.pop_list);
tList.setAdapter(new ArrayAdapter<String>(this, R.layout.pop_list_item,
R.id.pop_list_item_name, types));
typeWindow = new PopupWindow(v, Utils.screenWidth / 2,
Utils.screenHeight / 2, true);
tList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> av, View arg1,
int position, long arg3) {
type = position;
typeSel.setText(types[position]);
if (typeWindow != null) {
typeWindow.dismiss();
}
}
});
}
typeWindow.showAsDropDown(typeSel, 0, 0);
typeWindow.update();
}
/**
* 显示省份弹窗。type=0表示学校省份弹窗,=1表示用户所在地省份弹窗
* @param type
*/
public void showProvWindow(final int type) {
if (provWindow == null) {
View v = LayoutInflater.from(this).inflate(R.layout.pop_list, null);
provList = (ListView) v.findViewById(R.id.pop_list);
provList.setAdapter(new ArrayAdapter<String>(this,
R.layout.pop_list_item, R.id.pop_list_item_name, provs));
provWindow = new PopupWindow(v, Utils.screenWidth,
Utils.screenHeight / 2, true);
}
provList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> av, View arg1, int position,
long arg3) {
//ID为选择序号+1
if (type == 0) {
prov1 = position + 1;
prov1Sel.setText(provs[position]);
} else {
prov2 = position + 1;
prov2Sel.setText(provs[position]);
}
if (provWindow != null) {
provWindow.dismiss();
}
}
});
if (type == 0 && prov1 > 0) {
provList.setSelection(prov1 - 1);
} else if (type == 1 && prov2 > 0) {
provList.setSelection(prov2 - 1);
} else {
provList.setSelection(0);
}
provWindow.showAsDropDown(type == 0 ? prov1Sel : prov2Sel, 0, 0);
provWindow.update();
}
@Override
protected void onResume() {
super.onResume();
if (Utils.screenHeight <= 0) {
Utils.initSize(this);
}
if (datas == null) {
datas = MUtils.loadSchool(this);
}
}
//依次为省份选择ID,用户所在地ID,学校序号,文理科类型
int prov1, prov2, school, type;
ArrayList<HashMap<String, Object>> resultData; //查询录取结果
HashMap<Integer, ArrayList<HashMap<String, Object>>> datas; //保存省份下的学校信息
//依次为省份选择弹窗、学校选择弹窗和文理科选择弹窗
PopupWindow provWindow, schoolWindow, typeWindow;
TextView prov1Sel, schoolSel, typeSel, prov2Sel, tipView, resultTitleView;
ListView provList, schoolList, resultList;
LinearLayout resultLayout;
Button queryButton;
//依次为省份信息和文理科信息(从strings.xml中获取)
String[] provs, types;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (!isExit) {
isExit = true;
Toast.makeText(this, "再按一次返回键退出", Toast.LENGTH_SHORT).show();
tExit = new Timer();
tExit.schedule(new TimerTask() {
@Override
public void run() {
isExit = false;
tExit.cancel();
}
}, 3000);
} else {
finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.gc();
}
} else {
}
return false;
}
private boolean isExit = false;
Timer tExit;
}