可能很多人会问:之前已经写过一篇博文来介绍怎么做一款简单的新闻APP(http://blog.csdn.net/yiwei12/article/details/71249628),为什么还要专门一篇来介绍怎么做一款天气 APP,毕竟网络请求和数据处理都是大同小异的。如果真的要说差别的话,前一篇只是具备了一些基本的功能,来说明怎么请求和处理返回的数据,但还不足与在日常生活中使用?这一篇实践是来做一款日常可用的天气 APP - 彼时天气

—- 说明: 彼时天气仿照魅族 Flyme 天气设计
—- 在 coolWeather 的基础上进行处理


总体思路


Created with Raphaël 2.1.0开始有无缓存?直接加载缓存文件有无网络?通过百度SDK 获取位置OKHttp向接口请求对应位置的天气信息GSON 对返回的数据进行解析将返回的数据显示并写入缓存文件直接退出yesnoyesno

这就是总体的设计思路,至于后面其他的功能:选择地区,更新频率等功能可以之后再说


运行GIF


之所以大幅度提前展示 GIF 图,方便对后面布局部分有更好的理解


步骤


  1. 声明权限
  2. 依赖库
  3. 网络请求
  4. 网络解析
  5. 界面布局
  6. 最后
  7. 完整代码下载地址(github)

声明权限


因为我们需要用到百度SDK的定位服务,所以需要先下载百度中包含基础定位的 SDK(http://lbsyun.baidu.com/sdk/download),解压后,将其中的 .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);}
}

网路解析


本次的数据来源是和风天气(https://www.heweather.com/documents/api/v5/weather),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"><TextViewandroid: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.RecyclerViewandroid: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{@Overrideprotected 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,代码冗长,按照定义的基类方法讲:

@Overridepublic 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_nowdegreeText = (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_hourhourDegree = (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_aqiaqiText = (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_suggestioncomfortText = (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);// LBSmlocationClient = 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 将没有获得授权的权限进行添加,然后集中申请

/*** 权限申请处理*/@Overridepublic 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() {@Overridepublic void onRefresh() {if (getNetworkInfo() == null){Snackbar.make(swipeRefresh, "当前无网络,无法刷新 %>_<% ",Snackbar.LENGTH_LONG).setAction("去设置网络", new View.OnClickListener() {@Overridepublic 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() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic 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);}@Overridepublic 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{@Overridepublic void onReceiveLocation(BDLocation bdLocation) {currentPosition = bdLocation.getCity();requestWeather(currentPosition);showShort(currentPosition + " 定位成功");}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onFailure(Call call, IOException e) {e.printStackTrace();runOnUiThread(new Runnable() {@Overridepublic void run() {showShort("获取天气信息1失败");}});}@Overridepublic void onResponse(Call call, Response response) throws IOException {final String responseText = response.body().string();final Weather weather = Utility.handleWeatherResponse(responseText);runOnUiThread(new Runnable() {@Overridepublic 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);// 动态获取 资源idString 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/162017/5/16   ->   今天
2017/5/17   ->   明天
2017/5/18   ->   周四 5/18
2017/5/19   ->   周五 5/19

大概就是这样的作用,没什么好介绍的,不过这个类的作用是两天的间隔相差在一个月之内(毕竟….天气预报)

    @Overridepublic 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( ),并且传入对应的四个参数

    @Overrideprotected 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;@Overridepublic 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);}@Overridepublic void initListener() {}@Overridepublic 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);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {switch (item.getItemId()){case android.R.id.home:finish();}return true;}@Overridepublic 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 菜单项*/@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.menu, menu);return true;}/*** 菜单点击事件响应*/@Overridepublic 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.Toolbarandroid:id="@+id/toolbar_setting"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/colorWhite"app:titleTextColor="@color/colorFont"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_margin="15dp"android:orientation="vertical"android:background="@color/colorBackground"><android.support.v7.widget.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"android:layout_marginTop="10dp"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"android:layout_marginTop="10dp"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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;@Overridepublic 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);}@Overridepublic void initListener() {chooseArea.setOnClickListener(this);aboutApplication.setOnClickListener(this);autoUpdateTime.setOnClickListener(this);}@Overridepublic void initData() {}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {switch (item.getItemId()){case android.R.id.home:finish();break;default:break;}return true;}@Overridepublic 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;@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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();}@Overridepublic void initData() {LitePal.getDatabase();}@Overridepublic void initListener() {searchButton.setOnClickListener(this);}@Overridepublic 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() {@Overridepublic void onFailure(Call call, IOException e) {runOnUiThread(new Runnable() {@Overridepublic void run() {showShort("请求数据失败");}});}@Overridepublic void onResponse(Call call, Response response) throws IOException {String responseText = response.body().string();final City city = Utility.handleCityResponse(responseText);runOnUiThread(new Runnable() {@Overridepublic 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();}@Overridepublic 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.Toolbarandroid:id="@+id/tool_bar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/colorWhite"app:titleTextColor="@color/colorFont"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_margin="15dp"android:orientation="vertical"android:background="@color/colorBackground"><android.support.v7.widget.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"android:layout_marginTop="10dp"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"android:layout_marginTop="10dp"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"android:layout_marginTop="10dp"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"android:layout_marginTop="10dp"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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.CardViewandroid:layout_width="match_parent"android:layout_height="60dp"android:background="@color/colorWhite"android:layout_marginTop="10dp"app:cardCornerRadius="0dp"app:cardElevation="1dp"><Buttonandroid: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;@Overridepublic 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);}@Overridepublic void initData() {}@Overridepublic void initListener() {button0.setOnClickListener(this);button2.setOnClickListener(this);button1.setOnClickListener(this);button3.setOnClickListener(this);button5.setOnClickListener(this);button10.setOnClickListener(this);}@Overridepublic 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();}@Overridepublic 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() {}@Overridepublic IBinder onBind(Intent intent) {// TODO: Return the communication channel to the service.throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic 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() {@Overridepublic void onFailure(Call call, IOException e) {e.printStackTrace();}@Overridepublic 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;@Overridepublic 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);}@Overridepublic void initData() {}@Overridepublic void initListener() {github.setOnClickListener(this);csdn.setOnClickListener(this);}@Overridepublic 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);}@Overridepublic 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;@Overridepublic void onCreate() {context = getApplicationContext();LitePal.initialize(context);super.onCreate();showTheme();registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {@Overridepublic void onActivityCreated(Activity activity, Bundle savedInstanceState) {TaskKiller.addActivity(activity);// 用来对每一个 Activity 进行监听, 包括第三方 Activity}@Overridepublic void onActivityStarted(Activity activity) {}@Overridepublic void onActivityResumed(Activity activity) {showTheme();}@Overridepublic void onActivityPaused(Activity activity) {}@Overridepublic void onActivityStopped(Activity activity) {}@Overridepublic void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Overridepublic 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)


https://github.com/lentitude/BS-Weather

Android 实践:做一款可用的天气 APP相关推荐

  1. 推荐一款移动端天气App即刻天气

    推荐一款移动端天气App即刻天气 一 应用描述 即刻天气预报是一个提供全国各城市15日天气预报和空气质量的APP,包含全国3000个城市天气预报,3万个乡镇天气,15日及48小时空气质量预报,是万千用 ...

  2. 如何做一款有灵魂的App——关于hope的启发以及对于追梦App的思考

    文章目录 前言 一.一个有灵魂的App--hope 1.什么是有灵魂的App? 2.hope功能分析 ①代入感极强的引导页 ②富有趣味的交互方式 ③贴合主题的设计风格 ④无处不在的氛围渲染 ⑤hope ...

  3. 三星天气显示服务器不可用,三星手机天气APP出现诡异BUG!网友:这就是它爆炸的原因?...

    近日全国多位网友反映自己的三星手机天气报告出现了很严重的BUG,显示温度高达999度,这让不少三星用户很是震惊,甚至有的网友说,起床看到立马出门看了一下外面是不是真的. 从网友发布的截图来看,无论是手 ...

  4. 【Android】做一款类似我要当学霸里的学习监督的APP

    我要当学霸这款App有个学习监督的功能,当你启动它的时候,你将无法使用其他App,以此达到帮助人提高自觉性,起到监督学习的效果.最近和同学做了个小App,正好有这个功能,所以就来说说它是怎么实现的. ...

  5. Android一款简单的天气APP

    updated:10-28 这里放一个仿小米天气的半成品,新项目参考这里 updated:8-28 这个项目是我刚接触Android2个月写的,很不完善,不推荐大家再看了. 原文: 先上张图让大家看一 ...

  6. android 4k拍摄,五款专业安卓手机摄影App,立即提高手机生产力(全部免费)

    科技发展日新月异,智能手机的性能也跟着提高.这几年各大手机厂商都非常注重提升手机摄影功能,以致于现在的1200W,4800W等摄影像素都成了手机摄影标配.当然这也使我们不用带着贵重的单反相机,就可以完 ...

  7. 露眼看App--怎样做一款旅游类的App ?

    旅游App主要特点,也可以说是主要功能就是旅游攻略,游记这两个功能,有些App会有行程单功能. 那攻略,游记,行程单之间有什么样的区别呢?说一下我的看法,攻略一般指某个城市有什么好玩的景点啊,美餐,住 ...

  8. 用WebView秒做一款简易的浏览器app

    这里给大家分享一个用Webview这个组件快速制作一个简易的浏览器app,首页设置为百度,可根据自己喜好来定义首页页面显示. 大家可以看到下面加了3个按钮,分别对应是后退,返回主页面,前进 3个功能 ...

  9. android天气时钟设计报告,做一款自己的安卓天气闹钟(1)——首页界面布局

    写在前面 这一次是要结合前面做的一堆爬虫,语音之类的做一个终端的东西,是一款可以显示天气的闹钟APP,自己也是一边做一边学,如果有什么不对的地方欢迎大家指正 先看设计界面 首页界面 中间时间贼丑的字体 ...

最新文章

  1. DeepMind成功使用深度强化学习技术完美控制核聚变反应堆!
  2. EEPROM存储器--AT24CXX
  3. MyBatis-25MyBatis缓存配置【集成Redis】
  4. 嵌入式系统系统升级内核双备份的实现方式
  5. Qt中的角度和正方向描述清单
  6. 4款最具影响力的自助式BI工具
  7. 2017 Multi-University Training Contest - Team 4:1003. Counting Divisors(积性函数)
  8. 图像处理_如何保存浮点型数值的图像? (C++ / OpenCV)
  9. web操作日志丢失_日志异步落库,你了解不
  10. 编写一个应用程序,给出汉字“你”“我”“他”在Unicode表中的位置。
  11. Aqua data studio 19 汉化方法
  12. 网站上传服务器及安装包,如何上传安装包到服务器
  13. Linux(Ubuntu)触摸屏校准
  14. css中设置字体下划线,css如何设置字体下划线
  15. SQL 嵌套 N 层太长太难写怎么办?
  16. git 手动master_git 如何撤销一次remote的master commit?
  17. win10如何设置有线和无线同时上内网和外网?
  18. Python线程详解
  19. 谁在痛打“诺顿”落水狗
  20. ENVI遥感影像解译制作土地利用专题图

热门文章

  1. 用excle插入自定义两列(行)数据作为X、Y轴生成图表
  2. hiredis php,redis 连接池 hiredis
  3. 离散余弦变换在图像压缩中的应用
  4. 草根方式学习java中的多线程
  5. 西施果蔬彩色豆腐与传统豆腐对比调查报告
  6. SQL注入-常用函数和语句
  7. 计算机考研考什么科目?求解
  8. Python的线程17 Condition类,田径赛场上的主裁判
  9. 基于PID的直流电机调速控制系统
  10. 计算机模拟求解流体力学方程,基于CFD方法对圆盘空化器超空泡流动的数值模拟_计算流体力学-论文网...