当前位置:   article > 正文

Android 天气APP(十一)未来七天的天气预报、逐小时预报、UI优化_android天气软件 24小时布局实现

android天气软件 24小时布局实现

上一篇:Android 天气APP(十)下拉刷新页面天气数据

新版-------------------

  在上篇文章中,更新了每日壁纸的BUG,同时增加了下拉刷新和滑动改变标题,本篇文章中将增加逐小时天气预报以及UI的优化。

一、UI优化

  本篇文章中我们先改动一下UI,实际上就是对于activity_main.xml的控件改动,首先改动activity_main.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/lay_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/main_bg"
    android:fitsSystemWindows="true"
    tools:context=".ui.MainActivity">
    <!--顶部标题-->
    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:ellipsize="middle"
            android:singleLine="true"
            android:text="城市天气"
            android:textColor="@color/white"
            android:textSize="@dimen/sp_16" />
    </com.google.android.material.appbar.MaterialToolbar>

    <!--下拉刷新视图-->
    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/lay_refresh"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_0"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar">
        <!--滚动视图-->
        <androidx.core.widget.NestedScrollView
            android:id="@+id/lay_scroll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <!--页面主要内容视图-->
            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="@dimen/dp_0">
                <!--滑动距离布局-->
                <androidx.constraintlayout.widget.ConstraintLayout
                    android:id="@+id/lay_scroll_height"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent">
                    <!--天气状况-->
                    <TextView
                        android:id="@+id/tv_info"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginStart="@dimen/dp_16"
                        android:layout_marginTop="@dimen/dp_8"
                        android:text="天气状况"
                        android:textColor="@color/white"
                        android:textSize="@dimen/sp_18"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />
                    <!--温度-->
                    <TextView
                        android:id="@+id/tv_temp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="@dimen/dp_24"
                        android:text="0"
                        android:textColor="@color/white"
                        android:textSize="@dimen/sp_60"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toBottomOf="@+id/tv_info" />
                    <!--摄氏度符号-->
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text=""
                        android:textColor="@color/white"
                        android:textSize="@dimen/sp_24"
                        app:layout_constraintStart_toEndOf="@+id/tv_temp"
                        app:layout_constraintTop_toTopOf="@+id/tv_temp" />
                    <!--当天最高温和最低温-->
                    <LinearLayout
                        android:id="@+id/lay_temp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="16dp"
                        app:layout_constraintEnd_toEndOf="@+id/tv_temp"
                        app:layout_constraintStart_toStartOf="@+id/tv_temp"
                        app:layout_constraintTop_toBottomOf="@+id/tv_temp">
                        <!--最高温-->
                        <TextView
                            android:id="@+id/tv_height"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:textColor="@color/white"
                            android:textSize="@dimen/sp_14" />
                        <!--最低温-->
                        <TextView
                            android:id="@+id/tv_low"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:textColor="@color/temp_min_tx"
                            android:textSize="@dimen/sp_14" />
                    </LinearLayout>
                    <!--城市-->
                    <TextView
                        android:id="@+id/tv_city"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="16dp"
                        android:text="城市"
                        android:textColor="@color/white"
                        android:textSize="@dimen/sp_20"
                        app:layout_constraintEnd_toEndOf="@+id/tv_temp"
                        app:layout_constraintStart_toStartOf="@+id/tv_temp"
                        app:layout_constraintTop_toBottomOf="@+id/lay_temp" />
                </androidx.constraintlayout.widget.ConstraintLayout>
                <!--App名称-->
                <TextView
                    android:id="@+id/tv_app_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="@dimen/dp_16"
                    android:layout_marginTop="@dimen/dp_16"
                    android:drawableStart="@mipmap/icon_weather_sun"
                    android:drawablePadding="@dimen/dp_4"
                    android:text="Good Weather New"
                    android:textColor="@color/white"
                    android:textSize="@dimen/sp_12"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/lay_scroll_height" />
                <!--上一次更新时间-->
                <TextView
                    android:id="@+id/tv_update_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="@dimen/dp_16"
                    android:text="最近更新时间:"
                    android:textColor="@color/white"
                    android:textSize="@dimen/sp_12"
                    app:layout_constraintBottom_toBottomOf="@+id/tv_app_name"
                    app:layout_constraintEnd_toEndOf="@+id/lay_scroll_height"
                    app:layout_constraintTop_toTopOf="@+id/tv_app_name" />
                <!--分隔线 增加UI效果-->
                <View
                    android:id="@+id/view"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/dp_1"
                    android:layout_marginStart="@dimen/dp_16"
                    android:layout_marginTop="@dimen/dp_8"
                    android:layout_marginEnd="@dimen/dp_16"
                    android:alpha="0.1"
                    android:background="@color/white"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/tv_app_name" />
                <!--逐小时天气预报列表-->
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/rv_hourly"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/dp_16"
                    android:paddingStart="@dimen/dp_16"
                    android:paddingEnd="@dimen/dp_16"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/view" />
                <!--天气预报列表-->
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/rv_daily"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:paddingStart="@dimen/dp_16"
                    android:paddingEnd="@dimen/dp_16"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/rv_hourly" />
                <!--风向风力-->
                <TextView
                    android:id="@+id/textView2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="@dimen/dp_16"
                    android:layout_marginTop="8dp"
                    android:text="风向风力"
                    android:textColor="@color/white"
                    android:textSize="@dimen/sp_18"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/rv_daily" />
                <!--大风车-->
                <com.llw.goodweather.ui.view.WhiteWindmills
                    android:id="@+id/ww_big"
                    android:layout_width="100dp"
                    android:layout_height="120dp"
                    android:layout_marginTop="8dp"
                    app:layout_constraintEnd_toStartOf="@+id/guideline2"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/textView2"
                    tools:ignore="MissingConstraints" />
                <!--小风车-->
                <com.llw.goodweather.ui.view.WhiteWindmills
                    android:id="@+id/ww_small"
                    android:layout_width="50dp"
                    android:layout_height="60dp"
                    android:layout_marginStart="32dp"
                    app:layout_constraintBottom_toBottomOf="@+id/ww_big"
                    app:layout_constraintEnd_toStartOf="@+id/guideline2"
                    app:layout_constraintStart_toStartOf="@+id/ww_big"
                    tools:ignore="MissingConstraints" />
                <!--纵向辅助线-->
                <androidx.constraintlayout.widget.Guideline
                    android:id="@+id/guideline2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    app:layout_constraintGuide_begin="205dp" />
                <!--风向风力文字-->
                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    app:layout_constraintBottom_toBottomOf="@+id/ww_big"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="@+id/guideline2"
                    app:layout_constraintTop_toTopOf="@+id/ww_big">
                    <!--风向-->
                    <TextView
                        android:id="@+id/tv_wind_direction"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textColor="@color/white"
                        android:textSize="@dimen/sp_14" />
                    <!--风力-->
                    <TextView
                        android:id="@+id/tv_wind_power"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="@dimen/dp_24"
                        android:textColor="@color/white"
                        android:textSize="@dimen/sp_14" />
                </LinearLayout>
                <!--生活建议-->
                <TextView
                    android:id="@+id/textView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="@dimen/dp_16"
                    android:layout_marginTop="16dp"
                    android:text="生活建议"
                    android:textColor="@color/white"
                    android:textSize="@dimen/sp_18"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/ww_big" />
                <!--生活建议列表-->
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/rv_lifestyle"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:paddingStart="@dimen/dp_16"
                    android:paddingEnd="@dimen/dp_16"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/textView" />

            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.core.widget.NestedScrollView>
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279

  这里的布局就是整篇文章中改动后的布局,这里我改动了UI,并且增加了一个逐小时天气数据列表,首先是显示当天的最高温和最低温,在MainActivity中,天气预报返回中增加如下代码:

binding.tvHeight.setText(String.format("%s℃", daily.get(0).getTempMax()));
binding.tvLow.setText(String.format(" / %s℃", daily.get(0).getTempMin()));
  • 1
  • 2

添加位置如下图所示:

在这里插入图片描述

  同时修改最近更新时间的位置和时间显示格式,位置我通过布局进行了改动,时间显示通过代码来改动,在实况天气的返回中添加如下代码:

String time = EasyDate.updateTime(nowResponse.getUpdateTime());
binding.tvUpdateTime.setText(String.format("最近更新时间:%s%s", EasyDate.showTimeInfo(time), time));
  • 1
  • 2

添加位置如下图所示:

在这里插入图片描述

现在UI基本上就改动完了,下面开始增加逐小时天气预报。

二、逐小时天气预报

作为免费用户,可以获取24小时的逐小时天气预报,下面我们来使用它。

① 添加逐小时天气API

首先通过返回的示例数据手写一个实体类,在bean包下新建一个HourlyResponse类,代码如下所示:

public class HourlyResponse {

    private String code;
    private String updateTime;
    private String fxLink;
    private ReferBean refer;
    private List<HourlyBean> hourly;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(String updateTime) {
        this.updateTime = updateTime;
    }

    public String getFxLink() {
        return fxLink;
    }

    public void setFxLink(String fxLink) {
        this.fxLink = fxLink;
    }

    public ReferBean getRefer() {
        return refer;
    }

    public void setRefer(ReferBean refer) {
        this.refer = refer;
    }

    public List<HourlyBean> getHourly() {
        return hourly;
    }

    public void setHourly(List<HourlyBean> hourly) {
        this.hourly = hourly;
    }

    public static class ReferBean {
        private List<String> sources;
        private List<String> license;

        public List<String> getSources() {
            return sources;
        }

        public void setSources(List<String> sources) {
            this.sources = sources;
        }

        public List<String> getLicense() {
            return license;
        }

        public void setLicense(List<String> license) {
            this.license = license;
        }
    }

    public static class HourlyBean {

        private String fxTime;
        private String temp;
        private String icon;
        private String text;
        private String wind360;
        private String windDir;
        private String windScale;
        private String windSpeed;
        private String humidity;
        private String pop;
        private String precip;
        private String pressure;
        private String cloud;
        private String dew;

        public String getFxTime() {
            return fxTime;
        }

        public void setFxTime(String fxTime) {
            this.fxTime = fxTime;
        }

        public String getTemp() {
            return temp;
        }

        public void setTemp(String temp) {
            this.temp = temp;
        }

        public String getIcon() {
            return icon;
        }

        public void setIcon(String icon) {
            this.icon = icon;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public String getWind360() {
            return wind360;
        }

        public void setWind360(String wind360) {
            this.wind360 = wind360;
        }

        public String getWindDir() {
            return windDir;
        }

        public void setWindDir(String windDir) {
            this.windDir = windDir;
        }

        public String getWindScale() {
            return windScale;
        }

        public void setWindScale(String windScale) {
            this.windScale = windScale;
        }

        public String getWindSpeed() {
            return windSpeed;
        }

        public void setWindSpeed(String windSpeed) {
            this.windSpeed = windSpeed;
        }

        public String getHumidity() {
            return humidity;
        }

        public void setHumidity(String humidity) {
            this.humidity = humidity;
        }

        public String getPop() {
            return pop;
        }

        public void setPop(String pop) {
            this.pop = pop;
        }

        public String getPrecip() {
            return precip;
        }

        public void setPrecip(String precip) {
            this.precip = precip;
        }

        public String getPressure() {
            return pressure;
        }

        public void setPressure(String pressure) {
            this.pressure = pressure;
        }

        public String getCloud() {
            return cloud;
        }

        public void setCloud(String cloud) {
            this.cloud = cloud;
        }

        public String getDew() {
            return dew;
        }

        public void setDew(String dew) {
            this.dew = dew;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199

在ApiService接口中,增加如下代码:

    @GET("/v7/weather/24h?key=" + API_KEY)
    Observable<HourlyResponse> hourlyWeather(@Query("location") String location);
  • 1
  • 2

② 使用逐小时天气API

然后是使用的地方,在WeatherRepository中增加如下方法代码:

    public void hourlyWeather(MutableLiveData<HourlyResponse> responseLiveData,
                          MutableLiveData<String> failed, String cityId) {
        String type = "逐小时天气预报-->";
        NetworkApi.createService(ApiService.class, ApiType.WEATHER).hourlyWeather(cityId)
                .compose(NetworkApi.applySchedulers(new BaseObserver<>() {
                    @Override
                    public void onSuccess(HourlyResponse hourlyResponse) {
                        if (hourlyResponse == null) {
                            failed.postValue("逐小时天气预报数据为null,请检查城市ID是否正确。");
                            return;
                        }
                        //请求接口成功返回数据,失败返回状态码
                        if (Constant.SUCCESS.equals(hourlyResponse.getCode())) {
                            responseLiveData.postValue(hourlyResponse);
                        } else {
                            failed.postValue(type + hourlyResponse.getCode());
                        }
                    }

                    @Override
                    public void onFailure(Throwable e) {
                        Log.e(TAG, "onFailure: " + e.getMessage());
                        failed.postValue(type + e.getMessage());
                    }
                }));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在MainViewModel中使用,增加如下代码:

	public MutableLiveData<HourlyResponse> hourlyResponseMutableLiveData = new MutableLiveData<>();
   
    public void hourlyWeather(String cityId) {
        WeatherRepository.getInstance().hourlyWeather(hourlyResponseMutableLiveData, failed, cityId);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

现在只差调用了,不过因为是列表,所以还需要适配器。

③ 增加逐小时天气适配器

在layout下新增一个item_hourly_rv.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_hourly"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="@dimen/dp_8">
    <!--时间-->
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="上午10:00"
        android:textColor="@color/white"
        android:textSize="@dimen/sp_14" />
    <!--气候图标-->
    <ImageView
        android:id="@+id/iv_status"
        android:layout_width="@dimen/dp_32"
        android:layout_height="@dimen/dp_32"
        android:layout_marginTop="@dimen/dp_12"
        android:layout_marginBottom="@dimen/dp_8"
        android:background="@mipmap/icon_100" />
    <!--温度-->
    <TextView
        android:id="@+id/tv_temperature"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="25℃"
        android:textColor="@color/white"
        android:textSize="@dimen/sp_20" />
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

然后在adapter包下新建一个HourlyAdapter ,代码如下所示:

public class HourlyAdapter extends RecyclerView.Adapter<HourlyAdapter.ViewHolder> {

    private final List<HourlyResponse.HourlyBean> hourlyBeans;

    public HourlyAdapter(List<HourlyResponse.HourlyBean> dailyBeans) {
        this.hourlyBeans = dailyBeans;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemHourlyRvBinding binding = ItemHourlyRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        HourlyResponse.HourlyBean hourlyBean = hourlyBeans.get(position);
        String time = EasyDate.updateTime(hourlyBean.getFxTime());
        holder.binding.tvTime.setText(String.format("%s%s", EasyDate.showTimeInfo(time), time));
        WeatherUtil.changeIcon(holder.binding.ivStatus, Integer.parseInt(hourlyBean.getIcon()));
        holder.binding.tvTemperature.setText(String.format("%s℃", hourlyBean.getTemp()));
    }

    @Override
    public int getItemCount() {
        return hourlyBeans.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemHourlyRvBinding binding;

        public ViewHolder(@NonNull ItemHourlyRvBinding itemHourlyRvBinding) {
            super(itemHourlyRvBinding.getRoot());
            binding = itemHourlyRvBinding;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

这里面的代码很简单,就是数据显示而已,下面在MainActivity中使用。

④ 逐小时天气预报数据显示

在MainActivity中声明变量:

    private final List<HourlyResponse.HourlyBean> hourlyBeanList = new ArrayList<>();
    private final HourlyAdapter hourlyAdapter = new HourlyAdapter(hourlyBeanList);
  • 1
  • 2

然后在initView()方法中增加如下代码,对逐小时天气预报列表进行设置,代码如下:

	LinearLayoutManager hourlyLayoutManager = new LinearLayoutManager(this);
	hourlyLayoutManager.setOrientation(RecyclerView.HORIZONTAL);
    binding.rvHourly.setLayoutManager(hourlyLayoutManager);
    binding.rvHourly.setAdapter(hourlyAdapter);
  • 1
  • 2
  • 3
  • 4

添加位置如下图所示:

在这里插入图片描述

这里设置了横向滑动,下面就是逐小时天气请求数据的返回了,在onObserveData()方法中,添加如下代码:

            viewModel.hourlyResponseMutableLiveData.observe(this, hourlyResponse -> {
                List<HourlyResponse.HourlyBean> hourly = hourlyResponse.getHourly();
                if (hourly != null) {
                    if (hourlyBeanList.size() > 0) {
                        hourlyBeanList.clear();
                    }
                    hourlyBeanList.addAll(hourly);
                    hourlyAdapter.notifyDataSetChanged();
                }
            });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

添加位置如下图所示:

在这里插入图片描述

然后也不要忘记在搜索城市的回调中调用逐小时天气请求方法,如下图所示:

在这里插入图片描述

最后再改动一个地方,修改WeatherUtil中的changeIcon()方法,如下图所示:

在这里插入图片描述

因为有151这个天气状态编码,之前没有设置,所以就会显示N/A的图,这里补充一下,现在可以运行了。

在这里插入图片描述

这里看到的UI就是有了调整的。

三、文章源码

欢迎 StarFork

第十一篇文章源码地址:GoodWeather-New-11

旧版-------------------

前言

如果你看到这里那么你应该看过前面十篇文章了,这是第十一篇,其实写作的原意,并不是我想分这么多章节的,但是不得不分章节,我不能只考虑自己不考虑阅读的人,试问,我这里有一篇20万字的博客,你要不要看一下呢?你可能会望而却步吧,从而失去兴趣,故分章节,但请放心,我不是标题党,也不做无意义的分章节,标题肯定是要对应里面的内容的,现在有些博主写文章花里胡哨的,就靠标题吸引人,里面的内容都在胡扯,没有一点意义,题不对意,别人提问也不回复,这样就是不负责任,对此,我表示强烈的谴责和抗议。

正文

在这里插入图片描述
还记得之前注册这个和风天气的API吗?之前是用普通用户(作为白嫖党,如果不多搞一些,就亏了,所以我们要从普通用户升级到开发者,当然还是不要钱的,只不过拿到的数据会多一点,我们要的七天的天气预报就在开发者的版本里面,而且开发者的API访问是比普通用户要多的。当然你们要是有钱可以使用商业版,商业版的数据足够你开发一个商业级别的APP了,只不过商业版很贵就是了,我一个穷人用不起,当然我们也可以合作,你出钱,我出力,美滋滋),现在升级到开发者,点击和风天气登录,进入控制台

在这里插入图片描述

点击升级,会让你绑定手机号,输入验证码后下一步就是升级为开发者
在这里插入图片描述

在这里插入图片描述

作为开发者你就要对自己的APP负责了,所以就需要你的身份证号码和正反面照片了,还是应用的名称及应用的类型,相信你们也知道该怎么填,填完之后点击同意并变更

然后就会进入审核阶段,
在这里插入图片描述
审核的结果会发到你注册时的邮箱里面。慢慢等吧(建议你可以先把文章收藏,等你的审核通过之后继续看,有些文章,错过就不在了)。
反正你现在闲着也是闲着,来改UI吧,天气预报列表中的天气状态之前是用文字来描述的,现在改成用图标来描述,那么这个图标哪里来呢?羊毛出在羊身上,薅羊毛就要彻底一些,当然是去和风天气官网去拿图标了。
在这里插入图片描述
下载之后解压,你会看到很多图标,但是这些图标的颜色是蓝色的,而我希望是白色,这样才符合我这个APP的UI整体效果,所以还是要自己改一下图标的颜色,这么多图标,这也是个体力活啊,慢慢改吧。
在这里插入图片描述

打开你的PhotoShop,然后随便拖一个图标进去,比如这个图标。
在这里插入图片描述
颜色叠加
在这里插入图片描述

将默认的红色改成白色
在这里插入图片描述
然后点击确定,再点一次,回到PS主页面,可以看到就变成白色了
在这里插入图片描述
接下来保存图标,点击左上角的文件->存储为
在这里插入图片描述
格式保存为png格式,然后就是覆盖它原来的蓝色图标,然后一路保存,最后看到你的文件夹中的图标就变成白色的了。
在这里插入图片描述
好了,还有那么多图标呢,你慢慢改,不着急。磨刀不误砍柴工啊。
图标名中带“ n ”的是表示晚上,不带的就是白天的,你也可以改成白色的,并且在APP上增加现在是白天还是晚上的状态判断,也算是进一步优化,这个目前先不做。
Android不允许纯数字命名的图片,你可以把100.png改成icon_100.png。
所有图片都改好之后复制到你项目的mipmap-xhdpi文件夹下。
然后对照这个天气代码表来做判断显示
天气代码对照表

代码中文英文图标
100Sunny/Clear100.png
101多云Cloudy101.png
102少云Few Clouds102.png
103晴间多云Partly Cloudy103.png
104Overcast104.png
200有风Windy200.png
201平静Calm201.png
202微风Light Breeze202.png
203和风Moderate/Gentle Breeze203.png
204清风Fresh Breeze204.png
205强风/劲风Strong Breeze205.png
206疾风High Wind, Near Gale206.png
207大风Gale207.png
208烈风Strong Gale208.png
209风暴Storm209.png
210狂爆风Violent Storm210.png
211飓风Hurricane211.png
212龙卷风Tornado212.png
213热带风暴Tropical Storm213.png
300阵雨Shower Rain300.png
301强阵雨Heavy Shower Rain301.png
302雷阵雨Thundershower302.png
303强雷阵雨Heavy Thunderstorm303.png
304雷阵雨伴有冰雹Thundershower with hail304.png
305小雨Light Rain305.png
306中雨Moderate Rain306.png
307大雨Heavy Rain307.png
308极端降雨Extreme Rain308.png
309毛毛雨/细雨Drizzle Rain309.png
310暴雨Storm310.png
311大暴雨Heavy Storm311.png
312特大暴雨Severe Storm312.png
313冻雨Freezing Rain313.png
314小到中雨Light to moderate rain314.png
315中到大雨Moderate to heavy rain315.png
316大到暴雨Heavy rain to storm316.png
317暴雨到大暴雨Storm to heavy storm317.png
318大暴雨到特大暴雨Heavy to severe storm318.png
399Rain399.png
400小雪Light Snow400.png
401中雪Moderate Snow401.png
402大雪Heavy Snow402.png
403暴雪Snowstorm403.png
404雨夹雪Sleet404.png
405雨雪天气Rain And Snow405.png
406阵雨夹雪Shower Snow406.png
407阵雪Snow Flurry407.png
408小到中雪Light to moderate snow408.png
409中到大雪Moderate to heavy snow409.png
410大到暴雪Heavy snow to snowstorm410.png
499Snow499.png
500薄雾Mist500.png
501Foggy501.png
502Haze502.png
503扬沙Sand503.png
504浮尘Dust504.png
507沙尘暴Duststorm507.png
508强沙尘暴Sandstorm508.png
509浓雾Dense fog509.png
510强浓雾Strong fog510.png
511中度霾Moderate haze511.png
512重度霾Heavy haze512.png
513严重霾Severe haze513.png
514大雾Heavy fog514.png
515特强浓雾Extra heavy fog515.png
900Hot900.png
901Cold901.png
999未知Unknown999.png

七天的天气预报和UI优化

在我修改图标颜色的过程中,发现有好几个天气代码的图标是一模一样的,所以代码中判断显示的时候会有几个状态码对应的图标一样,提前说明,不要见怪。
接下里就是修改天气预报列表的item的布局文件了。
item_weather_forecast_list.xml

<?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:gravity="center_vertical"
        android:padding="@dimen/sp_12"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <!--日期-->
        <TextView
            android:id="@+id/tv_date"
            android:text="1234"
            android:textSize="@dimen/sp_14"
            android:textColor="#FFF"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"/>
        <!--天气描述  文字 隐藏-->
        <TextView
            android:visibility="gone"
            android:gravity="center"
            android:id="@+id/tv_info"
            android:textSize="@dimen/sp_14"
            android:textColor="#FFF"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"/>
        <!--天气描述  图标-->
        <ImageView
            android:id="@+id/iv_weather_state"
            android:background="@mipmap/icon_100"
            android:layout_width="30dp"
            android:layout_height="30dp"/>

        <!--最低温、最高温-->
        <TextView
            android:gravity="right"
            android:id="@+id/tv_low_and_height"
            android:textSize="@dimen/sp_14"
            android:textColor="#FFF"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

改动并不大,加了一个ImageView而已,然后在创建工具类
在utils包下创建一个WeatherUtil类
在这里插入图片描述
代码如下:

package com.llw.goodweather.utils;

import android.widget.ImageView;

import com.llw.goodweather.R;

/**
 * 天气工具类
 */
public class WeatherUtil {

    /**
     * 根据传入的状态码修改填入的天气图标
     * @param weatherStateIcon 显示的ImageView
     * @param code 天气状态码
     */
    public static void changeIcon(ImageView weatherStateIcon,int code){
        switch (code){
            case 100://晴
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_100);
                break;
            case 101://多云
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_101);
                break;
            case 102://少云
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_102);
                break;
            case 103://晴间多云
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_103);
                break;
            case 200://有风
            case 202://微风
            case 203://和风
            case 204://清风
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_200);//因为这几个状态的图标是一样的
                break;
            case 201://平静
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_201);
                break;
            case 205://强风/劲风
            case 206://疾风
            case 207://大风
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_205);//因为这几个状态的图标是一样的
                break;
            case 208://烈风
            case 209://风暴
            case 210://狂爆风
            case 211://飓风
            case 212://龙卷风
            case 213://热带风暴
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_208);//因为这几个状态的图标是一样的
                break;
            case 300://阵雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_300);
                break;
            case 301://强阵雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_301);
                break;
            case 302://雷阵雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_302);
                break;
            case 303://强雷阵雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_303);
                break;
            case 304://雷阵雨伴有冰雹
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_304);
                break;
            case 305://小雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_305);
                break;
            case 306://中雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_306);
                break;
            case 307://大雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_307);
                break;
            case 308://极端降雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_312);
                break;
            case 309://毛毛雨/细雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_309);
                break;
            case 310://暴雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_310);
                break;
            case 311://大暴雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_311);
                break;
            case 312://特大暴雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_312);
                break;
            case 313://冻雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_313);
                break;
            case 314://小到中雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_306);
                break;
            case 315://中到大雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_307);
                break;
            case 316://大到暴雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_310);
                break;
            case 317://大暴雨到特大暴雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_312);
                break;
            case 399://雨
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_399);
                break;
            case 400://小雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_400);
                break;
            case 401://中雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_401);
                break;
            case 402://大雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_402);
                break;
            case 403://暴雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_403);
                break;
            case 404://雨夹雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_404);
                break;
            case 405://雨雪天气
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_405);
                break;
            case 406://阵雨夹雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_406);
                break;
            case 407://阵雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_407);
                break;
            case 408://小到中雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_408);
                break;
            case 409://中到大雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_409);
                break;
            case 410://大到暴雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_410);
                break;
            case 499://雪
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_499);
                break;
            case 500://薄雾
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_500);
                break;
            case 501://雾
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_501);
                break;
            case 502://霾
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_502);
                break;
            case 503://扬沙
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_503);
                break;
            case 504://扬沙
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_504);
                break;
            case 507://沙尘暴
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_507);
                break;
            case 508://强沙尘暴
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_508);
                break;
            case 509://浓雾
            case 510://强浓雾
            case 514://大雾
            case 515://特强浓雾
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_509);
                break;
            case 511://中度霾
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_511);
                break;
            case 512://重度霾
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_512);
                break;
            case 513://严重霾
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_513);
                break;
            case 900://热
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_900);
                break;
            case 901://冷
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_901);
                break;
            case 999://未知
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_999);
                break;
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194

WeatherForecastAdapter.java中做判断显示图标,代码如下:

package com.llw.goodweather.adapter;

import android.widget.ImageView;

import androidx.annotation.Nullable;

import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.goodweather.R;
import com.llw.goodweather.bean.WeatherForecastResponse;
import com.llw.goodweather.utils.WeatherUtil;

import java.util.List;

/**
 * 天气预报列表展示适配器
 */
public class WeatherForecastAdapter extends BaseQuickAdapter<WeatherForecastResponse.HeWeather6Bean.DailyForecastBean, BaseViewHolder> {

    public WeatherForecastAdapter(int layoutResId, @Nullable List<WeatherForecastResponse.HeWeather6Bean.DailyForecastBean> data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, WeatherForecastResponse.HeWeather6Bean.DailyForecastBean item) {
        helper.setText(R.id.tv_date, item.getDate())//日期
//                .setText(R.id.tv_info, item.getCond_txt_d())//天气
                .setText(R.id.tv_low_and_height, item.getTmp_min() + "/" + item.getTmp_max() + "℃");//最低温和最高温

        //天气状态图片
        ImageView weatherStateIcon = helper.getView(R.id.iv_weather_state);
        int code = Integer.parseInt(item.getCond_code_d());//获取天气状态码,根据状态码来显示图标
        WeatherUtil.changeIcon(weatherStateIcon,code);//调用工具类中写好的方法
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

在这里插入图片描述
因为写作过程中,还有其他事情,所以并不是一蹴而就的,当我写适配器代码的时候就我的邮箱就已经收到通过审核的短信了,你如果没有收到也不要着急,只要资料没有问题,审核还是蛮快的,关键看审核的人员那个时候有没有帮你审核。现在我成为一个认证开发者了,那么可以直接运行一下。
在这里插入图片描述
再切换一个城市
在这里插入图片描述
从这个运行的效果图来看,完成了两件事,第一个就是未来七天的天气预报,这个只要你通过了审核,成为开发者,返回的数据自然就变成7天的,第二个就是UI优化,感觉图标显示还是比文字显示更好一些,这个就属于细节优化问题了,因为这个细节,你还是要做很多准备工作的。

逐小时预报和UI优化

https://free-api.heweather.net/s6/weather/hourly?location=%E7%A6%8F%E7%94%B0&key=3086e91d66c04ce588a7f538f917c7f4
  • 1

在这里插入图片描述

访问测试地址,请以自己的Key去访问,返回的结果

在这里插入图片描述

在bean包下创建一个HourlyResponse.java,里面的代码如下:

package com.llw.goodweather.bean;


import java.util.List;

public class HourlyResponse {

    private List<HeWeather6Bean> HeWeather6;

    public List<HeWeather6Bean> getHeWeather6() {
        return HeWeather6;
    }

    public void setHeWeather6(List<HeWeather6Bean> HeWeather6) {
        this.HeWeather6 = HeWeather6;
    }

    public static class HeWeather6Bean {
        /**
         * basic : {"cid":"CN101280603","location":"福田","parent_city":"深圳","admin_area":"广东","cnty":"中国","lat":"22.5410099","lon":"114.05095673","tz":"+8.00"}
         * update : {"loc":"2020-04-28 19:56","utc":"2020-04-28 11:56"}
         * status : ok
         * hourly : [{"cloud":"43","cond_code":"101","cond_txt":"多云","dew":"19","hum":"71","pop":"0","pres":"1010","time":"2020-04-28 22:00","tmp":"23","wind_deg":"82","wind_dir":"东风","wind_sc":"1-2","wind_spd":"2"},{"cloud":"35","cond_code":"100","cond_txt":"晴","dew":"18","hum":"74","pop":"0","pres":"1011","time":"2020-04-29 01:00","tmp":"22","wind_deg":"14","wind_dir":"东北风","wind_sc":"1-2","wind_spd":"11"},{"cloud":"69","cond_code":"100","cond_txt":"晴","dew":"18","hum":"72","pop":"0","pres":"1011","time":"2020-04-29 04:00","tmp":"21","wind_deg":"43","wind_dir":"东北风","wind_sc":"1-2","wind_spd":"4"},{"cloud":"90","cond_code":"101","cond_txt":"多云","dew":"19","hum":"62","pop":"0","pres":"1008","time":"2020-04-29 07:00","tmp":"21","wind_deg":"53","wind_dir":"东北风","wind_sc":"1-2","wind_spd":"9"},{"cloud":"91","cond_code":"101","cond_txt":"多云","dew":"19","hum":"58","pop":"0","pres":"1008","time":"2020-04-29 10:00","tmp":"27","wind_deg":"45","wind_dir":"东北风","wind_sc":"1-2","wind_spd":"10"},{"cloud":"75","cond_code":"101","cond_txt":"多云","dew":"18","hum":"59","pop":"0","pres":"1009","time":"2020-04-29 13:00","tmp":"29","wind_deg":"32","wind_dir":"东北风","wind_sc":"1-2","wind_spd":"4"},{"cloud":"59","cond_code":"100","cond_txt":"晴","dew":"18","hum":"60","pop":"0","pres":"1009","time":"2020-04-29 16:00","tmp":"27","wind_deg":"50","wind_dir":"东北风","wind_sc":"1-2","wind_spd":"6"},{"cloud":"37","cond_code":"101","cond_txt":"多云","dew":"19","hum":"61","pop":"0","pres":"1008","time":"2020-04-29 19:00","tmp":"26","wind_deg":"-1","wind_dir":"无持续风向","wind_sc":"1-2","wind_spd":"2"}]
         */

        private BasicBean basic;
        private UpdateBean update;
        private String status;
        private List<HourlyBean> hourly;

        public BasicBean getBasic() {
            return basic;
        }

        public void setBasic(BasicBean basic) {
            this.basic = basic;
        }

        public UpdateBean getUpdate() {
            return update;
        }

        public void setUpdate(UpdateBean update) {
            this.update = update;
        }

        public String getStatus() {
            return status;
        }

        public void setStatus(String status) {
            this.status = status;
        }

        public List<HourlyBean> getHourly() {
            return hourly;
        }

        public void setHourly(List<HourlyBean> hourly) {
            this.hourly = hourly;
        }

        public static class BasicBean {
            /**
             * cid : CN101280603
             * location : 福田
             * parent_city : 深圳
             * admin_area : 广东
             * cnty : 中国
             * lat : 22.5410099
             * lon : 114.05095673
             * tz : +8.00
             */

            private String cid;
            private String location;
            private String parent_city;
            private String admin_area;
            private String cnty;
            private String lat;
            private String lon;
            private String tz;

            public String getCid() {
                return cid;
            }

            public void setCid(String cid) {
                this.cid = cid;
            }

            public String getLocation() {
                return location;
            }

            public void setLocation(String location) {
                this.location = location;
            }

            public String getParent_city() {
                return parent_city;
            }

            public void setParent_city(String parent_city) {
                this.parent_city = parent_city;
            }

            public String getAdmin_area() {
                return admin_area;
            }

            public void setAdmin_area(String admin_area) {
                this.admin_area = admin_area;
            }

            public String getCnty() {
                return cnty;
            }

            public void setCnty(String cnty) {
                this.cnty = cnty;
            }

            public String getLat() {
                return lat;
            }

            public void setLat(String lat) {
                this.lat = lat;
            }

            public String getLon() {
                return lon;
            }

            public void setLon(String lon) {
                this.lon = lon;
            }

            public String getTz() {
                return tz;
            }

            public void setTz(String tz) {
                this.tz = tz;
            }
        }

        public static class UpdateBean {
            /**
             * loc : 2020-04-28 19:56
             * utc : 2020-04-28 11:56
             */

            private String loc;
            private String utc;

            public String getLoc() {
                return loc;
            }

            public void setLoc(String loc) {
                this.loc = loc;
            }

            public String getUtc() {
                return utc;
            }

            public void setUtc(String utc) {
                this.utc = utc;
            }
        }

        public static class HourlyBean {
            /**
             * cloud : 43
             * cond_code : 101
             * cond_txt : 多云
             * dew : 19
             * hum : 71
             * pop : 0
             * pres : 1010
             * time : 2020-04-28 22:00
             * tmp : 23
             * wind_deg : 82
             * wind_dir : 东风
             * wind_sc : 1-2
             * wind_spd : 2
             */

            private String cloud;
            private String cond_code;
            private String cond_txt;
            private String dew;
            private String hum;
            private String pop;
            private String pres;
            private String time;
            private String tmp;
            private String wind_deg;
            private String wind_dir;
            private String wind_sc;
            private String wind_spd;

            public String getCloud() {
                return cloud;
            }

            public void setCloud(String cloud) {
                this.cloud = cloud;
            }

            public String getCond_code() {
                return cond_code;
            }

            public void setCond_code(String cond_code) {
                this.cond_code = cond_code;
            }

            public String getCond_txt() {
                return cond_txt;
            }

            public void setCond_txt(String cond_txt) {
                this.cond_txt = cond_txt;
            }

            public String getDew() {
                return dew;
            }

            public void setDew(String dew) {
                this.dew = dew;
            }

            public String getHum() {
                return hum;
            }

            public void setHum(String hum) {
                this.hum = hum;
            }

            public String getPop() {
                return pop;
            }

            public void setPop(String pop) {
                this.pop = pop;
            }

            public String getPres() {
                return pres;
            }

            public void setPres(String pres) {
                this.pres = pres;
            }

            public String getTime() {
                return time;
            }

            public void setTime(String time) {
                this.time = time;
            }

            public String getTmp() {
                return tmp;
            }

            public void setTmp(String tmp) {
                this.tmp = tmp;
            }

            public String getWind_deg() {
                return wind_deg;
            }

            public void setWind_deg(String wind_deg) {
                this.wind_deg = wind_deg;
            }

            public String getWind_dir() {
                return wind_dir;
            }

            public void setWind_dir(String wind_dir) {
                this.wind_dir = wind_dir;
            }

            public String getWind_sc() {
                return wind_sc;
            }

            public void setWind_sc(String wind_sc) {
                this.wind_sc = wind_sc;
            }

            public String getWind_spd() {
                return wind_spd;
            }

            public void setWind_spd(String wind_spd) {
                this.wind_spd = wind_spd;
            }
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312

页面渲染的数据从这些返回实体里面取,下一步,创建接口,打开ApiService.java
在里面增加
在这里插入图片描述

注意Key用自己的,然后创建订阅,打开WeatherContract.java
在这里插入图片描述
在这里插入图片描述

订阅方法

		/**
         * 逐小时预报
         * @param context
         * @param location
         */
        public void hourly(final Context context,String location){
            ApiService service = ServiceGenerator.createService(ApiService.class,0);
            service.getHourly(location).enqueue(new NetCallBack<HourlyResponse>() {
                @Override
                public void onSuccess(Call<HourlyResponse> call, Response<HourlyResponse> response) {
                    if(getView() != null){
                        getView().getHourlyResult(response);
                    }
                }

                @Override
                public void onFailed() {
                    if(getView() != null){
                        getView().getDataFailed();
                    }
                }
            });
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

接口返回值

		//查询逐小时天气的数据返回
        void getHourlyResult(Response<HourlyResponse> response);
  • 1
  • 2

接下来就是MainActivity.java

在这里插入图片描述
代码如下:

	//逐小时天气预报返回
    @Override
    public void getHourlyResult(Response<HourlyResponse> response) {
        dismissLoadingDialog();//关闭弹窗
        if (("ok").equals(response.body().getHeWeather6().get(0).getStatus())) {

        } else {
            ToastUtils.showShortToast(context, response.body().getHeWeather6().get(0).getStatus());
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

需要在三个地方进行请求,拿到定位之后、下拉的时候、切换城市之后
在这里插入图片描述

在这里插入图片描述
通过断点查看到了返回的数据,这一步你可以跳过,不会有什么影响,往下走
在这里插入图片描述
现在数据已经拿到了,接下来就是数据的渲染了,依然使用列表来做显示,这里我们用横向的列表,摆放的位置就放在7天天气预报的上方,这样会比较合理。
接下来在layout下创建一个item_weather_hourly_list.xml文件,
在这里插入图片描述
这就是每一个item的显示效果,只不过背景是无色的,这里之所以放黑色背景,是在写布局的时候易于调整。

item_weather_hourly_list.xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    android:padding="8dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <!--时间-->
    <TextView
        android:id="@+id/tv_time"
        android:textColor="#FFF"
        android:text="上午10:00"
        android:textSize="14sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <!--气候图标-->
    <ImageView
        android:layout_marginTop="12dp"
        android:layout_marginBottom="8dp"
        android:id="@+id/iv_weather_state"
        android:background="@mipmap/icon_100"
        android:layout_width="30dp"
        android:layout_height="30dp"/>
    <!--温度-->
    <TextView
        android:textSize="20sp"
        android:id="@+id/tv_temperature"
        android:textColor="#FFF"
        android:text="25℃"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

然后在WeatherUtil.java中再增加一个方法showTimeInfo

	/**
     * 根据传入的时间显示时间段描述信息
     * @param timeData
     * @return
     */
    public static String showTimeInfo(String timeData){
        String timeInfo = null;
        int time = 0;
        time = Integer.parseInt(timeData.trim().substring(0,2));
        if (time >= 0 && time <= 6) {
            timeInfo = "凌晨";
        }else if (time > 6 && time <= 12) {
            timeInfo = "上午";
        }else if (time > 12 && time <= 13) {
            timeInfo = "中午";
        }else if (time > 13 && time <= 18) {
            timeInfo = "下午";
        } else if (time > 18 && time <= 24) {
            timeInfo = "晚上";
        } else {
            timeInfo = "未知";
        }
        return timeInfo;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

接下来在adapter包下创建一个WeatherHourlyAdapter.java

package com.llw.goodweather.adapter;

import android.widget.ImageView;

import androidx.annotation.Nullable;

import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.goodweather.R;
import com.llw.goodweather.bean.HourlyResponse;
import com.llw.goodweather.utils.WeatherUtil;

import java.util.List;

public class WeatherHourlyAdapter extends BaseQuickAdapter<HourlyResponse.HeWeather6Bean.HourlyBean, BaseViewHolder> {

    public WeatherHourlyAdapter(int layoutResId, @Nullable List<HourlyResponse.HeWeather6Bean.HourlyBean> data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, HourlyResponse.HeWeather6Bean.HourlyBean item) {
        //首先是对时间格式的处理,因为拿到的时间是  2020-04-28 22:00  要改成   晚上22:00
        //分两步,第一个是字符串的截取,第二个是时间段的判断返回文字描述
        String datetime = item.getTime();//赋值
        String time = datetime.substring(11);//截去前面的字符,保留后面所有的字符,就剩下 22:00
        helper.setText(R.id.tv_time,WeatherUtil.showTimeInfo(time)+time)//时间
                .setText(R.id.tv_temperature,item.getTmp()+"℃");//温度

        //天气状态图片
        ImageView weatherStateIcon = helper.getView(R.id.iv_weather_state);
        int code = Integer.parseInt(item.getCond_code());//获取天气状态码,根据状态码来显示图标
        WeatherUtil.changeIcon(weatherStateIcon,code);
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

适配器写完了,接下来修改一下activity_main.xml布局文件
在这里插入图片描述
icon_weather_sun.png
上方是一个图标。因为是白色所以你看到不到而已
在这里插入图片描述
我改动之前的这个更新时间的布局,下方放了一个分割线和一个列表,列表用于显示逐小时天气、原来的天气预报列表增加了内边距
修改地方的代码如下:

							<!--上一次更新时间-->
                            <LinearLayout
                                android:paddingLeft="20dp"
                                android:paddingRight="20dp"
                                android:layout_marginTop="20dp"
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content">
                                <TextView
                                    android:drawableLeft="@mipmap/icon_weather_sun"
                                    android:drawablePadding="4dp"
                                    android:text="Good Weather"
                                    android:textSize="@dimen/sp_12"
                                    android:textColor="#FFF"
                                    android:layout_width="wrap_content"
                                    android:layout_height="wrap_content"/>
                                <TextView
                                    android:gravity="right"
                                    android:id="@+id/tv_old_time"
                                    android:textColor="#FFF"
                                    android:text="上次更新时间:"
                                    android:textSize="@dimen/sp_12"
                                    android:layout_width="0dp"
                                    android:layout_weight="1"
                                    android:layout_height="wrap_content"/>
                            </LinearLayout>

                            <!--分隔线 增加UI效果-->
                            <View
                                android:layout_marginTop="8dp"
                                android:layout_marginLeft="20dp"
                                android:layout_marginRight="20dp"
                                android:layout_width="match_parent"
                                android:layout_height="1dp"
                                android:background="#FFF"
                                android:alpha="0.1"/>

                            <!--用于显示逐小时天气-->
                            <androidx.recyclerview.widget.RecyclerView
                                android:padding="12dp"
                                android:id="@+id/rv_hourly"
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"/>

                            <!--用于显示天气预报数据-->
                            <androidx.recyclerview.widget.RecyclerView
                                android:paddingLeft="8dp"
                                android:paddingRight="8dp"
                                android:id="@+id/rv"
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

接下来就是在MainActivity.java中渲染数据了。
在这里插入图片描述
初始列表和适配器

	List<HourlyResponse.HeWeather6Bean.HourlyBean> mListHourly;//初始化数据源 -> 逐小时天气预报
    WeatherHourlyAdapter mAdapterHourly;//初始化适配器 逐小时天气预报
  • 1
  • 2

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
改动的地方大概就这么多了,然后可以运行一下了。

运行效果图如下
在这里插入图片描述
这一篇文章就结束了,但是这个APP并没有结束,后续我有什么想法还会一直在里面加,当然各位有什么好的建议也可以评论或者私信我,写完才发现这篇文章也有34000多字,说短也不短了,如果你看了里面的内容不明白的话,就请从第一篇看起,你就一定知道是怎么回事了。

源码地址:GoodWeather
欢迎 StarFork

下一篇:Android 天气APP(十二)空气质量、UI优化调整

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/468027
推荐阅读
相关标签
  

闽ICP备14008679号