可能很多人会问:之前已经写过一篇博文来介绍怎么做一款简单的新闻APP(blog.csdn.net/yiwei12/art…),为什么还要专门一篇来介绍怎么做一款天气 APP,毕竟网络请求和数据处理都是大同小异的。如果真的要说差别的话,前一篇只是具备了一些基本的功能,来说明怎么请求和处理返回的数据,但还不足与在日常生活中使用?这一篇实践是来做一款日常可用的天气 APP - 彼时天气
—- 说明: 彼时天气仿照魅族 Flyme 天气设计
—- 在 coolWeather 的基础上进行处理
总体思路
这就是总体的设计思路,至于后面其他的功能:选择地区,更新频率等功能可以之后再说
运行GIF
之所以大幅度提前展示 GIF 图,方便对后面布局部分有更好的理解
步骤
- 声明权限
- 依赖库
- 网络请求
- 网络解析
- 界面布局
- 最后
- 完整代码下载地址(github)
声明权限
因为我们需要用到百度SDK的定位服务,所以需要先下载百度中包含基础定位的 SDK(lbsyun.baidu.com/sdk/downloa…),解压后,将其中的 .jar 文件移动到 libs 文件夹中,在 main 目录下新建一个 jniLibs,将剩下的几个文件夹复制到里面,点击 Sync 按钮进行同步。 在 AndroidManifest 文件中声明权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
基本上都是 百度SDK 所需要的权限,其中部分权限是需要进行运行时处理的。同时还需要添加:
<meta-data
android:name="com.baidu.lbsapi.API_KEY"
android:value="SmwjePIXo1eeRGbjw8QKrbncWfgi5V0f" />
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" />
value 中填写自己申请获取的 Key,其他的格式都是固定的,这样我们就可以调用 基础定位 中的功能了
依赖库
compile 'org.litepal.android:core:1.4.1' // 数据库框架
compile 'com.squareup.okhttp3:okhttp:3.4.1' // 网络请求
compile 'com.google.code.gson:gson:2.7' // 网络解析
compile 'com.github.bumptech.glide:glide:3.8.0' // 图片加载
compile 'com.android.support:cardview-v7:24.2.1' // 卡片式布局
compile 'com.android.support:design:24.2.1' // Material Design中用到的依赖库
compile 'net.danlew:android.joda:2.9.9' // 时间处理
网络请求
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);
}
}
网路解析
本次的数据来源是和风天气(www.heweather.com/documents/a…),O(∩_∩)O哈哈~第一行代码的同学有没有很熟悉
参数
请求地址
{
"HeWeather5": [
{
"alarms": [
{
"level": "蓝色",
"stat": "预警中",
"title": "山东省青岛市气象台发布大风蓝色预警",
"txt": "青岛市气象台2016年08月29日15时24分继续发布大风蓝色预警信号:预计今天下午到明天,我市北风风力海上6到7级阵风9级,陆地4到5阵风7级,请注意防范。",
"type": "大风"
}
],
"aqi": {
"city": {
"aqi": "60",
"co": "0",
"no2": "14",
"o3": "95",
"pm10": "67",
"pm25": "15",
"qlty": "良", //共六个级别,分别:优,良,轻度污染,中度污染,重度污染,严重污染
"so2": "10"
}
},
"basic": {
"city": "青岛",
"cnty": "中国",
"id": "CN101120201",
"lat": "36.088000",
"lon": "120.343000",
"prov": "山东" //城市所属省份(仅限国内城市)
"update": {
"loc": "2016-08-30 11:52",
"utc": "2016-08-30 03:52"
}
},
"daily_forecast": [
{
"astro": {
"mr": "03:09",
"ms": "17:06",
"sr": "05:28",
"ss": "18:29"
},
"cond": {
"code_d": "100",
"code_n": "100",
"txt_d": "晴",
"txt_n": "晴"
},
"date": "2016-08-30",
"hum": "45",
"pcpn": "0.0",
"pop": "8",
"pres": "1005",
"tmp": {
"max": "29",
"min": "22"
},
"vis": "10",
"wind": {
"deg": "339",
"dir": "北风",
"sc": "4-5",
"spd": "24"
}
}
],
"hourly_forecast": [
{
"cond": {
"code": "100",
"txt": "晴"
},
"date": "2016-08-30 12:00",
"hum": "47",
"pop": "0",
"pres": "1006",
"tmp": "29",
"wind": {
"deg": "335",
"dir": "西北风",
"sc": "4-5",
"spd": "36"
}
}
],
"now": {
"cond": {
"code": "100",
"txt": "晴"
},
"fl": "28",
"hum": "41",
"pcpn": "0",
"pres": "1005",
"tmp": "26",
"vis": "10",
"wind": {
"deg": "330",
"dir": "西北风",
"sc": "6-7",
"spd": "34"
}
},
"status": "ok",
"suggestion": {
"comf": {
"brf": "较舒适",
"txt": "白天天气晴好,您在这种天气条件下,会感觉早晚凉爽、舒适,午后偏热。"
},
"cw": {
"brf": "较不宜",
"txt": "较不宜洗车,未来一天无雨,风力较大,如果执意擦洗汽车,要做好蒙上污垢的心理准备。"
},
"drsg": {
"brf": "热",
"txt": "天气热,建议着短裙、短裤、短薄外套、T恤等夏季服装。"
},
"flu": {
"brf": "较易发",
"txt": "虽然温度适宜但风力较大,仍较易发生感冒,体质较弱的朋友请注意适当防护。"
},
"sport": {
"brf": "较适宜",
"txt": "天气较好,但风力较大,推荐您进行室内运动,若在户外运动请注意防风。"
},
"trav": {
"brf": "适宜",
"txt": "天气较好,风稍大,但温度适宜,是个好天气哦。适宜旅游,您可以尽情地享受大自然的无限风光。"
},
"uv": {
"brf": "强",
"txt": "紫外线辐射强,建议涂擦SPF20左右、PA++的防晒护肤品。避免在10点至14点暴露于日光下。"
}
}
}
]
}
返回类型示例
我们后面申请数据采用的参数都为 城市名称,从返回示例中我们可以看出,结构和我们上次新闻API 返回的结构是有差异的,多嵌套了一层,不过 GSON 解析的方式还是一样的。在包名下新建一个 gson 文件夹,在里面新建对应数据的实体类:
AQI.class
public class AQI {
public AQICity city;
public class AQICity{
public String aqi;
public String pm25;
public String co;
public String o3;
public String pm10;
public String so2;
}
}
Basic.class
public class Basic {
@SerializedName("city")
public String cityName;
@SerializedName("id")
public String weatherId;
public Update update;
public class Update{
public String loc;
}
}
Forecast.class
public class Forecast {
public String date;
@SerializedName("tmp")
public Temperature temperature;
@SerializedName("cond")
public More more;
public class More{
@SerializedName("txt_d")
public String info;
@SerializedName("code_d")
public int code;
}
public class Temperature{
public String max;
public String min;
}
}
Hourly.class
public class Hourly {
public Cond cond;
public class Cond{
public String code;
public String txt;
}
public String date;
public String tmp;
}
Now.class
public class Now {
@SerializedName("tmp")
public String temperature;
@SerializedName("cond")
public More more;
public class More{
@SerializedName("txt")
public String info;
}
}
Suggestion.class
public class Suggestion {
@SerializedName("comf")
public Comfort comfort;
@SerializedName("cw")
public CarWash carWash;
public Sport sport;
@SerializedName("drsg")
public Clothes clothes;
@SerializedName("flu")
public Cold cold;
public UV uv;
public class UV{
@SerializedName("txt")
public String info;
@SerializedName("brf")
public String sign;
}
public class Cold{
@SerializedName("txt")
public String info;
@SerializedName("brf")
public String sign;
}
public class Clothes{
@SerializedName("txt")
public String info;
@SerializedName("brf")
public String sign;
}
public class Comfort{
@SerializedName("txt")
public String info;
@SerializedName("brf")
public String sign;
}
public class CarWash{
@SerializedName("txt")
public String info;
@SerializedName("brf")
public String sign;
}
public class Sport{
@SerializedName("txt")
public String info;
@SerializedName("brf")
public String sign;
}
}
最后就是返回数据的对应实体类 Weather.class
public class Weather {
public String status;
public Basic basic;
public AQI aqi;
public Now now;
public Suggestion suggestion;
@SerializedName("daily_forecast")
public List<Forecast> forecastList;
@SerializedName("hourly_forecast")
public List<Hourly> hourlyList;
}
在包名下新建目录 util , 在其中新建类:Utility.class
public class Utility {
/**
*
* 处理得到的 weather 数据,转化为 weather 对象
*/
public static Weather handleWeatherResponse(String response){
try{
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather5");
String weatherContent = jsonArray.getJSONObject(0).toString();
return new Gson().fromJson(weatherContent, Weather.class);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
界面布局
相信大家都看了运行的 GIF 图,可以看到主界面的布局还是比较繁琐的,所以引入布局不失为一个好的选择,主界面布局主要分为以下几个部分:
weather_title(标题栏)
weather_now(当前天气信息)
weather_hourly(小时天气预报)
weather_forecast(未来几天的天气预报)
weather_aqi(空气质量)
weather_suggestion(生活建议)
以下是各部分的代码:
weather_title:
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorWhite"
app:popupTheme="@style/ToolbarPopupTheme"
app:subtitleTextColor="@color/colorFont">
<TextView
android:id="@+id/title_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="@color/colorFont"
android:textSize="20sp"/>
</android.support.v7.widget.Toolbar>
Toolbar 中间放置了 title_city ,用来显示当前的城市名
weather_now
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="@color/colorWhite">
<RelativeLayout
android:id="@+id/weather_now_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorWhite">
<TextView
android:id="@+id/degree_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:textColor="@color/colorFont"
android:textSize="120sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/degree_text"
android:layout_alignTop="@+id/degree_text"
android:text="°"
android:textColor="@color/colorFont"
android:textSize="120sp"
/>
<TextView
android:id="@+id/weather_info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/degree_text"
android:layout_centerHorizontal="true"
android:textColor="@color/colorFont"
android:textSize="20sp"/>
<TextView
android:id="@+id/update_time_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/degree_text"
android:layout_centerHorizontal="true"
android:textColor="@color/colorFont"
android:textSize="12sp"/>
</RelativeLayout>
</RelativeLayout>
在视图中间,从上至下放置了三个TextView 控件:weather_info_text(天气状况),degree_text(天气温度),update_time_text(数据更新时间),另外一个“°”符号放在温度的右上角
weather_hourly:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/colorWhite">
<android.support.v7.widget.RecyclerView
android:id="@+id/weather_hourly"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
</LinearLayout>
为了支持小时天气预报部分可以直接水平滑动(不过免费用户可以得到的数据量好像不需要滑动 2333333),放置了一个 RecyclerView
weather_hourly_item
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="80dp"
android:layout_height="100dp"
android:padding="10dp">
<TextView
android:id="@+id/hour_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorFont"
android:layout_centerInParent="true"
android:textSize="14sp"/>
<TextView
android:id="@+id/hour_degree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorFont"
android:textSize="14sp"
android:layout_below="@+id/hour_text"
android:layout_centerHorizontal="true"
android:layout_marginTop="4dp"/>
<TextView
android:id="@+id/hout_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorFont"
android:textSize="14sp"
android:layout_above="@+id/hour_text"
android:layout_centerHorizontal="true"
android:layout_marginBottom="4dp"/>
</RelativeLayout>
小时天气预报的每个子项中,从上到下放置了三个TextView控件:hour_degree(天气温度),hour_text(天气描述),hout_time(时间)
weather_forecast:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWhite">
<LinearLayout
android:id="@+id/forecast_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
</LinearLayout>
这里采用的还是原方案,直接设置一个 LinearLayout 布局,后面直接在其中添加子布局,当然,大家也可以选用一个 ListView 来显示内容
weather_forecast_item
<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/data_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="left"
android:textColor="@color/colorFont"
/>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/weather_pic"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"/>
<TextView
android:id="@+id/info_text"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_gravity="center_vertical|left"
android:textColor="@color/colorFont"
android:gravity="left"
/>
</LinearLayout>
<TextView
android:id="@+id/max_min_text"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="right"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="@color/colorFont"
/>
</LinearLayout>
子项中水平放置了四个控件:data_text(预报日期),weather_pic(天气图片),info_text(天气信息),max_min_text(当天温度)
weather_aqi:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="75dp">
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/colorWhite"
>
<TextView
android:id="@+id/aqi_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/colorFont"
android:layout_centerInParent="true"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/aqi_text"
android:text="AQI 指数"
android:textSize="10sp"
android:textColor="@color/colorFont"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/colorWhite">
<TextView
android:id="@+id/pm25_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/colorFont"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/pm25_text"
android:text="PM2.5 指数"
android:textSize="10sp"
android:textColor="@color/colorFont"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/colorWhite">
<TextView
android:id="@+id/co_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/colorFont"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/co_text"
android:text="CO 指数"
android:textSize="10sp"
android:textColor="@color/colorFont"/>
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="75dp">
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/colorWhite">
<TextView
android:id="@+id/o3_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/colorFont"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/o3_text"
android:text="O3 指数"
android:textSize="10sp"
android:textColor="@color/colorFont"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/colorWhite">
<TextView
android:id="@+id/pm10_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/colorFont"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/pm10_text"
android:text="PM10 指数"
android:textSize="10sp"
android:textColor="@color/colorFont"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/colorWhite"
>
<TextView
android:id="@+id/so2_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/colorFont"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/so2_text"
android:text="SO2 指数"
android:textSize="10sp"
android:textColor="@color/colorFont"/>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
这里有个 2 × 3布局,上面一行为:aqi_text(AQI指数),pm25_text(PM2.5 指数),co_text(CO 指数),下面一行为:o3_text(O3 指数),pm10_text(PM10 指数),so2_text(SO2 指数)
weather_suggestion:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="150dp">
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:layout_marginRight="2.5dp"
android:background="@color/colorWhite"
>
<Button
android:id="@+id/comfort_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple" />
<TextView
android:id="@+id/comfort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="舒适指数"
android:textColor="@color/colorFont"
android:textSize="12sp"/>
<ImageView
android:id="@+id/comfort_pic"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_centerHorizontal="true"
android:layout_above="@+id/comfort"
android:layout_marginBottom="10dp"/>
<TextView
android:id="@+id/comfort_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorBlack"
android:layout_centerHorizontal="true"
android:layout_below="@+id/comfort"
android:textSize="18sp"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:layout_marginLeft="2.5dp"
android:layout_marginRight="2.5dp"
android:background="@color/colorWhite">
<Button
android:id="@+id/car_wash_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"/>
<TextView
android:id="@+id/car_wash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="洗车指数"
android:textColor="@color/colorFont"
android:textSize="12sp"/>
<ImageView
android:id="@+id/car_wash_pic"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_centerHorizontal="true"
android:layout_above="@+id/car_wash"
android:layout_marginBottom="10dp"/>
<TextView
android:id="@+id/car_wash_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/car_wash"
android:textColor="@color/colorBlack"
android:textSize="18sp"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:layout_marginLeft="2.5dp"
android:layout_marginRight="5dp"
android:background="@color/colorWhite">
<Button
android:id="@+id/sport_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"/>
<TextView
android:id="@+id/sport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="运动指数"
android:textColor="@color/colorFont"
android:textSize="12sp"/>
<ImageView
android:id="@+id/sport_pic"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_centerHorizontal="true"
android:layout_above="@+id/sport"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/sport_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/sport"
android:textColor="@color/colorBlack"
android:textSize="18sp"/>
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_marginTop="5dp">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:layout_marginRight="2.5dp"
android:layout_weight="1"
android:background="@color/colorWhite">
<Button
android:id="@+id/cold_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"/>
<TextView
android:id="@+id/cold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="感冒指数"
android:textColor="@color/colorFont"
android:textSize="12sp" />
<ImageView
android:id="@+id/cold_pic"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_above="@+id/cold"
android:layout_centerHorizontal="true"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/cold_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/cold"
android:layout_centerHorizontal="true"
android:textColor="@color/colorBlack"
android:textSize="18sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="2.5dp"
android:layout_marginRight="2.5dp"
android:layout_weight="1"
android:background="@color/colorWhite">
<Button
android:id="@+id/clothes_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"/>
<TextView
android:id="@+id/clothes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="穿衣指数"
android:textColor="@color/colorFont"
android:textSize="12sp" />
<ImageView
android:id="@+id/colthes_pic"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_above="@+id/clothes"
android:layout_centerHorizontal="true" />
<TextView
android:id="@+id/clothes_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/clothes"
android:layout_centerHorizontal="true"
android:textColor="@color/colorBlack"
android:textSize="18sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="2.5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:background="@color/colorWhite">
<Button
android:id="@+id/uv_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"/>
<TextView
android:id="@+id/uv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="紫外线指数"
android:textColor="@color/colorFont"
android:textSize="12sp" />
<ImageView
android:id="@+id/uv_pic"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_above="@+id/uv"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp" />
<TextView
android:id="@+id/uv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/uv"
android:layout_centerHorizontal="true"
android:textColor="@color/colorBlack"
android:textSize="18sp" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
也是2×3布局,上面一行为:comfort_text(舒适指数),car_wash_text(洗车指数),sport_text(运动指数),下面一行为:cold_text(感冒指数),clothes_text(穿衣指数),uv_text(紫外线指数)。在每个布局外面覆盖了一个 Button。除此之外,每个 text 都放置了一个 ImageView,用来放置对应的图片,不过这里没有放置,大家可以不用管
activity_main
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coor_layout"
android:layout_width="match_parent" android:layout_height="match_parent"
android:background="@color/colorBackground"
android:visibility="invisible">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp"
>
<include
android:id="@+id/tool_bar"
layout="@layout/weather_title"
app:layout_scrollFlags="scroll|enterAlways|snap"
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:overScrollMode="always"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/weather_now"/>
<include layout="@layout/weather_hourly"/>
<include layout="@layout/weather_space"/>
<include layout="@layout/weather_forecast"/>
<include layout="@layout/weather_space"/>
<include layout="@layout/weather_aqi"/>
<include layout="@layout/weather_space"/>
<include layout="@layout/weather_suggestion"/>
<include layout="@layout/weather_author"/>
</LinearLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>
引入对应的布局,布局 weather_space 是进行隔断处理的,这里可以看到最外层布局的可见属性为 invisible, 因为界面中存在很多不需要数据处理也可以显示的控件,不如摄氏度和描述性文字,解决方案是,默认不显示,在解析完数据之后重新设置属性为 visible
BaseActivity
public abstract class BaseActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initView();
initData();
initListener();
}
// 初始化控件
public abstract void initView();
// 初始化疏数据
public abstract void initData();
// 初始化监听器
public abstract void initListener();
// 判断是否有网络
public NetworkInfo getNetworkInfo(){
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo;
}
// Toast 长时间
public void showShort(String msg){
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
在这里,写了一个 BaseActivity ,还是推荐大家使用这样的方式,先自己创建一个基类,更加方便自己对代码进行结构化处理,这里我我定义了三个抽象函数:initView() 初始化控件, initData() 初始化数据, initListener() 初始化监听器
和两个定义好的函数:根据 getNetworkInfo()函数返回的结果判定是否存在网络, showShort()就是对Toast 简单的包装
,这样在 Activity 中只需要重写这几个方法即可
接下来是 MainActivity,代码冗长,按照定义的基类方法讲:
@Override
public void initView() {
setContentView(R.layout.acticity_main);
Toolbar toolbar = (Toolbar)findViewById(R.id.tool_bar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
actionBar.setDisplayShowTitleEnabled(false);
}
// 初始化各种控件
weatherLayout = (ScrollView)findViewById(R.id.weather_layout);
titleCity = (TextView)findViewById(R.id.title_city);
forecastLayout = (LinearLayout)findViewById(R.id.forecast_layout);
coordinatorLayout = (CoordinatorLayout)findViewById(R.id.coor_layout);
// weather_now
degreeText = (TextView)findViewById(R.id.degree_text);
weatherInfoText = (TextView)findViewById(R.id.weather_info_text);
weaherNowLayout = (RelativeLayout)findViewById(R.id.weather_now_layout);
updateTimeText = (TextView)findViewById(R.id.update_time_text);
// weather_hour
hourDegree = (TextView)findViewById(R.id.hour_degree);
hourText = (TextView)findViewById(R.id.hour_text);
hourTime = (TextView)findViewById(R.id.hout_time);
recyclerView = (RecyclerView)findViewById(R.id.weather_hourly);
hourAdapter = new HourAdapter(hourList);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(hourAdapter);
// weather_aqi
aqiText = (TextView)findViewById(R.id.aqi_text);
pm25Text = (TextView)findViewById(R.id.pm25_text);
coText = (TextView)findViewById(R.id.co_text);
o3Text = (TextView)findViewById(R.id.o3_text);
pm10Text = (TextView)findViewById(R.id.pm10_text);
so2Text = (TextView)findViewById(R.id.so2_text);
// weather_suggestion
comfortText = (TextView)findViewById(R.id.comfort_text);
carWashText = (TextView)findViewById(R.id.car_wash_text);
sportText = (TextView)findViewById(R.id.sport_text);
uvText = (TextView)findViewById(R.id.uv_text);
clothesText = (TextView)findViewById(R.id.clothes_text);
coldText = (TextView)findViewById(R.id.cold_text);
comfortBtn = (Button)findViewById(R.id.comfort_button);
carWashBtn = (Button)findViewById(R.id.car_wash_button);
sportBtn = (Button)findViewById(R.id.sport_button);
uvBtn = (Button)findViewById(R.id.uv_button);
clothesBtn = (Button)findViewById(R.id.clothes_button);
coldBtn = (Button)findViewById(R.id.cold_button);
// LBS
mlocationClient = new LocationClient(getApplicationContext());
mlocationClient.registerLocationListener(new MyLocationListener());
List<String> permissionList = new ArrayList<>();
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.READ_PHONE_STATE);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!permissionList.isEmpty()){
String[] permissions = permissionList.toArray(new String[permissionList.size()]);
ActivityCompat.requestPermissions(MainActivity.this, permissions, 1);
}
swipeRefresh = (SwipeRefreshLayout)findViewById(R.id.swipe_refresh);
swipeRefresh.setColorSchemeColors(getResources().getColor(R.color.colorAccent));
}
在 initView 中初始化控件,为了方便起见,我们在这里进行运行时权限处理,先将 List 将没有获得授权的权限进行添加,然后集中申请
/**
* 权限申请处理
*/
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0){
for (int result:grantResults){
if (result != PackageManager.PERMISSION_GRANTED){
// 如果存在某个权限没有处理
finish();
}
}
}else{
// 发生未知错误
showShort("权限申请出现位置错误");
}
break;
default:
}
}
在 onRequestPermissionsResult() 方法,如果存在权限未被允许,则直接退出
public void initListener() {
comfortBtn.setOnClickListener(this);
carWashBtn.setOnClickListener(this);
sportBtn.setOnClickListener(this);
uvBtn.setOnClickListener(this);
clothesBtn.setOnClickListener(this);
coldBtn.setOnClickListener(this);
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (getNetworkInfo() == null){
Snackbar.make(swipeRefresh, "当前无网络,无法刷新 %>_<% ",Snackbar.LENGTH_LONG).setAction("去设置网络", new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Settings.ACTION_SETTINGS);
startActivity(intent);
}
}).show();
swipeRefresh.setRefreshing(false);
}else{
showAnimationAlpha(weaherNowLayout);
}
}
});
}
在 initListener()方法中,对 Suggestion 部分的按钮设置监听器,同时对刷新进行监听,如果有网络的话,则执行 showAnimationAlpha(weaherNowLayout)方法,否则,显示一个可交互通知,点击通知上面的按钮,可以跳转到设置界面
private void showAnimationAlpha(final View view){
Animation alpha = AnimationUtils.loadAnimation(MainActivity.this,R.anim.alpha_before);
view.startAnimation(alpha);
alpha.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
String cityName = prefs.getString("cityName", null);
requestWeather(cityName);
Animation alpha = AnimationUtils.loadAnimation(MainActivity.this,R.anim.alpha_after);
view.startAnimation(alpha);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
在 showAnimationAlpha()方法中,我设置了一个透明度动画,同时进行监听,如果动画结束(也就是此时完全透明的时候),执行 requestWeather()方法,对数据进行更新,之后便开启一个透明度由 0 到 1 的动画
public void initData() {
String cityName = getIntent().getStringExtra("cityName");
if (!TextUtils.isEmpty(cityName)){
requestWeather(cityName);
}else{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = prefs.getString("weatherResponse", null); // weather 保存API 返回的字符串
if (weatherString != null){
// 有缓存时直接解析天气数据
Weather weather = Utility.handleWeatherResponse(weatherString);
showWeatherInfo(weather);
coordinatorLayout.setVisibility(View.VISIBLE);
}else {
// 无缓存时向服务器查询数据
if (getNetworkInfo() != null && getNetworkInfo().isAvailable()){
// 查询完之后显示 coordinatorLayout.setVisibility(View.VISIBLE);
LocationClientOption option = new LocationClientOption();
option.setIsNeedAddress(true);
mlocationClient.setLocOption(option);
mlocationClient.start();
}else{
showDialog();
}
}
Intent intent = new Intent(MainActivity.this, AutoUpdateService.class);
startService(intent);
}
}
在 initData()方法执行一开始流程图的逻辑,在 else 分支中查看是否存在 weatherResponse ,如果有则直接进行加载,如果没有则通过getNetworkInfo()方法的结果来判断是否存在网络,如果当前有网路可用则调用:
LocationClientOption option = new LocationClientOption();
option.setIsNeedAddress(true); // 获得详细地址
mlocationClient.setLocOption(option);
mlocationClient.start();
调用 mlocationClient.start() 方法后,会跳转到:
public class MyLocationListener implements BDLocationListener{
@Override
public void onReceiveLocation(BDLocation bdLocation) {
currentPosition = bdLocation.getCity();
requestWeather(currentPosition);
showShort(currentPosition + " 定位成功");
}
@Override
public void onConnectHotSpotMessage(String s, int i) {
}
}
则直接通过自动定位得到的城市名称来调用 requestWeather()方法,同时显示定位成功
如果当前没有网路,则调用 showDialog()
public void showDialog(){
AlertDialog.Builder alertDialog = new AlertDialog.Builder(MainActivity.this);
alertDialog.setMessage("当前无网络,请先打开网络");
alertDialog.setCancelable(false);
alertDialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_SETTINGS);
startActivity(intent);
TaskKiller.dropAllAcitivty();
}
});
alertDialog.show();
}
显示对话框,显示当前无网路,点击确定后,直接退出所有的 Activity
接下来就是程序的主要函数部分了:
public void requestWeather(final String cityName){
String address = "https://api.heweather.com/v5/weather?city=" + cityName + "&key=bc0418b57b2d4918819d3974ac1285d9";
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
showShort("获取天气信息1失败");
}
});
}
@Override
public void onResponse(Call call, 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(MainActivity.this).edit();
editor.putString("weatherResponse", responseText);
editor.putString("cityName",cityName);
editor.apply();
showWeatherInfo(weather);
}else{
showShort("获取天气信息2失败");
}
}
});
}
});
swipeRefresh.setRefreshing(false);
}
在 requestWeather()方法中,通过城市名来向接口请求数据,如果请求成功,在onResponse()方法中将相应的数据(weatherResponse,cityName)覆盖之前的 SharedPreferences文件,然后调用 showWeatherInfo()方法
private void showWeatherInfo(Weather weather){
String cityName = weather.basic.cityName;
String degree = weather.now.temperature ;
String weatherInfo = weather.now.more.info;
String updateTime = weather.basic.update.loc;
titleCity.setText(cityName);
degreeText.setText(degree);
weatherInfoText.setText(weatherInfo);
updateTimeText.setText("数据更新时间: " + updateTime.split(" ")[1]);
forecastLayout.removeAllViews();
for (Forecast forecast : weather.forecastList){
// 将未来几天的天气添加到视图中
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.weather_forecast_item, forecastLayout, false);
TextView dateText = (TextView)view.findViewById(R.id.data_text);
TextView infoText = (TextView)view.findViewById(R.id.info_text);
TextView maxMinText = (TextView)view.findViewById(R.id.max_min_text);
ImageView weatherPic = (ImageView)view.findViewById(R.id.weather_pic);
// 动态获取 资源id
String weatherCode = "weather_"+forecast.more.code;
int resId = getResources().getIdentifier(weatherCode, "drawable", this.getPackageName());
if (resId != 0){
weatherPic.setImageResource(resId);
}
dateText.setText(Time.parseTime(forecast.date));
infoText.setText(forecast.more.info);
maxMinText.setText(forecast.temperature.max + " ~ " + forecast.temperature.min);
forecastLayout.addView(view);
}
hourList.clear();
for (Hourly hourly:weather.hourlyList){
Hour hour = new Hour();
hour.setDegree(hourly.tmp + "°" );
hour.setText(hourly.cond.txt);
hour.setTime(hourly.date.split(" ")[1]);
hourList.add(hour);
}
hourAdapter.notifyDataSetChanged();
// weather_aqi 空气质量
{
String infoText = "无";
if (weather.aqi.city.aqi != null){
aqiText.setText(weather.aqi.city.aqi);
aqiText.getPaint().setFakeBoldText(true);
}else{
aqiText.setText(infoText);
}
if (weather.aqi.city.pm25 != null){
pm25Text.setText(weather.aqi.city.pm25);
pm25Text.getPaint().setFakeBoldText(true);
}else{
pm25Text.setText(infoText);
}
if (weather.aqi.city.co != null){
coText.setText(weather.aqi.city.co);
coText.getPaint().setFakeBoldText(true);
}else{
coText.setText(infoText);
}
if (weather.aqi.city.o3 != null){
o3Text.setText(weather.aqi.city.o3);
o3Text.getPaint().setFakeBoldText(true);
}else{
o3Text.setText(infoText);
}
if (weather.aqi.city.pm10 != null){
pm10Text.setText(weather.aqi.city.pm10);
pm10Text.getPaint().setFakeBoldText(true);
}else{
pm10Text.setText(infoText);
}
if (weather.aqi.city.so2 != null){
so2Text.setText(weather.aqi.city.so2);
so2Text.getPaint().setFakeBoldText(true);
}else{
so2Text.setText(infoText);
}
}
comfortSign = weather.suggestion.comfort.sign;
carWashSign = weather.suggestion.carWash.sign;
sportSign = weather.suggestion.sport.sign;
uvSign = weather.suggestion.uv.sign;
clothesSign = weather.suggestion.clothes.sign;
coldSign = weather.suggestion.cold.sign;
comfortText.setText(comfortSign);
comfortText.getPaint().setFakeBoldText(true);
carWashText.setText(carWashSign);
carWashText.getPaint().setFakeBoldText(true);
sportText.setText(sportSign);
sportText.getPaint().setFakeBoldText(true);
uvText.setText(uvSign);
uvText.getPaint().setFakeBoldText(true);
clothesText.setText(clothesSign);
clothesText.getPaint().setFakeBoldText(true);
coldText.setText(coldSign);
coldText.getPaint().setFakeBoldText(true);
comfortInfo = weather.suggestion.comfort.info;
carWashInfo = weather.suggestion.carWash.info;
sportInfo = weather.suggestion.sport.info;
uvInfo = weather.suggestion.uv.info;
clothesInfo = weather.suggestion.clothes.info;
coldInfo = weather.suggestion.cold.info;
weatherLayout.setVisibility(View.VISIBLE);
coordinatorLayout.setVisibility(View.VISIBLE);
}
在 showWeatherInfo() 方法中进行的操作基本就是将通过得到的 Weather 对象中获取数据,然后将数值赋值到控件上,因为在 Hourly 中,我们采用的是 RecyclerView,所以需要使用 notifyDataSetChanged() 方法让适配器更新数据。在最后的部分:
weatherLayout.setVisibility(View.VISIBLE);
coordinatorLayout.setVisibility(View.VISIBLE);
将界面设置为可见
需要注意的一部分代码是:
String weatherCode = "weather_"+forecast.more.code;
int resId = getResources().getIdentifier(weatherCode, "drawable", this.getPackageName());
if (resId != 0){
weatherPic.setImageResource(resId);
}
因为返回的数据中包含天气码,每个天气码都有一个对应的天气图片与之对应,但是我们总不能对几十个天气图片进行 if 判定,所以我们可以通过 getIdentifier() 方法动态的匹配资源 ID, 第一个参数为资源ID 的String 格式,第二个为资源所在的文件夹,第三个一般情况下是包名
另外,我们在 dateText.setText(Time.parseTime(forecast.date));
中,这里用到了一个 Time.parseTime() 的函数对解析得到的天气数据进行二次
public class Time {
/**
* 输入时间 XXXX-XX-XX 的字符串, 放回中文指代的时间, 比如 "今天 02/09"
*/
public static String parseTime(String timeText){
DateTime dateTime = new DateTime();
String[] time = timeText.split("-");
int currentMonth = dateTime.getMonthOfYear();
int currentDay = dateTime.getDayOfMonth();
int currentWeak = dateTime.getDayOfWeek();
int currentYear = dateTime.getYear();
int month = Integer.parseInt(time[1]);
int day = Integer.parseInt(time[2]);
int year = Integer.parseInt(time[0]);
int offset = 0; // 相差量
if (year == currentYear){
//如果是同一年:
if (month == currentMonth){
// 如果是同一个月
offset = day - currentDay;
}else{
offset = day + parseMonth(currentMonth, currentYear) - currentDay;
}
}else{
offset = 31 - currentDay + day;
}
String monthAndDay = time[1] + "/" + time[2];
if (offset == 0) return "今天 " + monthAndDay;
if (offset == 1) return "明天 " + monthAndDay;
return parseWeak(currentWeak + offset) + " " + monthAndDay;
}
/**
* 输入一个数字, 输出是星期几的字符串: 2 -> 周二
*/
public static String parseWeak(int weak){
String[] weakday = new String[]{"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
int index = (weak - 1)%7;
return weakday[index];
}
/**
* 输入月份和年份, 输出该月份的天数
*/
public static int parseMonth(int month, int year){
switch (month){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:return 31;
case 2:
if (parseYear(year)) {
return 29;
}
return 28;
default:
return 30;
}
}
/**
* 输入年份, 判断是否是闰年
*/
public static boolean parseYear(int year){
String yearOfString = String.valueOf(year);
int len = yearOfString.length();
char lastOne = yearOfString.charAt(len - 1);
char lastTwo = yearOfString.charAt(len - 2);
if (lastOne == lastTwo && lastOne == '0'){
if (year % 400 == 0){
return true;
}else{
return false;
}
}
if (year % 4 == 0){
return true;
}else{
return false;
}
}
}
这个类的作用主要是对天气数据进行处理,以此得到更加人性化的设计:
// 今天是 2017/5/16
2017/5/16 -> 今天
2017/5/17 -> 明天
2017/5/18 -> 周四 5/18
2017/5/19 -> 周五 5/19
大概就是这样的作用,没什么好介绍的,不过这个类的作用是两天的间隔相差在一个月之内(毕竟….天气预报)
@Override
public void onClick(View v) {
// 通过 SuggestionInfoActivity 中的静态方法直接传值
switch (v.getId()){
case R.id.comfort_button:
SuggestionInfoActivity.actionStart(this, comfortInfo,comfortSign,"舒适度指数");
break;
case R.id.car_wash_button:
SuggestionInfoActivity.actionStart(this, carWashInfo,carWashSign,"洗车指数");
break;
case R.id.sport_button:
SuggestionInfoActivity.actionStart(this, sportInfo,sportSign,"运动指数");
break;
case R.id.cold_button:
SuggestionInfoActivity.actionStart(this, coldInfo,coldSign,"感冒指数");
break;
case R.id.clothes_button:
SuggestionInfoActivity.actionStart(this, clothesInfo,clothesSign,"穿衣指数");
break;
case R.id.uv_button:
SuggestionInfoActivity.actionStart(this, uvInfo,uvSign,"紫外线指数");
break;
default:
break;
}
}
在 Suuggestion部分的button 点击事件中,调用了SuggestionInfoActivity 的静态方法 actionStart( ),并且传入对应的四个参数
@Override
protected void onDestroy() {
super.onDestroy();
mlocationClient.stop();
}
在 onDestor( )方法中停止 mlocationClient,毕竟,不能总在定位
public static void actionStart(Context context ,String cityName){
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("cityName", cityName);
context.startActivity(intent);
}
这是 MainActivity 中的跳转静态函数,用来在选择地区时跳转,传入选择的地区名称
activity_suggestion_info
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorBackground"
>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_suggestion_info"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorWhite"
app:titleTextColor="@color/colorFont"
/>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_marginTop="15dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
app:cardElevation="2dp"
app:cardCornerRadius="0dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorWhite">
<TextView
android:id="@+id/suggestion_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:textSize="24sp"
android:textColor="@color/colorRed"/>
<TextView
android:id="@+id/suggestion_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:textSize="14sp"
android:textColor="@color/colorFont"/>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
布局很简单, CardView 上有两个 TextView 控件
SuggestionInfoActivity
public class SuggestionInfoActivity extends BaseActivity {
private TextView suggestText;
private TextView suggestTitle;
private String info;
private String sign;
private String source;
@Override
public void initView() {
setContentView(R.layout.activity_suggestion_info);
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar_suggestion_info);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true);
}
suggestText = (TextView)findViewById(R.id.suggestion_text);
suggestTitle = (TextView)findViewById(R.id.suggestion_title);
}
@Override
public void initListener() {
}
@Override
public void initData() {
info = getIntent().getStringExtra("info");
sign = getIntent().getStringExtra("sign");
source = getIntent().getStringExtra("source");
getSupportActionBar().setTitle(source);
suggestTitle.setText(sign);
suggestTitle.getPaint().setFakeBoldText(true);
suggestText.setText(info);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
}
return true;
}
@Override
public void onClick(View v) {
}
public static void actionStart(Context context, String info, String sign, String source){
Intent intent = new Intent(context, SuggestionInfoActivity.class);
intent.putExtra("info", info);
intent.putExtra("sign", sign);
intent.putExtra("source", source);
context.startActivity(intent);
}
}
SuggestionInfoActivity 中的静态函数 actionStart( ) 传入四个参数,其他 Activity 来调用这个静态函数可以实现更加方便的跳转,而在 SuggestionInfoActivity 也可以更加方便处理传过来的参数,将传入的参数显示在控件上
新建 menu文件夹,新建文件 menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/setting"
android:title="设置"
app:showAsAction="never"/>
<item
android:id="@+id/night_model"
android:title="夜间模式"
app:showAsAction="never"/>
</menu>
两个菜单项 “设置”,“夜间模式”
在 MainActivity 中添加如下代码:
/**
* 添加 actionbar 菜单项
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
/**
* 菜单点击事件响应
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.setting:
// 跳转到设置界面
Intent intent1 = new Intent(this, SettingActivity.class);
intent1.putExtra("weather_title","设置");
startActivity(intent1);
break;
case R.id.night_model:
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = pref.edit();
boolean isNight = pref.getBoolean("isNight", false);
if (isNight){
// 如果已经是夜间模式
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
recreate();
editor.putBoolean("isNight", false);
editor.apply();
}else{
// 如果是日间模式
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
recreate();
editor.putBoolean("isNight", true);
editor.apply();
}
break;
}
return true;
}
我们先忽略 night_model 的点击事件,当点击 setting 按钮,跳转到设置界面 activity_setting
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorBackground"
>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_setting"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorWhite"
app:titleTextColor="@color/colorFont"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="15dp"
android:orientation="vertical"
android:background="@color/colorBackground"
>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/choose_area"
android:text="选择地区"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
android:layout_marginTop="10dp"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/auto_update_time"
android:text="更新频率"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
android:layout_marginTop="10dp"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/about_app"
android:text="关于天气"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
</LinearLayout>
</LinearLayout>
布局中存在三个按钮,选择地区,更新频率,关于天气
public class SettingActivity extends BaseActivity{
private Button chooseArea;
private Button aboutApplication;
private Button autoUpdateTime;
@Override
public void initView() {
setContentView(R.layout.activity_setting);
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar_setting);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle("设置");
}
chooseArea = (Button)findViewById(R.id.choose_area);
aboutApplication = (Button)findViewById(R.id.about_app);
autoUpdateTime = (Button)findViewById(R.id.auto_update_time);
}
@Override
public void initListener() {
chooseArea.setOnClickListener(this);
aboutApplication.setOnClickListener(this);
autoUpdateTime.setOnClickListener(this);
}
@Override
public void initData() {
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
break;
default:
break;
}
return true;
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.choose_area:
// 跳转到选择地区界面
actionStart(ChooseAreaActivity.class);
break;
case R.id.about_app:
// 跳转到关于天气界面
actionStart(AboutApplicationActivity.class);
break;
case R.id.auto_update_time:
//跳转到自动更新频率界面
actionStart(AutoUpdateTimeAcitivity.class);
break;
default:
}
}
public void actionStart(Class<?> c){
Intent intent = new Intent(this, c);
startActivity(intent);
}
}
点击不同按钮进行界面跳转时会调用 actionStart( )函数,参数之后一个类名,作用是从当前 Activity 跳转到其他的 Activity
当点击 第一个按钮 choose_area,跳转到选择地区界面,activity_choose_area:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="@color/colorBackground">
<android.support.v7.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorWhite"
app:titleTextColor="@color/colorFont"/>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_margin="20dp"
android:background="@color/colorWhite"
app:cardBackgroundColor="@color/colorWhite"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:background="@color/colorWhite"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWhite">
<EditText
android:id="@+id/search_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:hint="查找的城市"
android:textColorHint="@color/colorFont"
android:textColor="@color/colorFont"/>
<Button
android:id="@+id/search_button"
android:layout_width="wrap_content"
android:text="搜索"
android:textColor="@color/colorFont"
android:layout_height="wrap_content"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</LinearLayout>
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"/>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
app:cardBackgroundColor="@color/colorWhite">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:background="@color/colorWhite"
android:orientation="vertical">
<TextView
android:text="查找记录"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
<ListView
android:layout_marginTop="10dp"
android:id="@+id/list_view_recond"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"/>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
存在两个卡片布局,上一个卡片布局中包含三个控件,EditText,Button,ListView。其中 EditText 用来输入搜索的城市,点击 Button 进行搜索,结果显示在 ListView 上。下一个布局包含两个控件,TextView, ListView, 其中, ListView 显示搜索记录
由于要显示搜索记录,自然炫耀保存数据,这里我选择 LitePal 来保存搜索记录,这里我对搜索记录的处理是这样:只有搜索之后得到结果的搜索才能保存,其他的搜索没有得到有效的数据,自然没有保存的必要。我们先建立数据库,之前在 build.gradle 中已经声明了依赖库,我们还需要在 AndroidManifest 中添加 name 属性,以便 LitePal 可以全局获取 Context
在包下建立一个文件夹 db ,在文件夹中新建一个文件 CityRecond 用来记录搜索记录:
public class CityRecond extends DataSupport{
private int id;
private String cityName;
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;
}
}
在 main 目录下新建文件夹 assets ,新建 litepal 文件:
<litepal>
<dbname value="CoolWeather"/>
<version value="1"/>
<list>
<mapping class="com.example.coolweather.db.CityRecond"></mapping>
</list>
</litepal>
这样, 数据库基本就建立好了。 因为我们在搜索界面只需要检索城市信息,城市请求URL为:
在 gson 目录下创建对应的实体类:
City.class
public class City {
public Basic basic;
public String status;
public class Basic{
public String city;
public String cnty;
public String lat;
public String lon;
public String prov;
}
}
在 Utility 类中添加方法
public static City handleCityResponse(String response){
try{
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather5");
String cityContent = jsonArray.getJSONObject(0).toString();
return new Gson().fromJson(cityContent, City.class);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
直接转化为 City 类
回到 ChooaseAreaActivity
public class ChooseAreaActivity extends BaseActivity {
private EditText searchText;
private Button searchButton;
private ListView listView;
private ListView listViewRecond;
private List<String> cityList = new ArrayList<>();
private List<String> recondList = new ArrayList<>();
ArrayAdapter<String> adapter;
ArrayAdapter<String> recondAdapter;
@Override
public void initView() {
setContentView(R.layout.activity_choose_area);
Toolbar toolbar = (Toolbar)findViewById(R.id.tool_bar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle("选择地区");
}
searchText = (EditText)findViewById(R.id.search_text);
searchButton = (Button)findViewById(R.id.search_button);
listView = (ListView)findViewById(R.id.list_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, cityList);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (getNetworkInfo() != null && getNetworkInfo().isAvailable()){
String cityName = cityList.get(position);
MainActivity.actionStart(ChooseAreaActivity.this, cityName);
finish();
}else{
showShort("当前没有网络");
}
}
});
listViewRecond = (ListView)findViewById(R.id.list_view_recond);
recondAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, recondList);
listViewRecond.setAdapter(recondAdapter);
listViewRecond.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (getNetworkInfo() != null && getNetworkInfo().isAvailable()){
String cityName = recondList.get(position);
MainActivity.actionStart(ChooseAreaActivity.this, cityName);
finish();
}else{
showShort("当前没有网络");
}
}
});
showRecond();
}
@Override
public void initData() {
LitePal.getDatabase();
}
@Override
public void initListener() {
searchButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.search_button:
showSearchResult();
break;
default:
}
}
public void showSearchResult(){
String cityName = searchText.getText().toString();
if (!TextUtils.isEmpty(cityName)){
// 如果不为空,则进行查询
if (getNetworkInfo() != null && getNetworkInfo().isAvailable()){
String address = "https://api.heweather.com/v5/search?city=" + cityName + "&key=bc0418b57b2d4918819d3974ac1285d9";
requestData(address);
}else{
showShort("当前网络无连接");
}
}else{
showShort("请输入城市名称");
}
}
public void requestData(String address){
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
showShort("请求数据失败");
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseText = response.body().string();
final City city = Utility.handleCityResponse(responseText);
runOnUiThread(new Runnable() {
@Override
public void run() {
showCity(city);
}
});
}
});
}
public void showCity(City city){
cityList.clear();
if ("ok".equals(city.status) && city != null){
cityList.add(city.basic.city);
solveSearchRecond(city.basic.city);
adapter.notifyDataSetChanged();
listView.setSelection(0);
}else{
showShort("未找到该城市");
}
}
/**
* 保存城市名称 -> 数据库
*/
public void solveSearchRecond(String cityName){
CityRecond cityRecond = new CityRecond();
cityRecond.setCityName(cityName);
cityRecond.save();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
break;
}
return true;
}
/**
* 显示数据库中保存的信息
*/
public void showRecond(){
recondList.clear();
List<CityRecond> list = DataSupport.select("cityName").find(CityRecond.class);
for (CityRecond recond:list){
recondList.add(recond.getCityName());
}
recondAdapter.notifyDataSetChanged();
listViewRecond.setSelection(0);
}
}
在 initData( ) 方法中调用了 LitePal.getDatabase( ) 创建数据库同时创建表,当点击搜索按钮时,调用 showSearchResult( )方法,判定是否为空,如果为空,则调用showShort(“请输入城市名称”) 提醒,如果不为空,通过判定getNetworkInfo( ) 是否存在网络,如果不存在则提醒,否则执行requestData( ) 方法,如果回调成功,则调用 showCity( ) 方法,传入解析得到的 City 对象,在 showCity( ) 方法中,将数据显示在搜索结果中,同时将数据保存在数据库文件中,每次打开 ChooseAreaActivity 都会调用 showRecond( ) 方法,通过DataSupport.select( )找到所有保存的 cityName, 并且将数据显示在 ListView 中。 对两个 listView的子项都进行了监听,点击子项时,还是先判断是否存在网络,如果存在网络,则调用 MainActivity 中的静态方法,传入 cityName 参数,这就是在 MainActivity 中一开始需要判定的原因,如果是 Activity 跳转,则需要根据传入的 cityName 来进行请求,解析等一系列步骤,但是如果是从桌面打开,则可以直接通过缓存文件来解析数据
到这里,ChooseAreaActivity 的任务就结束了,开始看 activity_setting 界面的第二个按钮,更新频率
点击更新频率跳转到 activity_auto_update_time
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorBackground"
>
<android.support.v7.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorWhite"
app:titleTextColor="@color/colorFont"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="15dp"
android:orientation="vertical"
android:background="@color/colorBackground"
>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/hour1"
android:text="1 小时"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
android:layout_marginTop="10dp"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/hour2"
android:text="两小时"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
android:layout_marginTop="10dp"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/hour3"
android:text="三小时"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
android:layout_marginTop="10dp"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/hour5"
android:text="五小时"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
android:layout_marginTop="10dp"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/hour10"
android:text="十小时"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorWhite"
android:layout_marginTop="10dp"
app:cardCornerRadius="0dp"
app:cardElevation="1dp">
<Button
android:id="@+id/hour0"
android:text="不进行自动更新"
android:textColor="@color/colorFont"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle"
android:background="@drawable/ripple"
/>
</android.support.v7.widget.CardView>
</LinearLayout>
</LinearLayout>
界面很简单,有六个按钮,来控制更新的频率和是否更新,当然这个数值还是根据 SharedPerferenced 文件来传递的
AutoUpdateTimeActivity
public class AutoUpdateTimeAcitivity extends BaseActivity {
private Button button1;
private Button button2;
private Button button3;
private Button button5;
private Button button10;
private Button button0;
@Override
public void initView() {
setContentView(R.layout.activity_auto_update_time);
Toolbar toolbar = (Toolbar)findViewById(R.id.tool_bar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle("更新频率");
}
button0 = (Button)findViewById(R.id.hour0);
button1 =(Button)findViewById(R.id.hour1);
button2 = (Button)findViewById(R.id.hour2);
button3 = (Button)findViewById(R.id.hour3);
button5 = (Button)findViewById(R.id.hour5);
button10 = (Button)findViewById(R.id.hour10);
}
@Override
public void initData() {
}
@Override
public void initListener() {
button0.setOnClickListener(this);
button2.setOnClickListener(this);
button1.setOnClickListener(this);
button3.setOnClickListener(this);
button5.setOnClickListener(this);
button10.setOnClickListener(this);
}
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
switch (v.getId()){
case R.id.hour0:
editor.putBoolean("isUpdateTime", false);
break;
case R.id.hour1:
editor.putBoolean("isUpdateTime",true);
editor.putInt("autoUpdateTime", 60);
break;
case R.id.hour2:
editor.putBoolean("isUpdateTime",true);
editor.putInt("autoUpdateTime", 120);
break;
case R.id.hour3:
editor.putBoolean("isUpdateTime",true);
editor.putInt("autoUpdateTime", 180);
break;
case R.id.hour5:
editor.putBoolean("isUpdateTime",true);
editor.putInt("autoUpdateTime", 300);
break;
case R.id.hour10:
editor.putBoolean("isUpdateTime",true);
editor.putInt("autoUpdateTime", 600);
break;
default:
}
editor.apply();
Intent intent = new Intent(this, AutoUpdateService.class);
startService(intent);
showShort("设置成功");
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
break;
default:
}
return true;
}
}
可以很清楚的看到代码逻辑,点击不同的按键,会修改 isUpdateTime 和 autoUpdateTime 的值,然后会启动 AutoUpdateService:
public class AutoUpdateService extends Service {
public AutoUpdateService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
boolean isUpdateTime = pref.getBoolean("isUpdateTime", true);
if (isUpdateTime == true){
updateWeather();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int autoUpdateTime = pref.getInt("autoUpdateTime", 60);
int anHour = autoUpdateTime * 60 * 1000; // 这是 60 分钟的毫秒数
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this, AutoUpdateService.class);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
manager.cancel(pi);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
}
return super.onStartCommand(intent, flags, startId);
}
/**
* 更新信息
*/
private void updateWeather(){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String cityName = prefs.getString("cityName", null);
if(cityName != null){
String address = "https://api.heweather.com/v5/weather?city=" + cityName + "&key=bc0418b57b2d4918819d3974ac1285d9";
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseText = response.body().string();
Weather weather = Utility.handleWeatherResponse(responseText);
if (weather != null && "ok".equals(weather.status)){
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();
editor.putString("weatherResponse", responseText);
editor.apply();
}
}
});
}
}
}
在 AutoUpdateService 中,updateWeather( )是通过已经存在的缓存文件中得到 cityName 的值,重新发起请求,如果请求成功,并且数据可以成功解析的话,则将成功返回的数据保存在 weatherResponse 中,这样,就可以确保在每次打开该应用程序的时候,应用程序可以解析最新的数据。
在 onStartCommand( ) 方法中,我们先查看 isUpdateTime 的值,如果为 false(也就是点击了不进行自动更新),则不执行
updateWeather( ) 方法,如果为 true, 则继续查看 autoUpdateTime 的值,以此来决定定时执行的时间间隔,这样也就完成了定时更新天气数据的任务
至于第三个按钮,也就是关于天气 activity_about_application:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="@color/colorBackground">
<android.support.v7.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorWhite"
app:titleTextColor="@color/colorFont"/>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:background="@color/colorWhite"
app:cardBackgroundColor="@color/colorWhite"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:background="@color/colorWhite"
android:orientation="vertical">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerCrop"
android:src="@mipmap/logo"
android:layout_gravity="center_horizontal"
android:layout_marginTop="40dp"/>
<TextView
android:text="版本号 1.0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorFont"
android:layout_marginTop="7dp"
android:layout_gravity="center_horizontal"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:text="应用名称 : 彼时天气"
android:textSize="14sp"
android:textColor="@color/colorFont"
android:gravity="left"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="应用作者 : lentitude"
android:textColor="@color/colorFont"
android:textSize="14sp"
android:gravity="left"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@android:color/transparent">
<TextView
android:text="Github地址 :"
android:textSize="14sp"
android:textColor="@color/colorFont"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/github"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="https://Github.com/lentitude"
android:textAllCaps="false"
android:background="@android:color/transparent"
android:textColor="@color/colorFont"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent">
<TextView
android:text="CSDN 主页 :"
android:textSize="14sp"
android:textColor="@color/colorFont"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/csdn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="http://blog.csdn.net/yiwei12"
android:textAllCaps="false"
android:background="@android:color/transparent"
android:textColor="@color/colorFont"
/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
最主要的,这里放置了我的 Github 和 CSDN 按钮,如果大家没事,可以点击按钮给颗星星
AboutApplicationActivity:
package com.example.coolweather;
import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
public class AboutApplicationActivity extends BaseActivity {
private Button github;
private Button csdn;
@Override
public void initView() {
setContentView(R.layout.activity_about_application);
Toolbar toolbar = (Toolbar)findViewById(R.id.tool_bar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
actionBar.setTitle("关于天气");
actionBar.setDisplayHomeAsUpEnabled(true);
}
github = (Button)findViewById(R.id.github);
csdn = (Button)findViewById(R.id.csdn);
}
@Override
public void initData() {
}
@Override
public void initListener() {
github.setOnClickListener(this);
csdn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
switch (v.getId()){
case R.id.github:
intent.setData(Uri.parse("http://Github.com/lentitude"));
break;
case R.id.csdn:
intent.setData(Uri.parse("http://blog.csdn.net/yiwei12"));
break;
}
startActivity(intent);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
break;
}
return true;
}
}
截止现在,功能基本都实现了,还差一个重要的功能,夜间模式,当然夜间模式有很多实现方式,我这里使用的是限定符的方式:在 res 目录下新建一个 values-night 文件夹,用来存放 夜间模式采用的自定义文件
在 values 中的 color 文件:
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#757575</color>
<color name="colorFont">#757575</color>
<color name="colorBackground">#f5f5f5</color>
<color name="colorWhite">#ffffff</color>
<color name="colorBlack">#000000</color>
<color name="colorRed">#E91E63</color>
</resources>
在 values-night 中的 color 文件:
<resources>
<color name="colorPrimary">#607D8B</color>
<color name="colorPrimaryDark">#607D8B</color>
<color name="colorAccent">#607D8B</color>
<color name="colorFont">#f5f5f5</color>
<color name="colorBackground">#455A64</color>
<color name="colorWhite">#607D8B</color>
<color name="colorBlack">#ffffff</color>
<color name="colorRed">#00BCD4</color>
</resources>
这样,在系统调用夜间模式时,会调用 values-night 中的 color 文件,否则会调用 values 中的 color 文件
重写 BaseApplication
public class BaseApplication extends Application {
private static Context context;
@Override
public void onCreate() {
context = getApplicationContext();
LitePal.initialize(context);
super.onCreate();
showTheme();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
TaskKiller.addActivity(activity);
// 用来对每一个 Activity 进行监听, 包括第三方 Activity
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
showTheme();
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
TaskKiller.dropActivity(activity);
}
});
}
public void showTheme(){
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean isNight = pref.getBoolean("isNight",false);
if (isNight){
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
public static Context getContext(){
return context;
}
}
因为重写了 BaseApplication ,所以需要将 AndroidManifest 中的 name属性改为 .BaseApplication,那 LitePal 不能获取到 Context 怎么办呢?我们可以在 Application 的 onCreate( ) 方法中调用 LitePal.initialize(context) 对LitePal 进行初始化,效果也是一样的。在showTheme( )方法中,根据 isNight 的值来判定应用使用的主题,所以在 onCreate( )中调用该方法作为默认主题,那改变主题之后怎么办?这里实现了registerActivityLifecycleCallbacks 接口,可以在其中监听所有 Activity 的生命周期,在每一个 Activity调用resume( )方法时,调用 showTheme( )方法来改变该 Activity( )的主题,那 MainActivity 已经开启了,怎么处理呢,我们在 MainActivity 的点击事件中:
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.setting:
// 跳转到设置界面
Intent intent1 = new Intent(this, SettingActivity.class);
intent1.putExtra("weather_title","设置");
startActivity(intent1);
break;
case R.id.night_model:
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = pref.edit();
boolean isNight = pref.getBoolean("isNight", false);
if (isNight){
// 如果已经是夜间模式
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
recreate();
editor.putBoolean("isNight", false);
editor.apply();
}else{
// 如果是日间模式
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
recreate();
editor.putBoolean("isNight", true);
editor.apply();
}
break;
}
return true;
}
根据 isNight 的值来判断要使用什么模式,同时对界面进行重绘,修改 isNight 的值,这样就解决了所有 Activity 的模式转化
最后
好吧,我也没有想到会有这么长,但是毕竟是写完了,如果大家喜欢,给个星吧
完整代码下载地址(Github)