小格格的第一篇博客

标签: 博客


第一篇博客——安卓酷欧天气

第一部分——创建数据库和表

首先在com.coolweather.android包下创建 db geon service util包用于存放代码。
编辑build.gradle文件,在dependencise闭包中添加如下代码来声明依赖库:

  implementation 'org.litepal.android:core:1.4.1'
  implementation 'com.squareup.okhttp3:okhttp:3.4.1'
  implementation 'com.google.code.gson:gson:2.7'
  implementation 'com.github.bumptech.glide:glide:3.7.0'

在db包中建立Province类用于存放省级信息

package com.weather.weather.db;

import org.litepal.crud.DataSupport;

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包用于存放市级信息

package com.weather.weather.db;
import org.litepal.crud.DataSupport;
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;
    }
}

继续创建County类用于存放县级信息

package com.weather.weather.db;
import org.litepal.crud.DataSupport;

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;
    }
}

然后在litepal文件中指定数据库名称和版本。代码如下。

<litepal>
    <dbname value="cool_weather"/>
    <version value="1"/>
    <list>
        <mapping class="com.weather.weather.db.Province"/>
        <mapping class="com.weather.weather.db.City"/>
        <mapping class="com.weather.weather.db.County"/>
    </list>
</litepal>

接下来配置LitePalApplication。修改AndroidMainifest中的代码如下:

    <application
        android:name="org.litepal.LitePalApplication"
        android:allowBackup="true"

至此,第一阶段完成,开始进入第二阶段的开发。

第二阶段——遍历全国省市县数据

在util包下增加HttpUtil类,输入用于和服务器交互的代码,如下:

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);
    }

接下来我们需要提供一个工具类来解析和处理服务器返回的JSON格式的数据,在util包下新建一个Utility类,代码如下;

package com.weather.weather.util;

import android.text.TextUtils;

import com.google.gson.Gson;
import com.weather.weather.db.City;
import com.weather.weather.db.County;
import com.weather.weather.db.Province;
import com.weather.weather.gson.Weather;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

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;
        }

下面开始编写界面。直接写在布局里。在layout里创建choose_area布局。代码如下;

<?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/abc"/>
    </RelativeLayout>
    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

接下来编写用于遍历省市县数据的碎片。新建ChooseAreaFragment继承自Fragment。代码如下:

public class  ChooseAreaFragment extends Fragment {
    // vars
    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;
    // methodso
    @Override
    public View onCreateView(LayoutInflater inflater , ViewGroup container, 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.back_button);
        listView = (ListView) view.findViewById(R.id.list_view);
        adapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,dataList);
        listView.setAdapter(adapter);
        return view;
    }
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        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);
                    queryCounties();
                }else if(currentLevel==LEVEL_COUNTY){
                    String weatherId=countyList.get(position).getWeatherId();

                    if(getActivity() instanceof MainActivity){
                    Intent intent=new Intent(getActivity(),WeatherActivity.class);
                    intent.putExtra("weather_id",weatherId);
                    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);
        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, "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 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) {
                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, "county");
        }
    }
/*根据传入的地址和类型从服务器上去查询省县市数据*/
    private void queryFromServer(String address,final String type){
        showProgressDialog();
        HttpUtil.sendOkHttpRequest(address, new Callback() {
            @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();
                            }
                        }
                    });
                }
            }
            @Override
            public void onFailure(Call call, IOException e){
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        closeProgressDialog();
                        Toast.makeText(getContext(),"加载失败...",Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });
    }
    /*显示进度对话框*/
    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中的代码如下:

/*布局文件很简单,定义一个FramerLayout然后将ChooseAreaFragment添加进来,让他充满整个文件*/
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <fragment
            android:id="@+id/choose_area_fragment"
            android:name="com.weather.weather.ChooseAreaFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
  </FrameLayout>

修改styles中的代码如下所示:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

下面我们来声明运行程序所需的权限,修改AndroidManifest中的代码,如下:

 <uses-permission android:name="android.permission.INTERNET" />

接下来我们可以运行一下程序,会出现如下截图
在这里插入图片描述
可以看到全国所有的省级数据已经显示出来,还可以继续查看市级数据和县级数据,并且可以返回,这里就不一一列举截图。
下面开始第三阶段的任务。

第三阶段——显示天气信息

首先来定义GSON 实体类。在gson包下创建Basic aqi now suggestion和daily_forecast类,并将其定义为实体类,Basic类代码如下,其余四个类代码相似,就不一一列举了;

public class Basic {
    @SerializedName("city")
    public String cityName;

    @SerializedName("id")
    public String weatherId;

    public Update update;

    public class Update{
        @SerializedName("loc")
        public String updateTime;
    }
}

接下来我们创建一个总的实例类,来引用刚刚创建的各个实例类。在gson包下新建Weather类。代码如下:

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;
}

接下来我们来编写天气界面
在layout里面新建一个title作为头布局,在头布局中放置两个TextView一个居中显示城市名,一个居右显示时间,然后新建一个now作为当前天气的布局,格式同上,一个显示当前气温,一个显示当前概况。然后新建一个forecast作为未来几天天气信息的布局,再创建一个forecast_item的文件,用于显示未来天气的子项布局,然后新建aqi作为空气质量信息的布局,新建suggestion作为生活建议的布局,(由于这部分代码及其冗长且较为简单,这里就不列举出来了)
这样我们就把天气界面上每个部分的布局文件写好了,接下来把他们引入到activity_weather中,代码如下:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary">
    
        <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:overScrollMode="never">

            <include layout="@layout/title"/>

            <include layout="@layout/now"/>

            <include layout="@layout/forecast"/>

            <include layout="@layout/aqi"/>

            <include layout="@layout/suggestion"/>

        </LinearLayout>

    </ScrollView>

接下来,把天气显示到界面上。
首先在Utility中添加一个关于解析天气JSON数据的办法,代码如下:

 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;
        }

接下来的工作是我们如何在活动中去请求天气数据,以及将数据展示到界面上,修改WeatherActivity中的代码(代码过于冗长,不便展示)
现在我们要做的就是如何从省市县列表跳转到天气界面,修改ChooseAreaFragment中的代码,如下

else if(currentLevel==LEVEL_COUNTY){
                    String weatherId=countyList.get(position).getWeatherId();

                    if(getActivity() instanceof MainActivity){
                    Intent intent=new Intent(getActivity(),WeatherActivity.class);
                    intent.putExtra("weather_id",weatherId);
                    startActivity(intent);
                    getActivity().finish();
                }

另外我们还需要在MainActivity中加入一个缓存数据的判断,修改MainActivity中的代码:

 SharedPreferences prefs= PreferenceManager.getDefaultSharedPreferences(this);
        if(prefs.getString("weather",null)!=null){
            Intent intent=new Intent(this,WeatherActivity.class);
            startActivity(intent);
            finish();
        }

现在运行一下程序,会出现如下界面在这里插入图片描述
我们还可以向下滑动查看更多天气信息
接下来我们来解决每天的界面都一成不变的问题,让他变得更加美观。
**修改activity_weather中的代码,加入如下代码:

<ImageView
        android:id="@+id/bing_pic_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"/>

并修改WeatherActivity中的代码,如下:**

   String bingPic=prefs.getString("bing_pic",null);
        if(bingPic!=null){
            Glide.with(this).load(bingPic).into(bingPicImg);
        }else {
            loadBingPic();
        }
        ...
        private void loadBingPic(){
        String requestBingPic="http://guolin.tech/api/bing_pic";
        HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {

            @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);
                    }
                });

            }
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();

            }
        });
    }

接下来是第四阶段——手动更新天气和切换城市

我们采取下拉刷新的方式。修改activity_weather中的代码如下:

 <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

并修改WeatherActivity中的代码,键入更新天气的处理逻辑
现在运行一下程序,在屏幕的主界面向下拖动就可以进行更新了。
接下来我们来实现切换城市的功能。只要我们在天气界面的布局中引入前面我们创建的碎片就可以实现这个功能了
接下来我们加入滑动菜单功能。
很简单,代码不再细说。

现在我们可以用滑动菜单来切换城市啦

第五阶段——后台自动更新天气

创建AutoUpdateService,代码如下所示

public class AutoUpdateService extends Service {

    @Override
    public IBinder onBind(Intent intent){
        return null;
    }

    @Override
    public int onStartCommand(Intent intent,int flags,int startId){
        updateWeather();
        updateBingPic();
        AlarmManager manager=(AlarmManager)getSystemService(ALARM_SERVICE);
        int anHour=8*60*60*1000;
        long triggerAtTime= SystemClock.elapsedRealtime()+anHour;
        Intent i =new Intent(this,AutoUpdateService.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);
    }

    private void updateWeather(){
        SharedPreferences prefs= PreferenceManager.getDefaultSharedPreferences(this);
        String weatherString=prefs.getString("weather",null);
        if(weatherString!=null){
            Weather weather= Utility.handleWeatherResponse(weatherString);
            String weatherId=weather.basic.weatherId;

            String weatherUrl="http://guolin.tech/api/weather?cityid="+
                    weatherId+"&key=bc0418b57b2d4918819d3947ac1285d9";
            HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    String responseText=response.body().string();
                    Weather weather=Utility.handleWeatherResponse(responseText);
                    if(weather!=null&&"ok".equals(weather.status)){
                        SharedPreferences.Editor editor=PreferenceManager.
                                getDefaultSharedPreferences(AutoUpdateService.this).edit();
                        editor.putString("weather",responseText);
                        editor.apply();
                    }

                }
                @Override
                public void onFailure(Call call, IOException e) {
                    e.printStackTrace();

                }

            });
        }
    }

    private void updateBingPic(){
        String requestBingPic="http://guolin.tech/api/bing_pic";
        HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String bingPic=response.body().string();
                SharedPreferences.Editor editor=PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();
                editor.putString("bing_pic",bingPic);
                editor.apply();
            }

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }
        });
    }
}

接下来只需要激活这个功能就可以实现天气自动更新啦
我们还可以来修改这个软件的图标和名称,看自己喜好啦,不做过多赘述。

最后,第一次写博客,和大佬写的差的很多,不知道哪里是重点,代码放太多也不好,不放也不好,最后还有点不耐烦,省了好多代码,应该没人会看,但是还是要小小的纪念一下,嘿嘿。

原文链接:加载失败,请重新获取