《第一行代码Andorid》阅读笔记-第十三章(最终章)

这一部分是天气API的笔记,这本书最后会让你做一个天气的app程序
其他的无关紧要的部分我就不写了,这是因为我原本的笔记是在飞书上面的,同步到CSDN上的流程稍显复杂

天气API

1. 项目结构

在这里插入图片描述
类:

  • MainActivity:主活动
  • WeatherActivity:城市天气活动
  • ChooseAreaFragment:选择城市活动

包:

  • db包用于存放数据库模型相关的代码;
  • gson包用于存放GSON模型相关的代码;
  • service包用于存放服务相关的代码;
  • util包用于存放工具相关的代码。

布局文件:

  • activity_main.xml是主活动布局,里面是一个FrameLayout包含着一个fragment碎片,叫做choose_area_fragment
  • activity_weather.xml是显示具体某一个城市天气的布局,里面分了五个模块,这五个模块分别通过标签导入相应的布局。
  • aqi.xml是空气质量模块布局
  • choose_area.xml是选择城市模块的布局
  • forecast.xml是近七天预报的大布局
  • forecast_item.xml是近七天预报的listView的具体一行的布局
  • my_list_item_1.xml
  • now.xml是显示当天天气的布局,两个TextView,一个用于显示当前气温,一个用于显示天气概况。
  • suggestion.xml作为生活建议信息的布局
  • title.xml作为头布局显示天气页面的头部

assets 包:
通常用于存储应用所需的原始资源文件,里面我们放了litepal.xml文件是用于配置 LitePal 数据库框架的配置文件。

2. 数据库的搭建与对应

  1. 我们在db包下建立Province、City、County这3个类,分别对应3张表:province、city、county。
    例:Province类
package com.coolweather.android.db;

import org.litepal.crud.LitePalSupport;

//书中继承的是DataSupport(已经弃用)
public class Province extends LitePalSupport {
    //id是每个实体类中都应该有的字段
    private int id;
    //provinceName记录省的名字
    private String provinceName;
    //provinceCode记录省的代号
    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;
    }
}

首先要继承LitePalSupport因为我们用的是LitePalSupport方法操作数据库。
然后给出各个字段以及getter和setter方法
2. 我们在assets目录下的litepal.xml文件中

<?xml version="1.0" encoding="utf-8"?>
<litepal>
    <dbname value="cool_weather" />
    <version value="1" />
    <list>
        <mapping class="com.coolweather.android.db.Province"/>
        <mapping class="com.coolweather.android.db.City"/>
        <mapping class="com.coolweather.android.db.County"/>
    </list>
</litepal>

将数据库名指定成cool_weather,数据库版本指定成1,并将Province、City和County这3个实体类添加到映射列表当中。

  • 元素是配置文件的根元素,它包含了整个配置的信息。
  • 元素指定了数据库的名称,这里设置为 “cool_weather”,表示数据库的名称为 “cool_weather”。
  • 元素指定了数据库的版本号,这里设置为 “1”,表示数据库的版本号为 1。
  • 元素包含了一个或多个 元素,用于指定数据库表与模型类之间的映射关系。
  • 元素用于指定一个数据库表与一个模型类之间的映射关系。在这里,有三个 元素,分别映射了三个模型类 com.coolweather.android.db.Province、com.coolweather.android.db.City 和 com.coolweather.android.db.County 分别与数据库中的三张表关联。
  1. 配置LitePalApplication,在AndroidManifest.xml中加入
android:name="org.litepal.LitePalApplication"

这样我们就将所有的配置都完成了,数据库和表会在首次执行任意数据库操作的时候自动创建。

3. 城市选择碎片

该功能的主要实现都在ChooseAreaFragment.java中,代码有多行。方法如下:

3.1 onCreateView 方法

这是 Fragment 的生命周期方法之一,用于创建并返回与该 Fragment 关联的视图。

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    //在onCreateView()方法中先是获取到了一些控件的实例,然后去初始化了ArrayAdapter,并将它设置为ListView的适配器。
    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(),R.layout.my_list_item_1,dataList);
    listView.setAdapter(adapter);
    return view;
}
  1. 设定布局文件
  2. 获取控件按钮(如 titleText(用于显示标题文本的 TextView)、backButton(用于返回的按钮 Button)、listView(用于显示地区列表的 ListView))
  3. 创建适配器实例并设置给listView。

3.2 onActivityCreated 方法

public void onActivityCreated(@Nullable 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);
                    Log.d("requestWeather", "intent.putExtra.weatherId: " + weatherId);
                    startActivity(intent);
                    getActivity().finish();
                } else if (getActivity() instanceof WeatherActivity) {
                    //instanceof 是 Java 中的一个关键字,它用于检查一个对象是否是某个类的一个实例。
                    WeatherActivity activity = (WeatherActivity) getActivity();
                    activity.drawerLayout.closeDrawers();
                    activity.swipeRefreshLayout.setRefreshing(true);
                    activity.requestWeather(weatherId);
                }
            }
        }
    });
    /**
     * 在返回按钮的点击事件里,会对当前ListView的列表级别进行判断。
     * 如果当前是县级列表,那么就返回到市级列表,
     * 如果当前是市级列表,那么就返回到省级表列表。
     * 当返回到省级列表时,返回按钮会自动隐藏,从而也就不需要再做进一步的处理了。
     */
    backButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (currentLevel == LEVEL_COUNTY) {
                queryCities();
            } else if (currentLevel == LEVEL_CITY) {
                queryProvince();
            }
        }
    });
    //调用了queryProvinces()方法,也就是从这里开始加载省级数据的。
    queryProvince();
}

这是另一个生命周期方法,用于在 Fragment 与 Activity 关联之后进行一些初始化工作。

  1. listView.setOnItemClickListener: 这里设置了 ListView 的点击事件监听器,当用户点击列表项时,会触发 onItemClick 方法。在这个方法中,根据当前的列表级别(currentLevel)执行不同的操作。具体操作如下:
  • 如果当前级别是省级列表 (LEVEL_PROVINCE),则获取用户点击的省份数据,然后查询对应的城市数据。
  • 如果当前级别是城市列表 (LEVEL_CITY),则获取用户点击的城市数据,然后查询对应的县区数据。
  • 如果当前级别是县区列表 (LEVEL_COUNTY),则获取用户点击的县区数据的天气 ID (weatherId),然后执行不同的操作:
    • 如果当前 Activity 是 MainActivity 的实例,就创建一个意图 (Intent) 跳转到 WeatherActivity,并将天气 ID 作为额外数据传递给 WeatherActivity,最后关闭当前 Activity。
    • 如果当前 Activity 是 WeatherActivity 的实例,就获取 WeatherActivity 的实例,然后执行一些 UI 操作,如关闭抽屉布局、设置刷新状态,并请求天气信息。
  1. backButton.setOnClickListener: 这里设置了返回按钮的点击事件监听器,当用户点击返回按钮时,会触发 onClick 方法。在这个方法中,根据当前的列表级别执行不同的操作:
  • 如果当前级别是县区列表 (LEVEL_COUNTY),则返回到城市列表。
  • 如果当前级别是城市列表 (LEVEL_CITY),则返回到省级列表。
  1. 最后,调用 queryProvince() 方法加载省级数据。这是在 onActivityCreated 方法中的末尾调用的,表示当 Fragment 第一次创建时就会加载省级数据。

3.3 queryProvince、queryCities、queryCounties 方法:

这些方法用于查询省、市和县的数据,并将数据显示在列表中。以queryCounties为例

private void queryCounties() {
    titleText.setText(selectedCity.getCityName());
    backButton.setVisibility(View.VISIBLE);
    countyList = LitePal.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;
        queryFromService(address,"county");
    }
}
  1. 设置标题和返回按钮:首先,设置标题为"中国",表示当前正在浏览中国的省份信息,同时将返回按钮设置为不可见(View.GONE),因为在省份列表级别,不需要返回上一级。
  2. 查询本地数据库:接下来,使用 LitePal 数据库框架的 LitePal.findAll(Province.class) 方法尝试从本地数据库中查询省份数据。如果数据库中有省份数据(provinceList.size() > 0),就进行以下操作:
  • 清空数据列表 dataList,以便后续加载数据。
  • 遍历 provinceList,将每个省份的名称添加到 dataList 中。
  • 通过调用 adapter.notifyDataSetChanged() 刷新适配器,以更新界面显示。
  • 将 ListView 的选中项置为第一项,即 listView.setSelection(0),确保用户看到的是省份列表的第一个省份。
  • 设置当前列表级别为省份级别(currentLevel = LEVEL_PROVINCE),以便后续的操作。
  1. 从服务器获取数据:如果本地数据库中没有省份数据,就通过向指定的服务器地址发送请求,请求中国省份数据。具体操作如下:
  • 构建服务器地址 address,这里使用了一个示例地址 “http://guolin.tech/api/china”,该地址指向一个提供了中国省市县数据的接口。
  • 调用 queryFromService(address, “province”) 方法,向服务器请求数据,第二个参数 “province” 表示请求省份数据。该方法的解释就在下面。

3.4 queryFromService 方法:

这个方法用于向服务器发送网络请求,查询省、市、县的数据。它会在请求过程中显示一个进度条对话框。根据请求的类型(省、市、县),从服务器返回的数据经过解析后将存储到本地数据库中。

private void queryFromService(String address,final String type) {
    showProgressDialog();
    HttpUtil.sendOkHttpRequest(address, new Callback() {
        @Override
        public void onResponse(@NonNull Call call, @NonNull 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());
            }
            /**
             * 在解析和处理完数据之后,再次调用了queryProvinces()方法来重新加载省级数据,
             * 由于queryProvinces()方法牵扯到了UI操作,因此必须要在主线程中调用,
             * 这里借助了runOnUiThread()方法来实现从子线程切换到主线程。
             * 现在数据库中已经存在了数据,因此调用queryProvinces()就会直接将数据显示到界面上了。
             */
            if (result) {
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        closeProgressDialog();
                        if ("province".equals(type)) {
                            queryProvince();
                        } else if ("city".equals(type)) {
                            queryCities();
                        } else if ("county".equals(type)) {
                            queryCounties();
                        }
                    }
                });
            }
        }
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
            //通过runOnUiThread()方法回到主线程处理逻辑
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    closeProgressDialog();
                    Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
                }
            });
        }
    });
}
  1. 显示进度对话框:首先,调用 showProgressDialog() 方法显示一个进度对话框,用于提示用户正在加载数据。
  2. 发送网络请求:接着,使用 HttpUtil.sendOkHttpRequest(address, callback) 方法发送一个网络请求到指定的服务器地址(address),其中 callback 参数用于处理请求的响应。这里使用了 OkHttp 框架发送请求,该请求是异步的,不会阻塞主线程。
  3. 处理响应数据:在网络请求的回调方法中(onResponse() 和 onFailure()),根据不同的数据类型(type)来处理服务器响应的数据。
  • 如果 type 是 “province”,表示请求的是省份数据,调用 Utility.handleProvinceResponse(responseText) 方法来解析和处理省份数据。该方法会将解析后的数据存储到本地数据库中。
  • 如果 type 是 “city”,表示请求的是城市数据,调用 Utility.handleCityResponse(responseText, selectedProvince.getId()) 方法来解析和处理城市数据。其中 selectedProvince.getId() 表示当前选中的省份的 ID,用于关联城市数据与所属的省份。
  • 如果 type 是 “county”,表示请求的是县区数据,调用 Utility.handleCountyResponse(responseText, selectedCity.getId()) 方法来解析和处理县区数据。其中 selectedCity.getId() 表示当前选中的城市的 ID,用于关联县区数据与所属的城市。
  1. 切换到主线程:在数据解析和处理完毕后,根据处理结果(result)进行判断,如果处理成功,就通过 getActivity().runOnUiThread() 方法切换回主线程。
  • 如果请求的是省份数据,调用 queryProvince() 方法来重新加载省份数据。
  • 如果请求的是城市数据,调用 queryCities() 方法来加载城市数据。
  • 如果请求的是县区数据,调用 queryCounties() 方法来加载县区数据。
  • 同时,关闭进度对话框,提醒用户加载完成。
  1. 处理请求失败:如果网络请求失败(在 onFailure() 回调中),也需要切换回主线程,关闭进度对话框,并显示一个短暂的 Toast 提示用户加载失败。

3.5 showProgressDialog 和 closeProgressDialog 方法:

这些方法分别用于显示和关闭进度条对话框,以提供加载数据时的用户反馈。

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();
    }
}
  1. showProgressDialog() 方法:
  • 首先,它检查一个名为 progressDialog 的进度条对话框是否已经被创建(是否为 null)。
  • 如果 progressDialog 为 null,表示尚未创建进度条对话框,于是会新建一个。
  • 设置对话框的提示信息为 “正在加载…”,表明正在进行数据加载操作。
  • 通过 setCanceledOnTouchOutside(false) 方法,禁止用户点击对话框外部区域来取消对话框。
  • 最后,调用 progressDialog.show() 方法将对话框显示在界面上,让用户看到加载过程的进展。
  1. closeProgressDialog() 方法:
  • 这个方法用于关闭进度条对话框。
  • 首先检查 progressDialog 是否为 null,如果不为 null,表示进度条对话框已经被创建并显示在界面上。
  • 调用 progressDialog.dismiss() 方法关闭对话框,这会使进度条对话框消失。

4. 显示天气信息活动

该部分代码在Weather.java中

4.1 定义GSON实体类

我们需要将数据对应的实体类创建好,我们共建立了六个实体类分别如下

  • AQI:存放城市的AQI指数以及PM2.5指数
  • Basic:存放城市名、weatherId、update、updateTime
  • Forecast:存放date、temperature、more、最高气温、最低气温、info
    • Now:气温和天气情况
  • Suggestion:生活建议
  • Weather:近期每一天的天气情况,放了一个List

4.2 onCreate方法

这个方法主要是初始化界面控件、根据缓存数据或请求服务器数据来显示天气信息,支持下拉刷新天气数据,并获取并显示必应每日一图。

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_weather);
        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        navButton = (Button) findViewById(R.id.nav_button);
        navButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                drawerLayout.openDrawer(GravityCompat.START);
            }
        });
        if (Build.VERSION.SDK_INT >= 21) {
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }


        //初始化各种控件
        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);
        aqiText = (TextView) findViewById(R.id.aqi_text);
        pm25Text = (TextView) findViewById(R.id.pm25_text);
        comfortText = (TextView) findViewById(R.id.comfort_text);
        carWashText = (TextView) findViewById(R.id.car_wash_text);
        sportText = (TextView) findViewById(R.id.sport_text);
        bingPicImg = (ImageView) findViewById(R.id.pic_img);



        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        swipeRefreshLayout.setColorSchemeResources(com.google.android.material.R.color.design_default_color_primary);
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        String weatherString = prefs.getString("weather",null);
        if (weatherString != null) {
            //有缓存是直接解析天气数据
            Weather weather = Utility.handleWeatherResponse(weatherString);
            showWeatherInfo(weather);
        } else {
            //无缓存时去服务器查询天气

            String weatherId = getIntent().getStringExtra("weather_id");

            weatherLayout.setVisibility(View.INVISIBLE);
            requestWeather(weatherId);
        }
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
//                mWeatherId = mWeatherId.substring(mWeatherId.length() - 9);
                requestWeather(mWeatherId);
            }
        });
        String bingPic = prefs.getString("bing_pic",null);
        if (bingPic != null) {
            Glide.with(this).load(bingPic).into(bingPicImg);
//            Log.d("loadBingPic", "bingPic != null!!!!!!!!!!!!! "+ bingPic);
        } else {
            loadBingPic();
//            Log.d("loadBingPic", "loadBingPic启动 "+ bingPic);
        }


    }
  1. super.onCreate(savedInstanceState); 和 setContentView(R.layout.activity_weather);:
  • 这两行代码是通常的 Activity 生命周期中的启动和设置布局文件的操作。
  1. 初始化抽屉布局(DrawerLayout)和导航按钮(Button):
  • drawerLayout 用于创建一个抽屉式的布局,通常用于侧滑菜单等场景。
  • navButton 是一个按钮,点击它可以打开抽屉布局。
  1. 适配 Android 5.0 以上版本的状态栏:
  • 如果运行 Android 5.0 及以上版本,这段代码会设置状态栏的透明效果,使布局内容可以延伸到状态栏区域。此时状态栏会变成我们布局的一部分
  1. 初始化各种控件:
  • 这部分代码初始化了很多界面上的 TextView、LinearLayout、ImageView 等控件,这些控件用于显示天气信息和其他相关内容。
  1. 初始化下拉刷新控件(SwipeRefreshLayout):
  • swipeRefreshLayout 是一个下拉刷新控件,用于实现用户下拉刷新天气数据的功能。
  • 设置下拉刷新时的进度条颜色。
  1. 从 SharedPreferences 中获取天气数据缓存:
  • 通过 PreferenceManager 从默认的 SharedPreferences 获取缓存的天气数据,存储在 weatherString 变量中。
  1. 根据是否有缓存数据来显示天气信息或请求服务器数据:
  • 如果有缓存数据(weatherString != null),则使用 Utility.handleWeatherResponse(weatherString) 方法解析天气数据,并调用 showWeatherInfo(weather) 方法显示天气信息。
  • 如果没有缓存数据,说明需要向服务器请求数据,获取 weatherId(通过 getIntent().getStringExtra(“weather_id”) 获取)后,隐藏天气信息的布局(weatherLayout.setVisibility(View.INVISIBLE)),并调用 requestWeather(weatherId) 方法来请求天气数据。
  1. 设置下拉刷新监听器:
  • 当用户下拉刷新时,会触发 onRefresh 方法,该方法会重新请求天气数据。
  1. 获取必应每日一图(Bing 每日壁纸)的地址并显示:
  • 从 SharedPreferences 中获取 bingPic 数据,这是存储必应每日一图地址的缓存数据。
  • 如果有缓存数据,就使用 Glide 库加载图片并显示在 bingPicImg ImageView 中。
  • 如果没有缓存数据,则调用 loadBingPic() 方法来获取必应每日一图并显示。

4.3 loadBingPic方法

这段代码的主要目的是请求必应每日一图的图片数据,并将图片地址缓存起来,然后在主线程中通过 Glide 库将图片显示在界面上,以作为应用的背景图片。这样,用户每次打开应用时都能看到不同的壁纸。

private void loadBingPic() {
        String requestBingPic = "https://imgapi.cn/bing.php";
        HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
                Log.d("loadBingPic", "Failure ");
            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull 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();
//                Log.d("loadBingPic", "Response " + bingPic);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
//                        Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg);
                        Glide.with(WeatherActivity.this).load(requestBingPic).into(bingPicImg);
                    }
                });
            }
        });
    }
  1. 定义 loadBingPic 方法:
  • 这个方法用于加载必应每日一图的背景图片。
  1. 构建请求必应每日一图的 URL:
  • 使用字符串 requestBingPic 存储了请求必应每日一图的 URL,通常这个 URL 返回的是一张背景图片。
  1. 发送网络请求:
  • 使用 HttpUtil.sendOkHttpRequest 方法,通过 OkHttp 发送一个异步的 HTTP GET 请求,请求必应每日一图的图片。
  1. 处理请求成功(onResponse)的回调:
  • 如果请求成功,服务器会返回图片的数据。
  • 首先,在 onResponse 方法中,将服务器返回的图片数据(response.body().string())存储在 bingPic 变量中。
  1. 缓存图片地址:
  • 使用 SharedPreferences 来存储获取到的 bingPic 数据,以便后续使用。
  1. 在主线程中更新UI:
  • 使用 runOnUiThread 方法,确保 UI 操作在主线程中进行。
  • 在这里,通过 Glide 库加载 bingPic 到 bingPicImg 控件中,以显示必应每日一图的背景图片。

4.4 requestWeather方法

请求指定城市的天气信息,然后将获取到的天气数据解析并显示在界面上,同时也会更新背景图片。如果请求或解析失败,会给用户相应的提示信息。

public void requestWeather(String weatherId) {
//        weatherId = weatherId.substring(weatherId.length() - 9);
        String weatherUrl = "http://guolin.tech/api/weather?cityid=" +
                weatherId + "&key=2b8d73c0f8734617aff756f3f4477ded";
        Log.d("requestWeather", "weatherId: " + weatherId);
        HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
                Toast.makeText(WeatherActivity.this, "onFailure:获取天气信息失败", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull 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();
                            mWeatherId = weather.basic.weatherId;
                            showWeatherInfo(weather);
                        } else {
                            Toast.makeText(WeatherActivity.this, "onResponse:获取天气信息失败", Toast.LENGTH_SHORT).show();
                        }
                        swipeRefreshLayout.setRefreshing(false);
                    }
                });
            }
        });
        loadBingPic();
    }
  1. requestWeather 方法接受一个参数 weatherId,表示天气的城市代码。
  2. 构建天气信息请求的 URL:
  • 使用传入的 weatherId 构建了一个 URL,这个 URL 包括了城市代码和一个 API 密钥。该 URL 用于向服务器请求天气数据。
  1. 发送网络请求:
  • 使用 HttpUtil.sendOkHttpRequest 方法,通过 OkHttp 发送一个异步的 HTTP GET 请求,请求天气信息。
  1. 处理请求成功(onResponse)的回调:
  • 如果请求成功,服务器会返回天气信息的数据。
  • 在 onResponse 方法中,首先将服务器返回的数据存储在 responseText 变量中。
  • 然后,使用 Utility.handleWeatherResponse 方法解析 responseText,将 JSON 数据转化为 Weather 对象。
  1. 在主线程中更新 UI:
  • 使用 runOnUiThread 方法,确保 UI 操作在主线程中进行。
  • 如果成功解析了天气数据(weather != null 且 weather.status 为 “ok”),则进行以下操作:
    • 使用 SharedPreferences 存储获取到的天气信息数据,以便后续使用。
    • 更新 mWeatherId,以便在后续的刷新操作中重新请求该城市的天气信息。
    • 调用 showWeatherInfo 方法,将解析后的天气数据显示在界面上。
  • 如果解析天气数据失败,显示一个失败的提示信息。
  1. 停止刷新操作:
  • 使用 swipeRefreshLayout.setRefreshing(false) 来停止刷新操作,因为在获取完天气信息后不再需要刷新。
  1. 最后,调用 loadBingPic 方法,用于加载必应每日一图的背景图片。

4.5 showWeatherInfo方法

将天气信息以用户友好的方式呈现在界面上,包括当前天气、未来天气预报、空气质量、生活建议等

private void showWeatherInfo(Weather weather) {
        String cityName = weather.basic.cityName;
        String updateTime = weather.basic.update.updateTime.split(" ")[1];
        String degree = weather.now.temperature + "℃";
        String weatherInfo = weather.now.more.info;
        titleCity.setText(cityName);
        degreeText.setText(degree);
        weatherInfoText.setText(weatherInfo);
        titleUpdateTime.setText(updateTime);
        forecastLayout.removeAllViews();
        for (Forecast forecast : weather.forecastList) {
            View view = LayoutInflater.from(this).inflate(R.layout.forecast_item,forecastLayout,false);
            TextView dateText = (TextView) view.findViewById(R.id.date_text);
            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);
            dateText.setText(forecast.date);
            infoText.setText(forecast.more.info);
            maxText.setText(forecast.temperature.max);
            minText.setText(forecast.temperature.min);
            forecastLayout.addView(view);
        }
        if (weather.aqi != null) {
            aqiText.setText(weather.aqi.city.aqi);
            pm25Text.setText(weather.aqi.city.pm25);
        }
        String comfort = "舒适度:" + weather.suggestion.comfort.info;
        String carWash = "洗车指数:" + weather.suggestion.carWash.info;
        String sport = "运动建议:" + weather.suggestion.sport.info;
        comfortText.setText(comfort);
        carWashText.setText(carWash);
        sportText.setText(sport);
        weatherLayout.setVisibility(View.VISIBLE);
    }
}
  1. 获取天气信息对象 Weather 中的各种数据,包括城市名称、更新时间、温度、天气信息、天气预报、空气质量、舒适度指数、洗车指数和运动建议等。
  2. 将获取到的数据显示在对应的 TextView 控件中:
  • cityName 显示在 titleCity 控件中,表示城市名称。
  • updateTime 显示在 titleUpdateTime 控件中,表示更新时间。
  • degree 显示在 degreeText 控件中,表示温度。
  • weatherInfo 显示在 weatherInfoText 控件中,表示天气信息。
  1. 清空 forecastLayout 布局中的子视图,以便加载新的未来天气预报数据。
  2. 遍历天气预报数据列表 weather.forecastList,为每一天的天气预报创建一个新的视图,并将数据显示在相应的 TextView 控件中。每个预报包括日期、天气信息、最高温度和最低温度。
  3. 如果天气信息中包含空气质量数据 (weather.aqi != null),则将空气质量指数(AQI)和 PM2.5 数据显示在 aqiText 和 pm25Text 控件中。
  4. 显示舒适度指数、洗车指数和运动建议等建议信息,将这些信息显示在相应的 TextView 控件中。
  5. 最后,将天气信息的整个布局 weatherLayout 设置为可见,以便用户看到完整的天气信息。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值