一、项目框架的搭建
1、在git上新建一个coolweather的仓库
2、新建一个Coolweather项目,添加到git版本管理
3、在java中新建几个包,db:用于存放数据库模型相关的代码,gson:用于存放GSon相关的代码,service:用于存放服务相关的代码,util包用于存放工具相关代码
4、添加项目依赖的第三方,在app/build.gradle
依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'org.litepal.android:core:2.0.0'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.github.bumptech.glide:glide:4.7.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
仓库添加
allprojects {
repositories {
mavenCentral()
google()
jcenter()
}
}
说明:
implementation 'org.litepal.android:core:2.0.0'
是数据库操作的框架
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
网络请求框架
implementation 'com.google.code.gson:gson:2.8.5'
JSON解析框架
implementation 'com.github.bumptech.glide:glide:4.7.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
为加载图片的框架
二、数据库表的设计
1、数据获取
省份获取接口:
http://guolin.tech/api/china
获取的数据如下:
[{"id":1,"name":"北京"},{"id":2,"name":"上海"}]
省下面对应的市获取接口
http://guolin.tech/api/china/22,其中22
为省份的id,数据结构如下:
[{"id":191,"name":"长沙"},{"id":192,"name":"湘潭"},{"id":193,"name":"株洲"},{"id":194,"name":"衡阳"},{"id":195,"name":"郴州"},{"id":196,"name":"常德"},{"id":197,"name":"益阳"},{"id":198,"name":"娄底"},{"id":199,"name":"邵阳"},{"id":200,"name":"岳阳"},{"id":201,"name":"张家界"},{"id":202,"name":"怀化"},{"id":203,"name":"永州"},{"id":204,"name":"吉首"}]
获取区对应的信息
http://guolin.tech/api/china/22/191,其中191为市的id
[{"id":1422,"name":"长沙","weather_id":"CN101250101"},{"id":1423,"name":"宁乡","weather_id":"CN101250102"},{"id":1424,"name":"浏阳","weather_id":"CN101250103"},{"id":1425,"name":"马坡岭","weather_id":"CN101250104"},{"id":1426,"name":"望城","weather_id":"CN101250105"}]
最后通过weather_id 获取天气信息,使用和风天气的api
https://console.heweather.com/register?role=personal,注册免费
天气信息的获取
https://free-api.heweather.com/s6/weather/now?location=CN101250101&key=22c4dc1c0a3245c7a97e7c2543b9781a
数据如下:
{"HeWeather6":[{"basic":{"cid":"CN101250101","location":"长沙","parent_city":"长沙","admin_area":"湖南","cnty":"中国","lat":"28.19408989","lon":"112.98227692","tz":"+8.00"},"update":{"loc":"2018-07-06 16:49","utc":"2018-07-06 08:49"},"status":"ok","now":{"cloud":"75","cond_code":"104","cond_txt":"阴","fl":"31","hum":"85","pcpn":"0.0","pres":"1000","tmp":"29","vis":"13","wind_deg":"293","wind_dir":"西北风","wind_sc":"3","wind_spd":"19"}}]}
2、数据库表实现
新建Province类 继承LitePalSupport
类
public class Province extends LitePalSupport {
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类
public class City extends LitePalSupport {
private int id;
private String cityName;
private int cityCode;
private int provinceId;
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;
}
}
Country类
public class Country extends LitePalSupport {
private int id;
private String countryName;
private String weatherId;
private int cityId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCountryName() {
return countryName;
}
public void setCountryName(String countryName) {
this.countryName = countryName;
}
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;
}
}
在app/src/main 目录在新建assets 文件,在改目录下新建一个litepal.xml文件,编辑内容如下:
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<!--
Define the database name of your application.
By default each database name should be end with .db.
If you didn't name your database end with .db,
LitePal would plus the suffix automatically for you.
For example:
<dbname value="demo" />
-->
<dbname value="cool_weather" />
<!--
Define the version of your database. Each time you want
to upgrade your database, the version tag would helps.
Modify the models you defined in the mapping tag, and just
make the version value plus one, the upgrade of database
will be processed automatically without concern.
For example:
<version value="1" />
-->
<version value="1" />
<list>
<mapping class="com.example.yangjie.coolweather.db.Province" />
<mapping class="com.example.yangjie.coolweather.db.City" />
<mapping class="com.example.yangjie.coolweather.db.Country" />
</list>
<!--
Define where the .db file should be. "internal" means the .db file
will be stored in the database folder of internal storage which no
one can access. "external" means the .db file will be stored in the
path to the directory on the primary external storage device where
the application can place persistent files it owns which everyone
can access. "internal" will act as default.
For example:
<storage value="external" />
-->
</litepal>
最后需要在配置一下LitePalApplication,修改如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yangjie.coolweather">
<application
android:name="org.litepal.LitePalApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3、自定义Fragment
首先定义一个线性布局如下
<?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="?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"
android:text="标题"
/>
<Button
android:id="@+id/btn_back"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/btn_back"
/>
</RelativeLayout>
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
这里自定义了导航条,所以需要修改style.xml文件,修改如下:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
新建一个ChooseAreaFragment继承Fragment,代码如下:
public class ChooseAreaFragment extends Fragment {
public static final int LEVEL_PROVINCE = 0;
public static final int LEVEL_CITY = 1;
public static final int LEVEL_COUNTRY = 2;
private static final String TYPE_PROVINCE = "province";
private static final String TYPE_City = "city";
private static final String TYPE_Country = "country";
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<Country> countryList;
private Province selectedProvince;
private City selectedCity;
private int currentLevel;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.choose_area,container,false);
titleText = (TextView)view.findViewById(R.id.title_text);
backButton = (Button)view.findViewById(R.id.btn_back);
listView = (ListView)view.findViewById(R.id.listView);
adapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,dataList);
listView.setAdapter(adapter);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (currentLevel == LEVEL_PROVINCE){
selectedProvince = provinceList.get(i);
queryCities();
}else if (currentLevel == LEVEL_CITY){
selectedCity = cityList.get(i);
queryCounties();
}
}
});
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (currentLevel == LEVEL_COUNTRY){
queryCities();
}else if (currentLevel == LEVEL_CITY){
queryProinces();
}
}
});
queryProinces();
}
private void queryCounties() {
titleText.setText(selectedCity.getCityName());
backButton.setVisibility(View.VISIBLE);
countryList = LitePal.where("cityid = ?",String.valueOf(selectedCity.getId())).find(Country.class);
if (countryList.size()>0){
dataList.clear();
for (Country country:countryList){
dataList.add(country.getCountryName());
}
adapter.notifyDataSetChanged();;
listView.setSelection(0);
currentLevel = LEVEL_COUNTRY;
}else {
int provinceCode = selectedProvince.getProvinceCode();
int cityCode = selectedCity.getCityCode();
String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
queryFromServer(address,TYPE_Country);
}
}
private void queryCities() {
titleText.setText(selectedProvince.getProvinceName());
backButton.setVisibility(View.VISIBLE);
cityList = LitePal.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,TYPE_City);
}
}
private void queryProinces() {
titleText.setText("中国");
backButton.setVisibility(View.GONE);
provinceList = LitePal.findAll(Province.class);
if (provinceList.size()>0){
dataList.clear();
for (Province province:provinceList){
dataList.add(province.getProvinceName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
currentLevel = LEVEL_PROVINCE;
}else {
String address = "http://guolin.tech/api/china";
queryFromServer(address,TYPE_PROVINCE);
}
}
private void queryFromServer(String address,final String type){
showProgressDialog();
HttpUtil.sendHttpRequest(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();
Log.d(TAG, "onResponse: " + responseText);
boolean result = false;
if (type.equals(TYPE_PROVINCE)){
result = Utility.handleProvinceResponse(responseText);
}else if(type.equals(TYPE_City)){
result = Utility.handleCityResponse(responseText,selectedProvince.getId());
}else if(type.equals(TYPE_Country)){
result = Utility.handleCountryResponse(responseText,selectedCity.getId());
}
if (result){
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if (type.equals(TYPE_PROVINCE)){
queryProinces();
}else if(type.equals(TYPE_City)){
queryCities();
}else if(type.equals(TYPE_Country)){
queryCounties();
}
}
});
}
}
});
}
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_main.xml文件中添加Fragment布局如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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.yangjie.coolweather.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
三、展示天气数据
效果如下:
780FA754-69A3-440F-AFC3-297954E7F328.png
将上图的界面拆分成三个布局:1、导航部分(展示城市和更新时间)
2、现在天气信息 3、未来三天信息展示
1、导航部分,新建一个title.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"
android:id="@+id/relativeLayout">
<Button
android:id="@+id/nav_button"
android:layout_width="32dp"
android:layout_height="30dp"
android:background="@drawable/home"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="180dp"
android:layout_marginStart="180dp"
android:layout_marginTop="8dp"
android:text="北京"
android:textColor="#fff"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title_update_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:textColor="#fff"
android:textSize="16sp"
android:text="2018-7-10"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
2、天气信息部分
<?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:layout_margin="15dp"
>
<TextView
android:id="@+id/tmp_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:textSize="20sp"
android:textColor="#fff"
/>
</LinearLayout>
3、未来天气布局forcast.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: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:textColor="#fff"
android:text="预报"
android:textSize="20sp"
/>
<LinearLayout
android:id="@+id/forecast_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
</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="match_parent"
android:layout_margin="15dp"
>
<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:textColor="#fff"
/>
<TextView
android:id="@+id/tem_min"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:textColor="#fff"
/>
<TextView
android:id="@+id/tmp_max"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:textColor="#fff"
/>
<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"
/>
</LinearLayout>
网友评论