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

news2024/11/24 14:23:08

这一部分是天气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 设置为可见,以便用户看到完整的天气信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1067515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Echarts热力/散点/面积地图和高德amap不得不说的故事

简单封装高德amap 只要涉及到地图开发&#xff0c;我们都需要依赖地图工具&#xff0c;常见的有谷歌地图、百度地图、高德地图。我们的项目里依赖高德地图JS API 2.0。 npm i amap/amap-jsapi-loader -s 在项目里&#xff0c;我们需要一个预加载好的地图loader方便我们随调随…

【消费战略】解读100个食品品牌|速溶咖啡精品化,“三顿半”承接强势需求!

可可&#xff0c;咖啡、茶饮&#xff0c;作为世 界三大饮料&#xff0c;被人们所熟知。一直以来&#xff0c;咖啡都被人们认定为是舶来。其实&#xff0c;中国的咖啡市场经过这么多年的培育和发展&#xff0c;已不同往昔。就拿上海来说&#xff0c;根据2021年《上海咖啡消费指数…

【算法】算法设计与分析 课程笔记 第三章 动态规划

1.1 动态规划简介 1.1.1 引例 动态规划算法和分治法类似&#xff0c;基本思想也是将待求解问题分解成若干个子问题&#xff0c;子问题可以以继续拆分&#xff0c;直到问题规模达到临界条件即可。多说无益&#xff0c;举个例子来解释一下&#xff1a; 这其实是一个多阶段图求最…

python 打包可执行文件-Nuitka详解

python 打包可执行文件-Nuitka详解 引言一、参数详解二、与pyinstaller对比三、打包总结 引言 Nuitka是用Python编写的优化Python编译器&#xff0c;它可以创建运行时不需要单独安装程序的可执行文件。简单易使用&#xff0c;与Python2&#xff08;2.6、2.7&#xff09;和Pyth…

3d环形图开发(vue3+vite+ts)

开发效果&#xff08;待完善&#xff09;&#xff1a; 技术支持&#xff1a; Echarts echarts-gl 安装&#xff1a; 注&#xff1a;echarts与echarts-gl版本需对应&#xff0c;可参考官网 pnpm add echarts4.9.0 echarts-gl1.1.2 组件封装&#xff1a; <template><…

unity操作_Camera c#

观察场景中Main Camera 的清除背景Clear Flags 第一种&#xff1a;Skybox天空盒渲染 制作3D游戏使用 第二种&#xff1a;Solid Color 制作2D游戏 第三种&#xff1a;Depth only 多个摄像机叠加渲染 相对重点学会多个摄像机设置Depth only使…

鸿蒙手表开发之使用adb命令安装线上包

#国庆发生的那些事儿# 鸿蒙手表开发之使用adb命令安装线上包 前言&#xff1a; 由于之前的哥们匆忙离职了&#xff0c;所以鸿蒙手表项目的新版本我临时接过来打包发布&#xff0c;基本上之前没有啥鸿蒙经验&#xff0c;但是一直是做Android开发的&#xff0c;在工作人员的指…

【FPGA零基础学习之旅#14】串口发送字符串

&#x1f389;欢迎来到FPGA专栏~串口发送字符串 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大家能指正…

DNSlog 注入简单笔记

无回显的盲注可以想办法回显到 dns 日志上&#xff1a; 1、打开 http://www.dnslog.cn 获取域名 2、注入&#xff1a; ?id1 and (select load_file(concat(//,(select database()),.3.mw0gxd.dnslog.cn/a)))-- 3、点击刷新得到回显&#xff1a;

机器学习笔记 - 两个静态手势识别的简单示例

一、关于手势识别 手势识别方法通常分为两类:静态或动态。 静态手势是那些只需要在分类器的输入处处理单个图像的手势,这种方法的优点是计算成本较低。动态手势需要处理图像序列和更复杂的手势识别方法。 进一步了解可以参考下面链接。 静态手势识别和动态手势识别的区别和技…

jpype 调用jar时,返回结果的中文乱码

解决方法&#xff1a; 在启动java虚拟机的参数上&#xff0c;加上 "-Dfile.encodingUTF-8"

MongoDB集群管理

1、副本集-Replica Sets 1.1、简介 MongoDB中的副本集&#xff08;Replica Set&#xff09;是一组维护相同数据集的mongod服务。 副本集可提供冗余和高 可用性&#xff0c;是所有生产部署的基础。 也可以说&#xff0c;副本集类似于有自动故障恢复功能的主从集群。通俗的讲就…

项目管理中有效任务分配的简单指南

在项目管理中&#xff0c;有时会出现人力资源匮乏或负担过重的情况。因此&#xff0c;项目经理有责任确保在项目进度内&#xff0c;将任务于正确的时间分配给正确的人。 任务分配有哪些不容忽视的好处&#xff1f; 在专业项目管理工具的帮助下&#xff0c;正确地进行任务分配…

详解IDEA git 版本回滚

作者简介 目录 1.git分区 2.未commit&#xff0c;进行回滚 3.commit未push&#xff0c;进行回滚 3.1.undo commit 3.2.reset 4.已commit&push&#xff0c;进行回滚 1.git分区 git的版本回滚其实就是回滚不同的分区&#xff0c;所以在聊git回滚之前我们有必要简单了解…

ElasticSearch环境准备

Elasticsearch 是一个基于 Apache Lucene™ 的开源搜索引擎。不仅仅是一个全文搜索引擎&#xff0c;它还是一个分布式的搜索和分析引擎&#xff0c;可扩展并能够实时处理大数据。以下是关于 Elasticsearch 的一些主要特点和说明&#xff1a; 1.实时分析&#xff1a;Elasticsear…

文件格式转换

把我的悲惨故事说给大家乐呵乐呵&#xff1a;老板让运营把一些数据以json格式给我&#xff0c;当我看到运营在石墨文档上编辑的时候我人都傻了&#xff0c;我理解运营的艰难&#xff0c;可我也是真的难啊&#xff0c;在石墨文档编辑的眼花缭乱的&#xff0c;很多属性都错乱了(诸…

关于seata启动时连接数据库异常,Mysql版本8.0

异常报错&#xff1a; ERROR --- [ionPool-Create-1772825962] com.alibaba.druid.pool.DruidDataSource : create connection SQLException, url: jdbc:mysql://127.0.0.1:3306/seata?useUnicodetrue&rewriteBatchedStatementstrue, errorCode 0, state 08001 > com…

工信部教考中心:什么是《研发效能(DevOps)工程师》认证,拿到证书之后有什么作用!(上篇)丨IDCF

在计算机行业中&#xff0c;资质认证可以证明在该领域内的专业能力和知识水平。各种技术水平认证也是层出不穷&#xff0c;而考取具有公信力和权威性的认证是从业者的首选。同时&#xff0c;随着国内企业技术实力的提升和国家对于自主可控的重视程度不断提高&#xff0c;国产证…

最有趣的代码or最蠢的代码?

如何写漂亮的代码 “愚蠢的代码” 是一个主观的说法&#xff0c;因为代码的质量取决于许多因素&#xff0c;包括编写代码的人的经验、知识水平以及代码的上下文。但是&#xff0c;有一些常见的编程实践&#xff0c;如果被采用&#xff0c;可能会导致代码被认为是愚蠢的或低质量…

深入理解Docker:简化部署与管理的利器

文章目录 引言Docker简介Docker的背景和发展Docker的优势和特点 Docker的基本概念和架构镜像&#xff08;Image&#xff09;容器&#xff08;Container&#xff09;仓库&#xff08;Repository&#xff09;Docker架构 Docker的常用命令和操作Docker的安装和配置Docker镜像的管理…