Android基础实战之天气实战|完整代码|带实例地址

天气实战

  • 第一步

第一步,注册和风天气控制台 | 和风天气,申请key

08f828b5a3ec4905a29156bf5f29136f

查看郭林的后台天气pai接口

http://guolin.tech/api/china

  • 第二步,建立省市县实体类
/**
 * Description
 * <p>
 * id是每个实体该有的字段
 * provinceName记录省的名字
 * provinceCode记录省的代号
 * @author qricis on 2020/9/3 14:47
 * @version 1.0.0
 */
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;
    }
}

/**
 * Description
 * <p>
 * id是每个实体类都应该有的字段
 * cityName记录市的名字
 * cityCode记录市的代码
 * provinceId记录当前市所属省的id值
 * @author qricis on 2020/9/3 14:47
 * @version 1.0.0
 */
public class City extends DataSupport {

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

/**
 * Description
 * <p>
 * id是每个实体类都应该有的字段
 * countyName记录县的名字
 * weatherId记录县的天气代码
 * cityId记录当前县所属市的id
 * @author qricis on 2020/9/3 14:47
 * @version 1.0.0
 */
public class County extends DataSupport {

    private int id;

    private String countyName;

    private int countyCode;

    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 int getCountyCode() {
        return countyCode;
    }

    public void setCountyCode(int countyCode) {
        this.countyCode = countyCode;
    }

    public int getCityId() {
        return cityId;
    }

    public void setCityId(int cityId) {
        this.cityId = cityId;
    }
}
  • 第三步,配置litepal
<litepal>

		    <!--  设置数据库名为cool_weather,数据库版本1,并将三个实体类映射到列表当中  -->
		    <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>
  • 第四步,编写与服务端交互的类HttpUtil
/**
 * Description
 * <p>
 * 与服务器进行交互
 * @author qricis on 2020/9/3 15:46
 * @version 1.0.0
 */
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);
    }

}
  • 第五步,编写一个工具类用来处理从服务器端获取到的数据Utility
/**
 * Description
 * <p>
 * 解析服务器返回的数据
 * @author qricis on 2020/9/3 15:53
 * @version 1.0.0
 */
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.setCountyCode(countyObject.getInt("id"));
                    county.setCityId(cityId);
                    county.save();
                }
                return true;
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

}
  • 第六步,编写选择布局choose_area
<!-- 定义一个展示所有省市县的碎片 -->
<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/area_tv_title"
		            android:layout_width="wrap_content"
		            android:layout_height="wrap_content"
		            android:layout_centerInParent="true"
		            android:textColor="#fff"
		            android:textSize="20sp"/>

        	<Button
		            android:id="@+id/area_btn_back"
		            android:layout_width="25dp"
		            android:layout_height="25dp"
		            android:layout_marginLeft="10dp"
		            android:layout_marginStart="10dp"
		            android:layout_alignParentLeft="true"
		            android:layout_alignParentStart="true"
		            android:layout_centerVertical="true"
		            android:background="@drawable/area_btn_back"
		             />
    </RelativeLayout>

	<ListView
		        android:id="@+id/area_list_view"
		        android:layout_width="match_parent"
		        android:layout_height="match_parent" />
</LinearLayout>
  • 第七步,编写用于遍历省市县数据的碎片ChooseAreaFragment
/**
 * Description
 * <p>
 * 用于遍历省市县数据的碎片ChooseAreaFragment
 * @author qricis on 2020/9/3 17:25
 * @version 1.0.0
 */
public class ChooseAreaFragment extends Fragment {

    public static final int LEVEL_PROVINCE = 0;

    public static final int LEVEL_CITY = 1;

    public static final int LEVEL_COUNTY = 2;

    private ProgressDialog mProgressDialog;

    private TextView mTitleText;

    private Button mBackButton;

    private ListView mListView;

    private ArrayAdapter<String> mStringArrayAdapter;

    private List<String> mDataList = new ArrayList<>();

    /**
     * 省列表
     * */
    private List<Province> mProvinceList;

    /**
     * 市列表
     * */
    private List<City> mCityList;

    /**
     * 县列表
     * */
    private List<County> mCountyList;

    /**
     * 选中的省份
     * */
    private Province mSelectedProvince;

    /**
     * 选中的城市
     * */
    private City mSelectedCity;

    /**
     * 当前选中的级别
     * */
    private int mCurrentLevel;

    /**
     * 获取控件实例,初始化mStringArrayAdapter,并将之设置为ListView的适配器
     * */
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.choose_area,container,false);
        mTitleText = view.findViewById(R.id.area_tv_title);
        mBackButton = view.findViewById(R.id.area_btn_back);
        mListView = view.findViewById(R.id.area_list_view);
        mStringArrayAdapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,mDataList);
        mListView.setAdapter(mStringArrayAdapter);
        return view;
    }

    /**
     * 给ListView和Button设置点击事件
     * */
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
                if (mCurrentLevel == LEVEL_PROVINCE) {
                    mSelectedProvince = mProvinceList.get(position);
                    queryCities();
                } else if (mCurrentLevel == LEVEL_CITY) {
                    mSelectedCity = mCityList.get(position);
                    queryCounties();
                }
            }
        });
        mBackButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mCurrentLevel == LEVEL_COUNTY) {
                    queryCities();
                } else if (mCurrentLevel == LEVEL_CITY) {
                    queryProvinces();
                }
            }
        });
        queryProvinces();
    }

    /**
     * 查询全国所有的省,优先从数据库查询,如果没有查询到再去服务器查询
     * */
    private void queryProvinces() {
        mTitleText.setText("中国");
        mBackButton.setVisibility(View.GONE);
        mProvinceList = DataSupport.findAll(Province.class);
        if (mProvinceList.size() > 0) {
            mDataList.clear();
            for (Province province : mProvinceList) {
                mDataList.add(province.getProvinceName());
            }
            mStringArrayAdapter.notifyDataSetChanged();
            mListView.setSelection(0);
            mCurrentLevel = LEVEL_PROVINCE;
        } else {
            String address = "http://guolin.tech/api/china";
            queryFromServer(address,"province");
        }
    }

    /**
     * 查询全国所有的市,优先从数据库查询,如果没有查询到再去服务器查询
     * */
    private void queryCities() {
        mTitleText.setText(mSelectedProvince.getProvinceName());
        mBackButton.setVisibility(View.VISIBLE);
        mCityList = DataSupport.where("provinceid = ?", String.valueOf(mSelectedProvince.getId())).find(City.class);
        if (mCityList.size() > 0) {
            mDataList.clear();
            for (City city : mCityList) {
                mDataList.add(city.getCityName());
            }
            mStringArrayAdapter.notifyDataSetChanged();
            mListView.setSelection(0);
            mCurrentLevel = LEVEL_CITY;
        } else {
            int provinceCode = mSelectedProvince.getProvinceCode();
            String address = "http://guolin.tech/api/china/" + provinceCode;
            queryFromServer(address,"city");
        }
    }

    /**
     * 查询全国所有的县,优先从数据库查询,如果没有查询到再去服务器查询
     * */
    private void queryCounties() {
        mTitleText.setText(mSelectedCity.getCityName());
        mBackButton.setVisibility(View.VISIBLE);
        mCountyList = DataSupport.where("cityid = ?", String.valueOf(mSelectedCity.getId())).find(County.class);
        if (mCountyList.size() > 0) {
            mDataList.clear();
            for (County county : mCountyList) {
                mDataList.add(county.getCountyName());
            }
            mStringArrayAdapter.notifyDataSetChanged();
            mListView.setSelection(0);
            mCurrentLevel = LEVEL_COUNTY;
        } else {
            int provinceCode = mSelectedProvince.getProvinceCode();
            int cityCode = mSelectedCity.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 onFailure(@NotNull Call call, @NotNull IOException e) {
                // 通过runOnUiThread回到主线程处理逻辑
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        closeProgressDialog();
                        Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull 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,mSelectedProvince.getId());
                } else if ("county".equals(type)) {
                    result = Utility.handleCountyResponse(responseText,mSelectedCity.getId());
                }
                if (result) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            closeProgressDialog();
                            switch (type) {
                                case "province":
                                    queryProvinces();
                                    break;
                                case "city":
                                    queryCities();
                                    break;
                                case "county":
                                    queryCounties();
                                    break;
                            }
                        }
                    });
                }
            }
        });
    }

    /**
     * 显示对话框
     * */
    private void showProgressDialog() {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(getActivity());

            // 这里的话参数依次为,上下文,标题,内容,是否显示进度(flase表示显示进度),是否可以用取消按钮关闭
            // ProgressDialog.show(getActivity(), "资源加载中", "资源加载中,请稍后...",false,false);
            // setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)这里是设置进度条的风格,HORIZONTAL是水平进度条,SPINNER是圆形进度条
            mProgressDialog.setTitle("资源加载中");
            mProgressDialog.setMessage("正在加载,请稍后...");
            mProgressDialog.setCanceledOnTouchOutside(false);
        }
        mProgressDialog.show();
    }

    /**
     * 关闭进度对话框
     * */
    private void closeProgressDialog() {
        if (mProgressDialog != null) {
            mProgressDialog.dismiss();
        }
    }
}
  • 第八步,修改activity_main,使得碎片依附于活动显示
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
		    xmlns:tools="http://schemas.android.com/tools"
		    android:layout_width="match_parent"
		    android:layout_height="match_parent"
		    tools:context=".activity.MainActivity">

		<fragment
		        android:id="@+id/area_fragment_choose"
		        android:name="com.coolweather.android.activity.ChooseAreaFragment"
		        android:layout_width="match_parent"
		        android:layout_height="match_parent"/>

</FrameLayout>
  • 第九步,设置http的访问权限,新建xml/network_security_config
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
	<base-config cleartextTrafficPermitted="true" />
</network-security-config>
  • 第十步,修改Manifest
<!-- 配置LitePalApplication -->
	    <application
	        android:name="org.litepal.LitePalApplication"
	        android:networkSecurityConfig="@xml/network_security_config"
	        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"
	        tools:targetApi="n">
	        <activity android:name=".activity.MainActivity">
	            <intent-filter>
	                <action android:name="android.intent.action.MAIN" />

	                <category android:name="android.intent.category.LAUNCHER" />
	            </intent-filter>
	        </activity>
	    </application>
  • 第十一步,查看天气json数据

http://guolin.tech/api/weather?cityid=CN101010100&key=08f828b5a3ec4905a29156bf5f29136fhttp://guolin.tech/api/weather?cityid=CN101010100&key=08f828b5a3ec4905a29156bf5f29136f

  • 返回值
{"HeWeather":[{
				"basic":{"cid":"CN101010100","location":"北京","parent_city":"北京","admin_area":"北京","cnty":"中国","lat":"25.04060936","lon":"102.71224976","tz":"+8.00","city":"北京","id":"CN101010100","update":{"loc":"2020-09-04 10:52","utc":"2020-09-04 02:52"}},
				"update":{"loc":"2020-09-04 10:52","utc":"2020-09-04 02:52"},
				"status":"ok",
				"now":{"cloud":"0","cond_code":"100","cond_txt":"晴","fl":"19","hum":"34","pcpn":"0.0","pres":"1018","tmp":"21","vis":"16","wind_deg":"149","wind_dir":"东南风","wind_sc":"2","wind_spd":"10","cond":{"code":"100","txt":"晴"}},
				"daily_forecast":[
					{"date":"2020-09-05","cond":{"txt_d":"晴"},"tmp":{"max":"24","min":"7"}},
					{"date":"2020-09-06","cond":{"txt_d":"晴"},"tmp":{"max":"26","min":"8"}},
					{"date":"2020-09-07","cond":{"txt_d":"多云"},"tmp":{"max":"25","min":"10"}},
					{"date":"2020-09-08","cond":{"txt_d":"阴"},"tmp":{"max":"27","min":"7"}},
					{"date":"2020-09-09","cond":{"txt_d":"晴"},"tmp":{"max":"28","min":"9"}},
					{"date":"2020-09-10","cond":{"txt_d":"多云"},"tmp":{"max":"26","min":"11"}}],
				"aqi":{"city":{"aqi":"47","pm25":"28","qlty":"优"}},
				"suggestion":{
					"comf":{"type":"comf","brf":"舒适","txt":"白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。"},
					"sport":{"type":"sport","brf":"较适宜","txt":"天气较好,户外运动请注意防晒。推荐您进行室内运动。"},
					"cw":{"type":"cw","brf":"较适宜","txt":"较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。"}},
				"msg":"所有天气数据均为模拟数据,仅用作学习目的使用,请勿当作真实的天气预报软件来使用。"
			}]}
  • 简化版

basic、now、daily_forecast、aqi、suggestion的内部又有其他的内容,因此我们可以将这5个部分定义成5个实体类

{
    "HeWeather":[
        {
            "basic":{},
            "status":"ok",
            "now":{},
            "daily_forecast":[],
            "aqi":{},
            "suggestion":{},
        }
    ]
}

basic中,city表示城市名,id表示城市对应的天气update表示天气更新时间

"basic":{
    "city":"北京",
    "id":"CN101010100",
    "update":{
        "loc":"2020-09-04 10:52",
        "utc":"2020-09-04 02:52"
        }
}
  • 实体类
public class Basic {

    @SerializedName("city")
    public String cityName;

    @SerializedName("id")
    public String weatherId;

    @SerializedName("update")
    public Update update;

    public class Update {

        @SerializedName("loc")
        public String updateTime;
    }

}
  • aqi中
"aqi":{
    "city":{
        "aqi":"47",
        "pm25":"28",
        "qlty":"优"
        }
}
  • 实体类
public class AQI {

    @SerializedName("city")
    public AQICity mAQICity;

    public class AQICity {

        public String aqi;

        public String pm25;

    }
}

now

"now":{
    "tmp":"21",
    "cond":{
        "code":"100",
        "txt":"晴"
        }
}

实体类

public class Now {

    @SerializedName("tmp")
    public String temperature;

    @SerializedName("cond")
    public More more;

    public class More {

        @SerializedName("txt")
        public String info;
    }
}

suggestipon

"suggestion":{ "comf":{ "type":"comf", "brf":"舒适", "txt":"白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。" }, "sport":{ "type":"sport", "brf":"较适宜", "txt":"天气较好,户外运动请注意防晒。推荐您进行室内运动。" }, "cw":{ "type":"cw", "brf":"较适宜", "txt":"较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。" } }

实体类

public class Suggestion {

    @SerializedName("comf")
    public Comfort mComfort;

    @SerializedName("cw")
    public CarWash mCarWash;

    @SerializedName("sport")
    public Sport mSport;

    public static class Comfort {

        @SerializedName("txt")
        public String info;

    }

    public static class CarWash {

        @SerializedName("txt")
        public String info;

    }

    public static class Sport {

        @SerializedName("txt")
        public String info;

    }

}

daily_forecast

"daily_forecast":[
    {
        "date":"2020-09-05",
        "cond":{
            "txt_d":"晴"
            },
        "tmp":{
            "max":"24",
            "min":"7"
            }
    },
    {
        "date":"2020-09-06",
        "cond":{
            "txt_d":"晴"
            },
        "tmp":{
            "max":"26",
            "min":"8"
            }
    },
    ...
]

实体类

public class Forecast {

    public String date;

    @SerializedName("tmp")
    public Temperature mTemperature;

    @SerializedName("cond")
    public More mMore;

    public static class Temperature {

        public String max;

        public String min;

    }

    public static class More {

        @SerializedName("txt")
        public String info;
    }

}

weather

public class Weather {

    public String status;

    @SerializedName("basic")
    public Basic mBasic;

    @SerializedName("aqi")
    public AQI mAQI;

    @SerializedName("now")
    public Now mNow;

    @SerializedName("suggestion")
    public Suggestion mSuggestion;

    @SerializedName("daily_forecast")
    public List<Forecast> mForecastList;
}

第十二步,编写天气界面,由于模块较多,分开写,最后include进来

<?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=".activity.WeatherActivity"
		    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:layout_width="match_parent"
		            android:layout_height="wrap_content"
		            android:orientation="vertical">

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

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

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

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

		            <include layout="@layout/weather_suggestion"/>
		        </LinearLayout>
		    </ScrollView>
		</FrameLayout>

weather_title

<?xml version="1.0" encoding="utf-8"?>
			<!--头布局,用于居中显示城市名,居右显示更新时间-->
			<RelativeLayout
			    xmlns:android="http://schemas.android.com/apk/res/android"
			    android:layout_width="match_parent"
			    android:layout_height="?attr/actionBarSize">


			    <TextView
			        android:id="@+id/weather_title_city"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_centerInParent="true"
			        android:textColor="#fff"
			        android:textSize="20sp"/>

			    <TextView
			        android:id="@+id/weather_title_update_time"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_marginRight="10dp"
			        android:layout_marginEnd="10dp"
			        android:layout_alignParentRight="true"
			        android:layout_alignParentEnd="true"
			        android:layout_centerVertical="true"
			        android:textColor="#fff"
			        android:textSize="16sp" />
			</RelativeLayout>

weather_now

<!--当前天气信息的布局,用于显示当前气温和天气概况-->
			<LinearLayout
			    xmlns:android="http://schemas.android.com/apk/res/android"
			    android:layout_width="match_parent"
			    android:layout_height="wrap_content"
			    android:orientation="vertical"
			    android:layout_margin="15dp">

			    <TextView
			        android:id="@+id/weather_now_degree_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_now_info_text"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_gravity="end"
			        android:textColor="#fff"
			        android:textSize="20sp"/>
			</LinearLayout>

weather_forecast

<!--未来几天天气的布局,用于显示未来几天天气,包含一个标题和用于显示未来几天天气信息的布局,无任何内容,因为要动态添加-->
			<LinearLayout
			    xmlns:android="http://schemas.android.com/apk/res/android"
			    android:layout_width="match_parent"
			    android:layout_height="wrap_content"
			    android:orientation="vertical"
			    android:layout_margin="15dp"
			    android:background="#8000">

			    <TextView
			        android:id="@+id/weather_forecast_title"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_marginLeft="15dp"
			        android:layout_marginStart="15dp"
			        android:layout_marginTop="15dp"
			        android:text="@string/weather_forecast_text_view"
			        android:textColor="#fff"
			        android:textSize="20sp" />

			    <LinearLayout
			        android:id="@+id/weather_forecast_layout"
			        android:layout_width="match_parent"
			        android:layout_height="wrap_content"
			        android:orientation="vertical"/>
			</LinearLayout>

item_forecast

<!--用于显示天气预报日期、天气概况、最高温、最低温-->
				<LinearLayout
				    xmlns:android="http://schemas.android.com/apk/res/android"
				    android:layout_width="match_parent"
				    android:layout_height="wrap_content"
				    android:layout_margin="15dp">

				    <TextView
				        android:id="@+id/item_forecast_data_text"
				        android:layout_width="0dp"
				        android:layout_height="wrap_content"
				        android:layout_weight="2"
				        android:layout_gravity="center_vertical"
				        android:textColor="#fff"/>

				    <TextView
				        android:id="@+id/item_forecast_info_text"
				        android:layout_width="0dp"
				        android:layout_height="wrap_content"
				        android:layout_weight="1"
				        android:layout_gravity="center_vertical"
				        android:gravity="center"
				        android:textColor="#fff" />

				    <TextView
				        android:id="@+id/item_forecast_max_text"
				        android:layout_width="0dp"
				        android:layout_height="wrap_content"
				        android:layout_weight="1"
				        android:layout_gravity="center_vertical"
				        android:gravity="right"
				        android:textColor="#fff" />

				    <TextView
				        android:id="@+id/item_forecast_min_text"
				        android:layout_width="0dp"
				        android:layout_height="wrap_content"
				        android:layout_weight="1"
				        android:layout_gravity="center_vertical"
				        android:gravity="right"
				        android:textColor="#fff" />
				</LinearLayout>

weather_aqi

<!--空气质量信息的布局,这里用于显示AQI指数和PM25指数-->
			<LinearLayout
			    xmlns:android="http://schemas.android.com/apk/res/android"
			    android:layout_width="match_parent"
			    android:layout_height="wrap_content"
			    android:layout_margin="15dp"
			    android:background="#8000"
			    android:orientation="vertical">

			    <TextView
			        android:id="@+id/weather_aqi_title"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_marginLeft="15dp"
			        android:layout_marginStart="15dp"
			        android:layout_marginTop="15dp"
			        android:text="@string/weather_aqi_title"
			        android:textColor="#fff"
			        android:textSize="20sp" />

			    <LinearLayout
			        android:layout_width="match_parent"
			        android:layout_height="wrap_content"
			        android:orientation="horizontal"
			        android:layout_margin="15dp"
			        android:baselineAligned="false">

			            <LinearLayout
			                android:layout_width="0dp"
			                android:layout_weight="1"
			                android:layout_height="wrap_content"
			                android:orientation="vertical">

			                <TextView
			                    android:id="@+id/weather_aqi_aqi_text"
			                    android:layout_width="wrap_content"
			                    android:layout_height="wrap_content"
			                    android:layout_gravity="center"
			                    android:textColor="#fff"
			                    android:textSize="40sp"/>

			                <TextView
			                    android:id="@+id/weather_aqi_aqi_info"
			                    android:layout_width="wrap_content"
			                    android:layout_height="wrap_content"
			                    android:layout_gravity="center"
			                    android:textColor="#fff"
			                    android:text="@string/weather_aqi_aqi_info" />
			            </LinearLayout>

			            <LinearLayout
			                android:layout_width="0dp"
			                android:layout_weight="1"
			                android:layout_height="wrap_content"
			                android:orientation="vertical">

			                <TextView
			                    android:id="@+id/weather_aqi_pm25_text"
			                    android:layout_width="wrap_content"
			                    android:layout_height="wrap_content"
			                    android:layout_gravity="center"
			                    android:textColor="#fff"
			                    android:textSize="40sp" />

			                <TextView
			                    android:id="@+id/weather_aqi_pm25_info"
			                    android:layout_width="wrap_content"
			                    android:layout_height="wrap_content"
			                    android:layout_gravity="center"
			                    android:textColor="#fff"
			                    android:text="@string/weather_aqi_pm25_info" />
			            </LinearLayout>
			    </LinearLayout>

			</LinearLayout>

weather_suggestion

<!--生活建议的布局,用于显示标题、舒适度、洗车指数和运动指数-->
			<LinearLayout
			    xmlns:android="http://schemas.android.com/apk/res/android"
			    android:layout_width="match_parent"
			    android:layout_height="wrap_content"
			    android:orientation="vertical"
			    android:layout_margin="15dp"
			    android:background="#8000">

			    <TextView
			        android:id="@+id/weather_suggestion_title"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_marginLeft="15dp"
			        android:layout_marginStart="15dp"
			        android:layout_marginTop="15dp"
			        android:text="@string/weather_suggestion_title"
			        android:textColor="#fff"
			        android:textSize="20sp" />

			    <TextView
			        android:id="@+id/weather_suggestion_comfort_text"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_margin="15dp"
			        android:textColor="#fff"/>

			    <TextView
			        android:id="@+id/weather_suggestion_car_wash_text"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_margin="15dp"
			        android:textColor="#fff" />

			    <TextView
			        android:id="@+id/weather_suggestion_sport_text"
			        android:layout_width="wrap_content"
			        android:layout_height="wrap_content"
			        android:layout_margin="15dp"
			        android:textColor="#fff" />
			</LinearLayout>

- 第十三步,在Utility里添加一个用于解析天气Json数据的方法

/**
 * 将返回的Json数据解析成Weather实体类
 * */
public static Weather handleWeatherResponse(String response) {
    try {
        JSONObject jsonObject = new JSONObject(response);
        JSONArray jsonArray = jsonObject.getJSONArray("HeWeather");
        String weatherContent = jsonArray.getJSONArray(0).toString();
        return new Gson().fromJson(weatherContent,Weather.class);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

- 第十四步,修改WeatherActivity的代码,在其中去请求天气数据,以及将数据展示在界面上

public class WeatherActivity extends AppCompatActivity {

    private ScrollView weatherLayout;

    private TextView weatherTitleCity;

    private TextView weatherTitleUpdateTime;

    private TextView weatherNowDegreeText;

    private TextView weatherNowInfoText;

    private LinearLayout weatherForecastLayout;

    private TextView weatherAQIText;

    private TextView weatherPM25Text;

    private TextView weatherSuggestionComfortText;

    private TextView weatherSuggestionCarWashText;

    private TextView weatherSuggestionSportText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_weather);
        //初始化各控件
        weatherLayout = findViewById(R.id.weather_layout);
        weatherTitleCity = findViewById(R.id.weather_title_city);
        weatherTitleUpdateTime = findViewById(R.id.weather_title_update_time);
        weatherNowDegreeText = findViewById(R.id.weather_now_degree_text);
        weatherNowInfoText = findViewById(R.id.weather_now_info_text);
        weatherForecastLayout = findViewById(R.id.weather_forecast_layout);
        weatherAQIText = findViewById(R.id.weather_aqi_aqi_text);
        weatherPM25Text = findViewById(R.id.weather_aqi_pm25_text);
        weatherSuggestionComfortText = findViewById(R.id.weather_suggestion_comfort_text);
        weatherSuggestionCarWashText = findViewById(R.id.weather_suggestion_car_wash_text);
        weatherSuggestionSportText = findViewById(R.id.weather_suggestion_sport_text);

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

    }

    /**
		     * 根据天气id请求城市天气信息
		     * */
    public void requestWeather(final String weatherId) {

        String weatherUrl = "http://guolin.tech/api/weather?cityid=" + weatherId + "&key=08f828b5a3ec4905a29156bf5f29136f";

        HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull 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();
                            showWeatherInfo(weather);
                        } else {
                            Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT);
                        }
                    }
                });
            }
        });



    }

    /**
		     * 处理并展示Weather实体类中的数据
		     * */
    private void showWeatherInfo(Weather weather) {
        String cityName = weather.mBasic.cityName;
        String updateTime = weather.mBasic.mUpdate.updateTime.split( " ")[1];
        String degree = weather.mNow.temperature + "℃";
        String weatherInfo = weather.mNow.mMore.info;
        weatherTitleCity.setText(cityName);
        weatherTitleUpdateTime.setText(updateTime);
        weatherNowDegreeText.setText(degree);
        weatherNowInfoText.setText(weatherInfo);
        weatherForecastLayout.removeAllViews();
        for (Forecast forecast : weather.mForecastList) {
            View view = LayoutInflater.from(this).inflate(R.layout.item_forecast,weatherForecastLayout,false);
            TextView dateText = view.findViewById(R.id.item_forecast_data_text);
            TextView infoText = view.findViewById(R.id.item_forecast_info_text);
            TextView maxText = view.findViewById(R.id.item_forecast_max_text);
            TextView minText = view.findViewById(R.id.item_forecast_min_text);
            dateText.setText(forecast.date);
            infoText.setText(forecast.mMore.info);
            maxText.setText(forecast.mTemperature.max);
            minText.setText(forecast.mTemperature.min);
            weatherForecastLayout.addView(view);
        }
        if (weather.mAQI != null) {
            weatherAQIText.setText(weather.mAQI.mAQICity.aqi);
            weatherPM25Text.setText(weather.mAQI.mAQICity.pm25);
        }
        String comfort = "舒适度:" + weather.mSuggestion.mComfort.info;
        String carWash = "洗车指数:" + weather.mSuggestion.mCarWash.info;
        String sport = "运动指数:" + weather.mSuggestion.mSport.info;
        weatherSuggestionComfortText.setText(comfort);
        weatherSuggestionCarWashText.setText(carWash);
        weatherSuggestionSportText.setText(sport);
        weatherLayout.setVisibility(View.VISIBLE);

    }
}
  • 第十五步,修改ChooseAreaFragment的onActivityCreated()代码
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
        if (mCurrentLevel == LEVEL_PROVINCE) {
            mSelectedProvince = mProvinceList.get(position);
            queryCities();
        } else if (mCurrentLevel == LEVEL_CITY) {
            mSelectedCity = mCityList.get(position);
            queryCounties();
        } else if (mCurrentLevel == LEVEL_COUNTY) {
            String weatherId = mCountyList.get(position).getWeatherId();
            Intent intent = new Intent(getActivity(),WeatherActivity.class);
            intent.putExtra("weather_id",weatherId);
            startActivity(intent);
            getActivity().finish();
        }
    }
});
  • 第十六步,修改MainActivity中的代码,判断是否已经请求过数据
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    if (prefs.getString("weather",null) != null) {
        Intent intent = new Intent(this,WeatherActivity.class);
        startActivity(intent);
        finish();
    }
}
  • 第十七步,获取必应的每日一图,修改activity_weather、weatherActivity
<ImageView
	        android:id="@+id/bing_pic_img"
	        android:layout_width="match_parent"
	        android:layout_height="match_parent"
	        android:scaleType="centerCrop"
	        android:contentDescription="@string/bing_pic_img" />
/**
	     * 加载必应每日一图
	     * */
	    private void loadBingPic() {
	        String requestBingPic = "http://guolin.tech/api/bing_pic";
	        HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
	            @Override
	            public void onFailure(@NotNull Call call, @NotNull IOException e) {
	                e.printStackTrace();
	            }

	            @Override
	            public void onResponse(@NotNull Call call, @NotNull 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);
	                    }
	                });
	            }
	        });
	    }
  • 第十八步,修改activity_weather,实现手动更新天气,切换城市
<!--使用DrawerLayout实现菜单布局,其中第一个子控件用于作为主屏幕显示的内容,第二个字控件用于作为滑动菜单中显示的内容
        layout_gravity指定了菜单所处的方向,且必须指定-->
	    <androidx.drawerlayout.widget.DrawerLayout
	        android:id="@+id/drawer_layout"
	        android:layout_width="match_parent"
	        android:layout_height="match_parent">

	        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
	            android:id="@+id/swipe_refresh"
	            android:layout_width="match_parent"
	            android:layout_height="match_parent">
	            <ScrollView
	                android:id="@+id/weather_layout"
	                android:layout_width="match_parent"
	                android:layout_height="match_parent"
	                android:scrollbars="none"
	                android:overScrollMode="never">

	                <!--为状态看留出空间fitsSystemWindows="true"-->
	                <LinearLayout
	                    android:layout_width="match_parent"
	                    android:layout_height="wrap_content"
	                    android:orientation="vertical"
	                    android:fitsSystemWindows="true">

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

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

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

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

	                    <include layout="@layout/weather_suggestion"/>
	                </LinearLayout>
	            </ScrollView>
	        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

	        <fragment
	            android:id="@+id/choose_area_fragment"
	            android:name="com.coolweather.android.activity.ChooseAreaFragment"
	            android:layout_width="match_parent"
	            android:layout_height="match_parent"
	            android:layout_gravity="start"/>

	    </androidx.drawerlayout.widget.DrawerLayout>
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    @Override
    public void onRefresh() {
        requestWeather(weatherId);
    }
});
String bingPic = prefs.getString("bing_pic",null);
if (bingPic != null) {
    Glide.with(this).load(bingPic).into(bingPicImg);
} else {
    loadBingPic();
}
  • 第十九步,新建AutoUpdateService,实现自动更新天气与城市
public class AutoUpdateService extends Service {
    public AutoUpdateService() {
    }

    @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 intentOne = new Intent(this,AutoUpdateService.class);
        PendingIntent pendingIntent = PendingIntent.getService(this,0,intentOne,0);
        manager.cancel(pendingIntent);
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
        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.mBasic.weatherId;

            String weatherUrl = "http://guolin.tech/api/weather?cityid=" + weatherId + "&key=08f828b5a3ec4905a29156bf5f29136f";
            HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    e.printStackTrace();
                }

                @Override
                public void onResponse(@NotNull Call call, @NotNull 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();
                    }
                }
            });
        }
    }

    /**
     * 更新必应每日一图
     * */
    private void updateBingPic() {
        String requestBingPic = "http://guolin.tech/api/bing_pic";
        HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                String bingPic = response.body().string();
                SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();
                editor.putString("bing_pic",bingPic);
                editor.apply();
            }
        });
    }
}
  • 下载地址

https://github.com/qricis/DoSomeAndroidTest/tree/main/CoolWeather

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值