Android 实践:做一款可用的天气 APP

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

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


总体思路


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

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


运行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">

    <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=
  • 18
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 21
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值