实验1 Android APP UI设计与实现
1.实验目的
根据实际需求,发现每日的空气质量、温度、风力等周围环境等对人类的生活影响很大,跟我们的生活密切相关,所以此实验的目的就是开发一款私人天气生活小助手的APP。
2.实验内容
1.首先得有个可以显示全国所有省市县的界面,设计如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"/>
<Button
android:id="@+id/back_button"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_back"/>
</RelativeLayout>
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
- 定义一个头布局作为标题栏,TextView显示标题内容,Button用于返回上一级目录,ListView显示省市县的数据。
运行效果大体如下:
2.然后设计一个天气显示界面,通过学习参考其他的类似APP,我的设计如下:
- title.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<Button
android:id="@+id/nav_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_home" />
<TextView
android:id="@+id/title_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"/>
<TextView
android:id="@+id/title_update_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:textColor="#fff"
android:textSize="16sp"/>
</RelativeLayout>
Button居左用于更换其他省市县,第一个TextView居中显示所选中的区,第2个TextView居右显示天气更新时间
- now.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp">
<TextView
android:id="@+id/degree_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="#fff"
android:textSize="60sp"/>
<TextView
android:id="@+id/weather_info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="#fff"
android:textSize="20sp"/>
</LinearLayout>
2个TextView,一个用于显示当前气温,一个显示天气概况;
- forecase.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="#8000">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="预报"
android:textColor="#fff"
android:textSize="20sp"/>
<LinearLayout
android:id="@+id/forecast_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
</LinearLayout>
外层LinearLayout定义为半透明状态,TextView用于显示标题,LinearLayout用于动态添加未来3天的天气预报
- forecast_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp">
<TextView
android:id="@+id/date_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:textColor="#fff"/>
<TextView
android:id="@+id/info_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="center"
android:textColor="#fff"/>
<TextView
android:id="@+id/max_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="right"
android:textColor="#fff"/>
<TextView
android:id="@+id/min_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="right"
android:textColor="#fff"/>
</LinearLayout>
用来显示未来天气每一天的布局:
定义4个TextView,分别显示日期,天气状况、最高温度和最低温度;
- aqi.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="#8000">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="空气质量"
android:textColor="#fff"
android:textSize="20sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:id="@+id/aqi_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="AQI指数"
android:textColor="#fff"/>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:id="@+id/pm25_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="40sp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="PM2.5指数"
android:textColor="#fff"
/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
用来显示空气质量状况,TextView用于显示标题,接下来使用嵌套方式实现左右平分且居中对齐的布局分别显示AQI指数和PM2.5指数
- suggestion.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="#8000">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="生活建议"
android:textColor="#fff"
android:textSize="20sp"/>
<TextView
android:id="@+id/comfort_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff" />
<TextView
android:id="@+id/car_wash_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff" />
<TextView
android:id="@+id/sport_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#fff" />
</LinearLayout>
用来显示生活建议布局,很简单,4个TextView分别显示标题、舒适度、洗车指数、运行建议。
activity_weaher.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<ImageView
android:id="@+id/bing_pic_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:overScrollMode="never">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<include layout="@layout/title" />
<include layout="@layout/now" />
<include layout="@layout/forecast" />
<include layout="@layout/aqi" />
<include layout="@layout/suggestion" />
</LinearLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.coolweather.android.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"/>
</android.support.v4.widget.DrawerLayout>
</FrameLayout>
最后整合所有布局,引入到最终的布局:
1.加入滑动显示选择地区的布局;
2.每日更新背景图片的布局;
3.向下拉动刷新天气;
至此,所有的UI设计全部完毕;
最终运行效果截图:
实验2、Android APP 功能实现
1.实验目的
通过前面实验1的UI设计完毕后,这个实验进入到主要功能代码实现阶段;
2.实验内容
具体实现如下:
1.实验需要用到网络来获取数据,所以需要天气API接口,这里采用的和风天气API;
需要获取省市区的信息,这里直接从别人搭建好的服务器中拉取到的;
所以需要用到okHttp开源开源框架和GSON框架来解析json数据
- Uility.java
/**
* Created by lwx on 2018/1/8.
*/
public class Utility {
/**
* 解析和处理服务器返回的省级数据
*/
public static boolean handleProvinceResponse(String response){
if(!TextUtils.isEmpty(response)){
try {
JSONArray allProvinces = new JSONArray(response);
for (int i = 0; i < allProvinces.length(); i++) {
JSONObject provinceObject = allProvinces.getJSONObject(i);
Province province = new Province();
province.setProvinceName(provinceObject.getString("name"));
province.setProvinceCode(provinceObject.getInt("id"));
province.save();
}
return true;
}catch (JSONException e){
e.printStackTrace();
}
}
return false;
}
/**
*解析和处理服务器返回的市级数据
*/
public static boolean handleCityResponse(String response,int provinceId){
if(!TextUtils.isEmpty(response)){
try {
JSONArray allCities = new JSONArray(response);
for(int i=0;i<allCities.length();i++){
JSONObject cityObject = allCities.getJSONObject(i);
City city = new City();
city.setCityName(cityObject.getString("name"));
city.setCityCode(cityObject.getInt("id"));
city.setProvinceId(provinceId);
city.save();
}
return true;
}catch (JSONException e){
e.printStackTrace();
}
}
return false;
}
/**
* 解析和处理服务器返回的县级数据
*/
public static boolean handleCountyResponse(String response,int cityId){
if(!TextUtils.isEmpty(response)){
try{
JSONArray allcounties = new JSONArray(response);
for(int i=0;i<allcounties.length();i++){
JSONObject countyObject = allcounties.getJSONObject(i);
County county = new County();
county.setCountyName(countyObject.getString("name"));
county.setWeatherId(countyObject.getString("weather_id"));
county.setCityId(cityId);
county.save();
}
return true;
}catch (JSONException e){
e.printStackTrace();
}
}
return false;
}
/**
* 将返回的json数据解析成Weather实体类
*/
public static Weather handleWeatherResponse(String response){
try{
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather");
String weatherContent = jsonArray.getJSONObject(0).toString();
return new Gson().fromJson(weatherContent,Weather.class);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
- HttpUtil.java
/**
* Created by lwx on 2018/1/8.
*/
public class HttpUtil {
public static void sendOkHttpRequest(String address, okhttp3.Callback callback){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
}
2.设计数据库表来保存省市区的信息,3个数据库表:
- province.java
/**
* Created by lwx on 2018/1/8.
*/
public class Province extends DataSupport {
private int id;
private String provinceName;
private int provinceCode;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProvinceName() {
return provinceName;
}
public void setProvinceName(String provinceName) {
this.provinceName = provinceName;
}
public int getProvinceCode() {
return provinceCode;
}
public void setProvinceCode(int provinceCode) {
this.provinceCode = provinceCode;
}
}
- city.java
/**
* Created by lwx on 2018/1/8.
*/
public class City extends DataSupport {
private int id;
private String cityName;
private int provinceId;
private int cityCode;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public int getProvinceId() {
return provinceId;
}
public void setProvinceId(int provinceId) {
this.provinceId = provinceId;
}
public int getCityCode() {
return cityCode;
}
public void setCityCode(int cityCode) {
this.cityCode = cityCode;
}
}
county.java
/**
* Created by lwx on 2018/1/8.
*/
public class County extends DataSupport {
private int id;
private String countyName;
private String weatherId;
private int cityId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCountyName() {
return countyName;
}
public void setCountyName(String countyName) {
this.countyName = countyName;
}
public String getWeatherId() {
return weatherId;
}
public void setWeatherId(String weatherId) {
this.weatherId = weatherId;
}
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
}
3.json数据解析对应到实体类
分别创建用于接收到返回json数据的实体类,这里仅列出几个,都是一样的;
- Weather.java
/**
* Created by lwx on 2018/1/8.
*/
public class Weather {
public String status;
public Basic basic;
public AQI aqi;
public Now now;
public Suggestion suggestion;
@SerializedName("daily_forecast")
public List<Forecast> forecastList;
}
Forecast.java
/**
* Created by lwx on 2018/1/8.
*/
public class Forecast {
public String date;
@SerializedName("tmp")
public Temperature temperature;
@SerializedName("cond")
public More more;
public class Temperature{
public String max;
public String min;
}
public class More{
@SerializedName("txt_d")
public String info;
}
}
4.Activity和Fragment的编写
将天气界面活动定义为Activity,将省市区界面定位为Fragment;为了重复利用
- ChooseAreaFragment.java核心代码:
/**
*查询选中的城市的所有县,优先从数据库查询,没有的话再去服务器上查询
*/
private void queryCounties(){
titleText.setText(selectedCity.getCityName());
backButton.setVisibility(View.VISIBLE);
countyList = DataSupport.where("cityid = ?",String.valueOf(selectedCity.getId())).find(County.class);
if(countyList.size()>0){
dataList.clear();
for(County county : countyList){
adapter.add(county.getCountyName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel = LEVE_COUNTY;
}else {
int provinceCode = selectedProvince.getProvinceCode();
int cityCode = selectedCity.getCityCode();
String address = "http://guolin.tech/api/china/"+provinceCode+"/"+cityCode;
queryFromServer(address,"county");
}
}
/**
*从服务器中获取数据
*/
private void queryFromServer(String address,final String type){
showProgressDialog();
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseText = response.body().string();
boolean result = false;
if("province".equals(type)){
result = Utility.handleProvinceResponse(responseText);
}else if("city".equals(type)){
result = Utility.handleCityResponse(responseText,selectedProvince.getId());
}else if("county".equals(type)){
result = Utility.handleCountyResponse(responseText,selectedCity.getId());
}
if(result){
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if("province".equals(type)){
queryProvinces();
}else if("city".equals(type)){
queryCities();
}else if("county".equals(type)){
queryCounties();
}
}
});
}
}
});
}
- WeatherActivity.java核心代码 :
通过设置缓存来加速显示数据
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = prefs.getString("weather",null);
//
final String weatherId;
if(weatherString != null){
//有缓存时,直接解析天气数据
Weather weather = Utility.handleWeatherResponse(weatherString);
weatherId = weather.basic.weatherId;
showWeatherInfo(weather);
}else {
//无缓存时去服务器查询天气
weatherId = getIntent().getStringExtra("weather_id");
weatherLayout.setVisibility(View.INVISIBLE);
requestWeather(weatherId);
}
/**
* 根据天气id请求城市天气信息
*/
public void requestWeather(final String weatherId){
String weatherUrl = "http://guolin.tech/api/weather?cityid="+weatherId+ "&key=ad622fd00da44c739e5f602ce6bd1fb2";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT).show();
swipeRefreshLayout.setRefreshing(false);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseText = response.body().string();
final Weather weather = Utility.handleWeatherResponse(responseText);
runOnUiThread(new Runnable() {
@Override
public void run() {
if (weather != null && "ok".equals(weather.status)) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.putString("weather",responseText);
editor.apply();
showWeatherInfo(weather);
}else {
Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT).show();
}
swipeRefreshLayout.setRefreshing(false);
}
});
}
});
loadBingPic();
}
/**
*加载必应每日一图
*/
private void loadBingPic(){
String requestBingPic = "http://guolin.tech/api/bing_pic";
HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String bingPic = response.body().string();
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.putString("bing_pic",bingPic);
editor.apply();
runOnUiThread(new Runnable() {
@Override
public void run() {
Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg);
}
});
}
});
}
5.后台设置自动更新服务:
- AutoUpdataService.java核心代码:
public int onStartCommand(Intent intent, int flags, int startId) {
updateWeather();
updateBingPic();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 12 * 60 * 60 *1000;
long triggerAtTime = SystemClock.elapsedRealtime()+ anHour;
Intent i = new Intent(this,AutoUpdataService.class);
PendingIntent pi = PendingIntent.getService(this,0,i,0);
manager.cancel(pi);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
return super.onStartCommand(intent,flags,startId);
}
至此,所有的功能已经实现,效果展示在界面UI设计已经全部展示完毕;