天气预报App
- 简介
- 功能
- 1.省市县三级地区信息获取与显示
- 1.1 添加数据库及后续依赖
- 1.2 创建数据库所需的实体类(表)
- 1.3 创建和配置数据库
- 1.4 更换继承的Application
- 1.5 加载省市县的所有数据
- 2. 天气信息的获取
- 2.1 申请API
- 2.2 Android SDK的下载
- 2.3 天气UI界面的编写
- 2.4 逻辑实现
- 2.4.1 将返回的json数据解析成HeWeather实体类
- 2.4.2 根据获得的天气信息动态获取天气图标和天气背景
- 2.4.3 天气信息的请求和展示
- 2.4.4 打开App后显示上次所选城市的信息
- 3. 运行情况
简介
该App是在《第一行代码》中酷欧天气的基础上修改的。
加上托管在gihub上的代码链接https://github.com/SONGSONG729/MyWeather
功能
- 通过选择地区获取天气信息 。
- 下拉手动更新天气信息。
- 重新选择城市获取天气信息。
1.省市县三级地区信息获取与显示
1.1 添加数据库及后续依赖
采用LitePal来管理项目的数据库,需要先在build.gradle中添加依赖(包括后续的依赖):
//用于处理网络请求
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
//用于解析json数据
implementation 'com.google.code.gson:gson:2.6.2'
//用于对数据库进行操作
implementation 'org.litepal.android:core:1.4.1'
//用于加载和展示图片
implementation 'com.github.bumptech.glide:glide:3.7.0'
1.2 创建数据库所需的实体类(表)
创建db包,用于存放数据库模型的代码。
创建util包,用于存放工具类代码。
在db包下创建三个实体类:Province、City和County。
Province类的代码如下
public class Province extends DataSupport {
/**
* 记录省
*/
private int id; //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类的代码如下:
public class City extends DataSupport {
/**
* 记录市
*/
private int id; //id
private String cityName; //市名
private int cityCode; //代号
private int provinceId; //所属省的id
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 getCityCode() {
return cityCode;
}
public void setCityCode(int cityCode) {
this.cityCode = cityCode;
}
public int getProvinceId() {
return provinceId;
}
public void setProvinceId(int provinceId) {
this.provinceId = provinceId;
}
}
County类的代码如下:
public class County extends DataSupport {
/**
* 记录县
*/
private int id; //id
private String countyName; //县名
private String weatherId; //对应天气
private int cityId; //所属市的id
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;
}
}
1.3 创建和配置数据库
在项目文件夹下创建一个Assets Folder文件夹,命名为assets,然后在assets文件夹下创建一个相应的litepal.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<!-- 创建数据库,名为myweather-->
<litepel>
<dbname value = "myweather"/>
<version value="2"/>
<list>
<mapping class="com.example.myweather.db.Province"/>
<mapping class="com.example.myweather.db.City"/>
<mapping class="com.example.myweather.db.County"/>
</list>
</litepel>
其中dbname标签是相应的数据库名称,version标签是相应的数据库的版本,list标签是需要管理的数据库对象(即表)。
1.4 更换继承的Application
本项目直接继承相应的LitePalApplication。
修改AndroidManifest.xml中的如下代码:
<application
android:name="org.litepal.LitePalApplication" //直接继承相应的LitePalApplication
android:allowBackup="true"
android:icon="@mipmap/icon_icon"
android:roundIcon="@mipmap/icon_icon"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
配置完毕后,数据库和表会在首次执行时自行创建。
1.5 加载省市县的所有数据
省市县数据是从服务器中获取的,因此需要与服务器进行交互。
在util包中创建HttpUtil类,该类中具有发送请求并返回数据的功能。
public class HttpUtil {
/**
* 发送请求并反馈
* @param address 请求的地址
* @param callback 返回数据
*/
public static void sendOkHttpRequests(String address,okhttp3.Callback callback){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
}
从服务器获取json数据后还需要对json信息进行解析和处理,可以在util包中创建一个Utility工具类来实现该功能。
public class Utility {
/**
* 解析和处理返回的省级数据
* @param response
* @return
*/
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实例中
province.setProvinceName(provinceObject.getString("name"));
province.setProvinceCode(provinceObject.getInt("id"));
//调用save方法将实体类对象一个一个存入数据库
province.save();
}
return true;//解析成功
} catch (JSONException e) {
e.printStackTrace();
}
}
return false;//解析失败
}
/**
* 解析和处理服务器返回的市级数据
* @param response
* @param provinceId
* @return
*/
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.setCityCode(cityObject.getInt("id"));
city.setCityName(cityObject.getString("name"));
city.setProvinceId(provinceId); //所属的省级id
city.save();
}
return true;
} catch (JSONException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 解析和处理服务器返回的县级数据
* @param response
* @param cityId
* @return
*/
public static boolean handleCountyResponse(String response,int cityId){
if (!TextUtils.isEmpty(response)){
try {
JSONArray allCountries = new JSONArray(response);
for (int i=0;i<allCountries.length();i++){
JSONObject countryObject = allCountries.getJSONObject(i);
County county = new County();
county.setCountyName(countryObject.getString("name"));
//县级天气信息
county.setWeatherId(countryObject.getString("weather_id"));
//所属的市级代号
county.setCityId(cityId);
county.save();
}
return true;
} catch (JSONException e) {
e.printStackTrace();
}
}
return false;
}
}
编写UI界面,不使用原生的ActionBarer,在styles.xml中进行设置:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
将界面写在布局文件中,新建布局文件choose_area.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="match_parent"
android:orientation="vertical"
android:background="#fff">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/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_arrow_back_black_24dp" />
</RelativeLayout>
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
创建一个AreaFragment类,继承自Fragment。该Fragment可以通过从数据库获取或网络获取两种方式来加载全国省市县数据,获取到天气id后跳转到后续编写的天气界面。
public class ChooseAreaFragment extends Fragment {
public static final int LEVEL_PROVINCE=0;
public static final int LEVEL_CITY=1;
public static final int LEVEL_COUNTY=2;
private ProgressDialog progressDialog;
private TextView titleText;
private Button backButton;
private ListView listView;
private ArrayAdapter<String> adapter;
private List<String> dataList = new ArrayList<>();
private List<Province> provinceList; //省列表
private List<City> cityList; //市列表
private List<County> countyList; //县列表
private Province selectedProvince; //当前被选中的省
private City selectedCity; //当前被选中的城市
private int currentLevel; //当前被选中的级别
/**
* 初始化视图
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container,
@Nullable Bundle savedInstanceState) {
Log.d("ChooseAreaFragment","onCreateView");
View view = inflater.inflate(R.layout.choose_area,container,false);
//获取控件实例
titleText = (TextView)view.findViewById(R.id.title_text); //获取标题栏文本id
backButton = (Button) view.findViewById(R.id.back_button); //获取标题栏id
listView = (ListView)view.findViewById(R.id.list_view); //获取Item列表id
//获取ArrayAdapter对象
adapter =new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, dataList); //初始化adapter
listView.setAdapter(adapter);//将初始化后的适配器(adapter)设置为listView的适配器
return view; //将视图返回
}
/**
* 设置ListView和Button的的点击事件
* @param savedInstanceState
*/
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
Log.d("ChooseAreaFragment","onActivityCreated");
super.onActivityCreated(savedInstanceState);
//设置listView的点击事件,列表任意一栏被点击,则...
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (currentLevel == LEVEL_PROVINCE){ //当前选中的级别为省份时
selectedProvince = provinceList.get(position); //当前点击为选中状态
queryCities();//查询市的方法
}
else if (currentLevel == LEVEL_CITY){
selectedCity = cityList.get(position);
queryCountries();
}else if (currentLevel == LEVEL_COUNTY){ //当点击县级item时
String weatherId = countyList.get(position).getWeatherId(); //获取县级对应的请求天气的代码
if (getActivity() instanceof MainActivity){ //
Intent intent = new Intent(getActivity(),WeatherActivity.class); //启动Weather。。
intent.putExtra("weather_id",weatherId); //把当前县的天气传递到Wea。。。
startActivity(intent);
getActivity().finish();
}else if (getActivity() instanceof WeatherActivity){
WeatherActivity activity = (WeatherActivity) getActivity();
activity.drawerLayout.closeDrawers();
activity.swipeRefresh.setRefreshing(true);
activity.requestWeather(weatherId);
}
}
}
});
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (currentLevel == LEVEL_COUNTY){
queryCities();
}
else if (currentLevel == LEVEL_CITY){
queryProvinces();
}
}
});
queryProvinces();
}
/**
* 查询省,优先从数据库查询,没有再到服务器查询
*/
private void queryProvinces() {
titleText.setText("中国"); //设置标题内容为中国
backButton.setVisibility(View.GONE); //设置返回按钮不可见
//查询被选中的省份
provinceList = DataSupport.findAll(Province.class); //从Province表中查询所有省,放在列表中
if (provinceList.size()>0){ //如果省级列表不为空
dataList.clear(); //清空列表
for (Province province: provinceList){ //遍历每一份省级数据
dataList.add(province.getProvinceName()); //添加到数据列表中
}
adapter.notifyDataSetChanged(); //通知适配器更新了
listView.setSelection(0); //将listView滚动到顶部显示
currentLevel=LEVEL_PROVINCE; //设置当前级别
}else{ //如果provinceList为空则从服务器上查询数据
String address ="http://guolin.tech/api/china"; //获取查询地址
queryFromServer(address,"province"); //从网络中查询
}
}
/**
* 查询选中的省内的所有市。。。。
*/
private void queryCities() {
titleText.setText(selectedProvince.getProvinceName()); //设置标题为该省
backButton.setVisibility(View.VISIBLE); //返回按钮可见
//查询被选中的省份的市
cityList=DataSupport.where("provinceId=?",String.valueOf(selectedProvince.getId())).find(City.class );
if(cityList.size()>0) { //如果市列表不为空
dataList.clear();
for (City city : cityList) { //遍历城市
dataList.add(city.getCityName()); //将数据添加到列表中
}
adapter.notifyDataSetChanged(); //通知适配器更新
listView.setSelection(0);
currentLevel = LEVEL_CITY;
} else{
int provinceCode=selectedProvince.getProvinceCode();
String address="http://guolin.tech/api/china/"+provinceCode; //设置请求网址
queryFromServer(address,"city"); //
}
}
/**
* 查询当前选中的市的县。。。。
*/
private void queryCountries(){
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){
dataList.add(county.getCountyName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel=LEVEL_COUNTY;
}else{
int provinceCode=selectedProvince.getProvinceCode();
int cityCode=selectedCity.getCityCode() ;
String address="http://guolin.tech/api/china/"+provinceCode + "/" + cityCode;
queryFromServer(address,"country");
}
}
/**
* 从服务器上查询
* @param address 服务器地址
* @param type 类型
*/
private void queryFromServer(String address,final String type){
showProgressDialog(); //显示进度
//发送请求
HttpUtil.sendOkHttpRequests(address, new Callback() {
/**
* 请求加载失败
* @param call
* @param e
*/
@Override
public void onFailure(Call call, IOException e) {
//通过runOnUiThread方法从子线程切换到主线程逻辑
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show();
}
});
}
/**
* 响应的数据回调到该方法中
* @param call
* @param response
* @throws IOException
*/
@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 ("country".equals(type)){
result=Utility.handleCountyResponse(responseText,selectedCity.getId());}
if (result){ //如果从服务器请求并解析成功,再次调用query。。方法来获取省级/市级。。数据
//query..方法涉及UI操作,需要在主线程调用,用runOnThread方法从子线程切换到主线程
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if ("province".equals(type)){
queryProvinces();
}else if("city".equals(type)){
queryCities();
}else if("country".equals(type)){
queryCountries();
}
}
});
}
}
});
}
/**
* 显示进度
*/
private void showProgressDialog() {
if (progressDialog==null){
progressDialog=new ProgressDialog(getActivity());
progressDialog.setMessage("正在加载......");
progressDialog.setCanceledOnTouchOutside(false);
}
progressDialog.show();
}
/**
* 关闭进度
*/
private void closeProgressDialog() {
if (progressDialog!=null){
progressDialog.dismiss();
}
}
}
将碎片放置在主布局中,修改activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.example.myweather.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
在AndroidManifest.xml中添加网络访问权限:
<uses-permission android:name="android.permission.INTERNET" />
2. 天气信息的获取
本项目中的天气信息从和风天气中获取。
2.1 申请API
在和风天气官网上注册为个人开发者,登陆成功后点击控制台-我的控制台-账号信息-用户类型-点击修改-升级为个人开发者。
审核成功后点击控制台-应用管理-新建应用-[填写应用名称]-创建。创建好后为应用创建key,如下图所示:
选择Web API(若选择Android SDK,安卓项目的的名称和应用名称必须相同。),IP选填,点击创建,创建好后如下图所示:
该Web API的使用将在
2.2 Android SDK的下载
到和风天气开发者的Android SDK 使用文档中下载Android SDK,下载好后将SDK放到app目录下的libs目录中。
由于SDK的使用较为复杂,且该项目是作者的期末大作业,作者无法在短时间内弄懂如何使用SDK,因此本项目中的json天气信息是通过API获取的,只使用了SDK中封装的有关天气信息的bean来解析json数据。
2.3 天气UI界面的编写
创建一个活动为WeatherActivity,布局指定为activity_weather.xml。分模块编写天气UI布局,最后在activity_weather.xml中用include来继承。
新建title.xml作为头布局,该头布局中放置两个TextView,分别用于显示城市名和更新时间,代码如下所示:
<?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_black_24dp"/>
<!-- 显示城市名-->
<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>
新建now.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">
<!--显示当前气温-->
<RelativeLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="3">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/degree_text"
android:layout_gravity="end"
android:textSize="60sp"
android:textColor="#fff"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
/>
</RelativeLayout>
<!--显示天气概况-->
<RelativeLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="2">
<ImageView
android:id="@+id/weather_info_image"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_centerHorizontal="true"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:id="@+id/weather_info_text"
android:layout_gravity="end"
android:textSize="20sp"
android:textColor="#fff"
android:layout_below="@id/weather_info_image"
android:layout_centerHorizontal="true"
/>
</RelativeLayout>
</LinearLayout>
新建others.xml来显示湿度和降雨量,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@color/transparent">
<!--湿度-->
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_centerInParent="true">
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:src="@mipmap/icon_hum"
/>
<TextView
android:id="@+id/hum_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="15sp"/>
</LinearLayout>
</RelativeLayout>
<!--降雨量-->
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_centerInParent="true">
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:src="@mipmap/icon_rainfall"
android:textColor="#fff"/>
<TextView
android:id="@+id/cloud_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:layout_gravity="center"
android:textColor="#fff"
android:textSize="15sp"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
新建forecast.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="@drawable/layout_shape">
<!--定义了一个标题-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="预报"
android:textColor="@color/text"
android:textSize="20sp"/>
<!--未来几天天气信息的布局,需要根据服务器返回的数据在代码中动态添加-->
<LinearLayout
android:id="@+id/forecast_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
新建未来天气的子项布局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="@color/text"/>
<!--天气图片-->
<ImageView
android:id="@+id/info_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@mipmap/icon_100d"/>
<!--天气概况-->
<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="@color/text"/>
<!--最高温度-->
<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="@color/text"/>
<!--最低气温-->
<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="@color/text"/>
</LinearLayout>
新建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="@drawable/layout_shape">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:text="生活建议"
android:textColor="@color/text"
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="@color/text"/>
<!--洗车指数-->
<TextView
android:id="@+id/cloth_ware_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="@color/text"/>
<!--运动建议-->
<TextView
android:id="@+id/sport_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="@color/text"/>
<TextView
android:id="@+id/flu_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="@color/text"/>
<TextView
android:id="@+id/uv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="@color/text"/>
<TextView
android:id="@+id/spi_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="@color/text"/>
</LinearLayout>
将以上编写的布局用include引入到activity_weather.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">
<!--实现抽屉式效果的布局DrawerLayout -->
<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">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_back"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/back_100d" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/iv_back"
android:layout_marginTop="-50dp"
android:scaleType="fitXY"
android:src="@mipmap/back"/>
<include layout="@layout/title"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_alignParentTop="true"
android:layout_marginTop="10dp"
android:id="@+id/layout_title"/>
<include layout="@layout/now"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_below="@id/layout_title"
android:id="@+id/layout_now"/>
<include layout="@layout/others"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_below="@id/layout_now"
android:id="@+id/layout_others"/>
<include layout="@layout/forecast"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_below="@id/layout_others"
android:id="@+id/layout_forecast"/>
<include layout="@layout/suggestion"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_below="@id/layout_forecast"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:id="@+id/layout_suggestion"/>
</RelativeLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.example.myweather.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"/>
</android.support.v4.widget.DrawerLayout>
</FrameLayout>
2.4 逻辑实现
2.4.1 将返回的json数据解析成HeWeather实体类
在Utility类中添加解析天气json的方法,如下所示:
/**
* 将返回的JSON数据解析成Weather实体类
* @param response
* @return
*/
public static Weather handleWeatherResponse(String response){
try {
//通过JSONObject和JSONArray将天气数据中的主体内容解析出来
JSONObject jsonObject = new JSONObject(response);
Log.i("Gson",jsonObject.toString());
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather6");
//在控制台打印jsonArray数据
String weatherContent = jsonArray.getJSONObject(0).toString();
Log.i("Gson",weatherContent);
//调用fromJson将JSON数据转换成Weather对象
return new Gson().fromJson(weatherContent, interfaces.heweather.com.interfacesmodule.bean.weather.Weather.class);
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
2.4.2 根据获得的天气信息动态获取天气图标和天气背景
在util包中新建
public class IconUtils {
/**
* 获取天气图标
*/
public static int getDayIconDark(String weather) {
int imageId;
switch (weather) {
case "100":
imageId = R.mipmap.icon_100d;
break;
case "101":
imageId = R.mipmap.icon_101d;
break;
case "102":
imageId = R.mipmap.icon_102d;
break;
case "103":
imageId = R.mipmap.icon_103d;
break;
case "104":
imageId = R.mipmap.icon_104d;
break;
case "200":
imageId = R.mipmap.icon_200d;
break;
case "201":
imageId = R.mipmap.icon_210d;
break;
case "202":
imageId = R.mipmap.icon_202d;
break;
case "203":
imageId = R.mipmap.icon_203d;
break;
case "204":
imageId = R.mipmap.icon_204d;
break;
case "205":
imageId = R.mipmap.icon_205d;
break;
case "206":
imageId = R.mipmap.icon_206d;
break;
case "207":
imageId = R.mipmap.icon_207d;
break;
case "208":
imageId = R.mipmap.icon_208d;
break;
case "209":
imageId = R.mipmap.icon_209d;
break;
case "210":
imageId = R.mipmap.icon_210d;
break;
case "211":
imageId = R.mipmap.icon_211d;
break;
case "212":
imageId = R.mipmap.icon_212d;
break;
case "213":
imageId = R.mipmap.icon_213d;
break;
case "300":
imageId = R.mipmap.icon_300d;
break;
case "301":
imageId = R.mipmap.icon_301d;
break;
case "302":
imageId = R.mipmap.icon_302d;
break;
case "303":
imageId = R.mipmap.icon_303d;
break;
case "304":
imageId = R.mipmap.icon_304d;
break;
case "305":
imageId = R.mipmap.icon_305d;
break;
case "306":
imageId = R.mipmap.icon_306d;
break;
case "307":
imageId = R.mipmap.icon_307d;
break;
case "308":
imageId = R.mipmap.icon_308d;
break;
case "309":
imageId = R.mipmap.icon_309d;
break;
case "310":
imageId = R.mipmap.icon_310d;
break;
case "311":
imageId = R.mipmap.icon_311d;
break;
case "312":
imageId = R.mipmap.icon_312d;
break;
case "313":
imageId = R.mipmap.icon_313d;
break;
case "314":
imageId = R.mipmap.icon_314d;
break;
case "315":
imageId = R.mipmap.icon_315d;
break;
case "316":
imageId = R.mipmap.icon_316d;
break;
case "317":
imageId = R.mipmap.icon_317d;
break;
case "318":
imageId = R.mipmap.icon_318d;
break;
case "399":
imageId = R.mipmap.icon_399d;
break;
case "400":
imageId = R.mipmap.icon_400d;
break;
case "401":
imageId = R.mipmap.icon_401d;
break;
case "402":
imageId = R.mipmap.icon_402d;
break;
case "403":
imageId = R.mipmap.icon_403d;
break;
case "404":
imageId = R.mipmap.icon_404d;
break;
case "405":
imageId = R.mipmap.icon_405d;
break;
case "406":
imageId = R.mipmap.icon_406d;
break;
case "407":
imageId = R.mipmap.icon_407d;
break;
case "408":
imageId = R.mipmap.icon_408d;
break;
case "409":
imageId = R.mipmap.icon_409d;
break;
case "410":
imageId = R.mipmap.icon_410d;
break;
case "499":
imageId = R.mipmap.icon_499d;
break;
case "500":
imageId = R.mipmap.icon_500d;
break;
case "501":
imageId = R.mipmap.icon_501d;
break;
case "502":
imageId = R.mipmap.icon_502d;
break;
case "503":
imageId = R.mipmap.icon_503d;
break;
case "504":
imageId = R.mipmap.icon_504d;
break;
case "507":
imageId = R.mipmap.icon_507d;
break;
case "508":
imageId = R.mipmap.icon_508d;
break;
case "509":
imageId = R.mipmap.icon_509d;
break;
case "510":
imageId = R.mipmap.icon_510d;
break;
case "511":
imageId = R.mipmap.icon_511d;
break;
case "512":
imageId = R.mipmap.icon_512d;
break;
case "513":
imageId = R.mipmap.icon_513d;
break;
case "514":
imageId = R.mipmap.icon_514d;
break;
case "515":
imageId = R.mipmap.icon_515d;
break;
case "900":
imageId = R.mipmap.icon_900d;
break;
case "901":
imageId = R.mipmap.icon_901d;
break;
case "999":
imageId = R.mipmap.icon_999d;
break;
default:
imageId = R.mipmap.icon_100d;
break;
}
return imageId;
}
/**
* 获取背景
*/
public static int getDayBack(String weather) {
int imageId;
switch (weather) {
case "100":
imageId = R.mipmap.back_100d;
break;
case "101":
imageId = R.mipmap.back_101d;
break;
case "102":
imageId = R.mipmap.back_102d;
break;
case "103":
imageId = R.mipmap.back_103d;
break;
case "104":
imageId = R.mipmap.back_104d;
break;
case "200":
imageId = R.mipmap.back_200d;
break;
case "201":
imageId = R.mipmap.back_210d;
break;
case "202":
imageId = R.mipmap.back_202d;
break;
case "203":
imageId = R.mipmap.back_203d;
break;
case "204":
imageId = R.mipmap.back_204d;
break;
case "205":
imageId = R.mipmap.back_205d;
break;
case "206":
imageId = R.mipmap.back_206d;
break;
case "207":
imageId = R.mipmap.back_207d;
break;
case "208":
imageId = R.mipmap.back_208d;
break;
case "209":
imageId = R.mipmap.back_209d;
break;
case "210":
imageId = R.mipmap.back_210d;
break;
case "211":
imageId = R.mipmap.back_211d;
break;
case "212":
imageId = R.mipmap.back_212d;
break;
case "213":
imageId = R.mipmap.back_213d;
break;
case "300":
imageId = R.mipmap.back_300d;
break;
case "301":
imageId = R.mipmap.back_301d;
break;
case "302":
imageId = R.mipmap.back_302d;
break;
case "303":
imageId = R.mipmap.back_303d;
break;
case "304":
imageId = R.mipmap.back_304d;
break;
case "305":
imageId = R.mipmap.back_305d;
break;
case "306":
imageId = R.mipmap.back_306d;
break;
case "307":
imageId = R.mipmap.back_307d;
break;
case "308":
imageId = R.mipmap.back_308d;
break;
case "309":
imageId = R.mipmap.back_309d;
break;
case "310":
imageId = R.mipmap.back_310d;
break;
case "311":
imageId = R.mipmap.back_311d;
break;
case "312":
imageId = R.mipmap.back_312d;
break;
case "313":
imageId = R.mipmap.back_313d;
break;
case "314":
imageId = R.mipmap.back_314d;
break;
case "315":
imageId = R.mipmap.back_315d;
break;
case "316":
imageId = R.mipmap.back_316d;
break;
case "317":
imageId = R.mipmap.back_317d;
break;
case "318":
imageId = R.mipmap.back_318d;
break;
case "399":
imageId = R.mipmap.back_399d;
break;
case "400":
imageId = R.mipmap.back_400d;
break;
case "401":
imageId = R.mipmap.back_401d;
break;
case "402":
imageId = R.mipmap.back_402d;
break;
case "403":
imageId = R.mipmap.back_403d;
break;
case "404":
imageId = R.mipmap.back_404d;
break;
case "405":
imageId = R.mipmap.back_405d;
break;
case "406":
imageId = R.mipmap.back_406d;
break;
case "407":
imageId = R.mipmap.back_407d;
break;
case "408":
imageId = R.mipmap.back_408d;
break;
case "409":
imageId = R.mipmap.back_409d;
break;
case "410":
imageId = R.mipmap.back_410d;
break;
case "499":
imageId = R.mipmap.back_499d;
break;
case "500":
imageId = R.mipmap.back_500d;
break;
case "501":
imageId = R.mipmap.back_501d;
break;
case "502":
imageId = R.mipmap.back_502d;
break;
case "503":
imageId = R.mipmap.back_503d;
break;
case "504":
imageId = R.mipmap.back_504d;
break;
case "507":
imageId = R.mipmap.back_507d;
break;
case "508":
imageId = R.mipmap.back_508d;
break;
case "509":
imageId = R.mipmap.back_509d;
break;
case "510":
imageId = R.mipmap.back_510d;
break;
case "511":
imageId = R.mipmap.back_511d;
break;
case "512":
imageId = R.mipmap.back_512d;
break;
case "513":
imageId = R.mipmap.back_513d;
break;
case "514":
imageId = R.mipmap.back_514d;
break;
case "515":
imageId = R.mipmap.back_515d;
break;
case "900":
imageId = R.mipmap.back_900d;
break;
case "901":
imageId = R.mipmap.back_901d;
break;
case "999":
imageId = R.mipmap.back_999d;
break;
default:
imageId = R.mipmap.back_100d;
break;
}
return imageId;
}
}
2.4.3 天气信息的请求和展示
在WeatherActivity中请求天气数据,并将数据展示到界面上,如下所示:
public class WeatherActivity extends AppCompatActivity {
/**
* 该活动用于请求天气数据并将数据展示到界面
*/
public DrawerLayout drawerLayout;
private Button navButton;
public SwipeRefreshLayout swipeRefresh;
private String mWeatherId;
private ScrollView weatherLayout;
private TextView titleCity;
private TextView titleUpdateTime;
private TextView degreeText; //气温
private TextView weatherInfoText; //天气概况
private LinearLayout forecastLayout;
private TextView humText;
private TextView cloudText;
private TextView comfortText;
private TextView clothWareText;
private TextView sportText;
private TextView fluText;
private TextView uvText;
private TextView spiText;
private ImageView weatherInfoImage;
private ImageView ivBack;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
initView();
swipeRefresh.setColorSchemeColors(getResources().getColor(R.color.colorPrimary));
navButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.openDrawer(GravityCompat.START); //打开滑动菜单
}
});
//定义缓存对象
SharedPreferences prefs= PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = prefs.getString("weather",null);
if (weatherString!=null){
//有缓存时直接解析天气数据
Weather weather = Utility.handleWeatherResponse(weatherString);
mWeatherId = weather.getBasic().getCid();
showWeatherInfo(weather); //传入缓存中的weather并展示
}
else {
//无缓存时去服务器查询天气信息
mWeatherId = getIntent().getStringExtra("weather_id"); //从Intent中读取城市代码
weatherLayout.setVisibility(View.INVISIBLE); // 隐藏ScrollView(即天气显示界面)
requestWeather(mWeatherId); //通过同类中的requestWeather方法请求天气数据
}
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { //设置刷新监听器
@Override
public void onRefresh() {
requestWeather(mWeatherId); //刷新时请求天气数据
}
});
}
/**
* 根据城市代号请求天气信息
* @param weatherId
*/
public void requestWeather(final String weatherId) {
//拼装接口地址
String weatherUrl = "https://free-api.heweather.net/s6/weather?location="+
weatherId+"&key=your_key";
Log.i("url",weatherUrl);
//调用HttpUtil.sendOkHttpRequests方法来向url发送请求
HttpUtil.sendOkHttpRequests(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();
swipeRefresh.setRefreshing(false);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseText = response.body().string(); //服务器响应城市的天气信息,以json格式返回
final Weather weather = Utility.handleWeatherResponse(responseText); //将json数据转换成weather对象
runOnUiThread(new Runnable() {
@Override
public void run() {
if (weather!=null&&"ok".equals(weather.getStatus())) {
//如果weather不为空且请求返回的状态为ok则将返回的数据缓存到SharedPreference中
SharedPreferences.Editor editor = PreferenceManager.
getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.putString("weather",responseText);
editor.apply();
mWeatherId = weather.getBasic().getCid();
showWeatherInfo(weather); //调用首位Weather方法进行内容显示
}
else {
Toast.makeText(WeatherActivity.this,"获取天气信息失败",
Toast.LENGTH_SHORT).show();
}
swipeRefresh.setRefreshing(false);
}
});
}
});
}
/**
* 从weather对象中获取数据,然后显示到相应的控件上
* @param weather
*/
private void showWeatherInfo(Weather weather) {
String cityName = weather.getBasic().getLocation();
String updateTime = weather.getUpdate().getLoc().split(" ")[1];
String degree = weather.getNow().getTmp();
String weatherInfo = weather.getNow().getCond_txt();
titleCity.setText(cityName);
titleUpdateTime.setText(updateTime);
degreeText.setText(degree+"℃");
weatherInfoText.setText(weatherInfo);
String weatherCode = weather.getNow().getCond_code();
IconUtils iconUtils = new IconUtils();
int iconId = iconUtils.getDayIconDark(weatherCode);
int backId = iconUtils.getDayBack(weatherCode);
weatherInfoImage.setImageResource(iconId);
ivBack.setImageResource(backId);
forecastLayout.removeAllViews();
for (ForecastBase forecast : weather.getDaily_forecast()){
View view = LayoutInflater.from(this).inflate(R.layout.forecast_item,forecastLayout,false);
TextView dateText = (TextView)view.findViewById(R.id.date_text);
ImageView infoImage = (ImageView) view.findViewById(R.id.info_iv);
TextView infoText = (TextView)view.findViewById(R.id.info_text);
TextView maxText = (TextView)view.findViewById(R.id.max_text);
TextView minText = (TextView)view.findViewById(R.id.min_text);
//获取对应天气信息的Code,得到对应的天气图标id
String infoCode = forecast.getCond_code_d();
IconUtils infoUtils = new IconUtils();
int infoId = infoUtils.getDayIconDark(infoCode);
dateText.setText(forecast.getDate());
infoImage.setImageResource(infoId); //设置天气图标
infoText.setText(forecast.getCond_txt_d());
maxText.setText(forecast.getTmp_max());
minText.setText(forecast.getTmp_min());
forecastLayout.addView(view);
}
humText.setText("湿度" + weather.getNow().getHum());
cloudText.setText("降雨量" + weather.getNow().getPcpn());
String comf = "舒适度:" +weather.getLifestyle().get(0).getTxt();
String drsg = "穿衣指数:" +weather.getLifestyle().get(1).getTxt();
String sport = "运动建议:" +weather.getLifestyle().get(4).getTxt();
String flu ="感冒指数:"+weather.getLifestyle().get(2).getTxt();
String uv = "紫外线指数:"+weather.getLifestyle().get(5).getTxt();
String spi = "空气污染扩散条件指数:"+weather.getLifestyle().get(7).getTxt();
comfortText.setText(comf);
clothWareText.setText(drsg);
sportText.setText(sport);
fluText.setText(flu);
uvText.setText(uv);
spiText.setText(spi);
weatherLayout.setVisibility(View.VISIBLE); //ScrollView设为可见
Intent intent = new Intent(this, AutoUpdateService.class);
startService(intent);
}
//初始化控件
private void initView() {
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
navButton = (Button) findViewById(R.id.nav_button);
swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
weatherLayout = (ScrollView) findViewById(R.id.weather_layout);
titleCity = (TextView)findViewById(R.id.title_city);
titleUpdateTime = (TextView)findViewById(R.id.title_update_time);
degreeText = (TextView)findViewById(R.id.degree_text);
weatherInfoText = (TextView)findViewById(R.id.weather_info_text);
forecastLayout = (LinearLayout)findViewById(R.id.forecast_layout);
humText = (TextView)findViewById(R.id.hum_text);
cloudText = (TextView)findViewById(R.id.cloud_text);
comfortText = (TextView) findViewById(R.id.comfort_text);
clothWareText = (TextView) findViewById(R.id.cloth_ware_text);
sportText = (TextView)findViewById(R.id.sport_text);
weatherInfoImage = (ImageView) findViewById(R.id.weather_info_image);
ivBack = (ImageView) findViewById(R.id.iv_back);
fluText = (TextView) findViewById(R.id.flu_text);
uvText = (TextView) findViewById(R.id.uv_text);
spiText = (TextView) findViewById(R.id.spi_text);
}
}
2.4.4 打开App后显示上次所选城市的信息
修改MainActivity中的代码,使打开App时从缓存中读出上次所选城市的信息,如下所示:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//从缓存中读取数据
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs.getString("weather", null) != null){
//如果缓存中的数据不为空,说明已经请求过天气数据,不用再选择城市,直接跳转到WeatherActivity
Intent intent = new Intent(this, WeatherActivity.class);
startActivity(intent);
finish();
}
}
}
3. 运行情况
运行结果如下图所示: