Android 天气APP(六)旋转风车显示风力、风向

上一篇:Android 天气APP(五)天气预报、生活指数的数据请求与渲染

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

在上一篇文章中,实现了实时天气的获取,本篇文章中需要实现天气预报和生活指数的数据请求和渲染。

一、添加天气预报API

在这里插入图片描述

这里的每日数据我们可以拿到7天的返回数据。

在这里插入图片描述

通过这里的数据可以手写一个DailyResponse类,在bean包下新建,代码如下所示:

public class DailyResponse {

    private String code;
    private String updateTime;
    private String fxLink;
    private ReferBean refer;
    private List<DailyBean> daily;

    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<DailyBean> getDaily() {
        return daily;
    }

    public void setDaily(List<DailyBean> daily) {
        this.daily = daily;
    }

    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 DailyBean {

        private String fxDate;
        private String sunrise;
        private String sunset;
        private String moonrise;
        private String moonset;
        private String moonPhase;
        private String moonPhaseIcon;
        private String tempMax;
        private String tempMin;
        private String iconDay;
        private String textDay;
        private String iconNight;
        private String textNight;
        private String wind360Day;
        private String windDirDay;
        private String windScaleDay;
        private String windSpeedDay;
        private String wind360Night;
        private String windDirNight;
        private String windScaleNight;
        private String windSpeedNight;
        private String humidity;
        private String precip;
        private String pressure;
        private String vis;
        private String cloud;
        private String uvIndex;

        public String getFxDate() {
            return fxDate;
        }

        public void setFxDate(String fxDate) {
            this.fxDate = fxDate;
        }

        public String getSunrise() {
            return sunrise;
        }

        public void setSunrise(String sunrise) {
            this.sunrise = sunrise;
        }

        public String getSunset() {
            return sunset;
        }

        public void setSunset(String sunset) {
            this.sunset = sunset;
        }

        public String getMoonrise() {
            return moonrise;
        }

        public void setMoonrise(String moonrise) {
            this.moonrise = moonrise;
        }

        public String getMoonset() {
            return moonset;
        }

        public void setMoonset(String moonset) {
            this.moonset = moonset;
        }

        public String getMoonPhase() {
            return moonPhase;
        }

        public void setMoonPhase(String moonPhase) {
            this.moonPhase = moonPhase;
        }

        public String getMoonPhaseIcon() {
            return moonPhaseIcon;
        }

        public void setMoonPhaseIcon(String moonPhaseIcon) {
            this.moonPhaseIcon = moonPhaseIcon;
        }

        public String getTempMax() {
            return tempMax;
        }

        public void setTempMax(String tempMax) {
            this.tempMax = tempMax;
        }

        public String getTempMin() {
            return tempMin;
        }

        public void setTempMin(String tempMin) {
            this.tempMin = tempMin;
        }

        public String getIconDay() {
            return iconDay;
        }

        public void setIconDay(String iconDay) {
            this.iconDay = iconDay;
        }

        public String getTextDay() {
            return textDay;
        }

        public void setTextDay(String textDay) {
            this.textDay = textDay;
        }

        public String getIconNight() {
            return iconNight;
        }

        public void setIconNight(String iconNight) {
            this.iconNight = iconNight;
        }

        public String getTextNight() {
            return textNight;
        }

        public void setTextNight(String textNight) {
            this.textNight = textNight;
        }

        public String getWind360Day() {
            return wind360Day;
        }

        public void setWind360Day(String wind360Day) {
            this.wind360Day = wind360Day;
        }

        public String getWindDirDay() {
            return windDirDay;
        }

        public void setWindDirDay(String windDirDay) {
            this.windDirDay = windDirDay;
        }

        public String getWindScaleDay() {
            return windScaleDay;
        }

        public void setWindScaleDay(String windScaleDay) {
            this.windScaleDay = windScaleDay;
        }

        public String getWindSpeedDay() {
            return windSpeedDay;
        }

        public void setWindSpeedDay(String windSpeedDay) {
            this.windSpeedDay = windSpeedDay;
        }

        public String getWind360Night() {
            return wind360Night;
        }

        public void setWind360Night(String wind360Night) {
            this.wind360Night = wind360Night;
        }

        public String getWindDirNight() {
            return windDirNight;
        }

        public void setWindDirNight(String windDirNight) {
            this.windDirNight = windDirNight;
        }

        public String getWindScaleNight() {
            return windScaleNight;
        }

        public void setWindScaleNight(String windScaleNight) {
            this.windScaleNight = windScaleNight;
        }

        public String getWindSpeedNight() {
            return windSpeedNight;
        }

        public void setWindSpeedNight(String windSpeedNight) {
            this.windSpeedNight = windSpeedNight;
        }

        public String getHumidity() {
            return humidity;
        }

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

        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 getVis() {
            return vis;
        }

        public void setVis(String vis) {
            this.vis = vis;
        }

        public String getCloud() {
            return cloud;
        }

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

        public String getUvIndex() {
            return uvIndex;
        }

        public void setUvIndex(String uvIndex) {
            this.uvIndex = uvIndex;
        }
    }
}

下面我们添加API,在ApiService中添加如下代码:

	@GET("/v7/weather/7d?key=" + API_KEY)
    Observable<DailyResponse> dailyWeather(@Query("location") String location);

二、添加API调用方法

在上一篇中写了一个WeatherRepository,用于处理所有的天气数据API请求,那么同样在这里添加,添加方法如下所示:

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

                    @Override
                    public void onFailure(Throwable e) {
                        Log.e(TAG, "onFailure: " + e.getMessage());
                        failed.postValue(type + e.getMessage());
                    }
                }));
    }

添加位置如下图所示:

在这里插入图片描述

下面在MainViewModel中添加对WeatherRepository中dailyWeather()方法的调用,添加代码如下:

public MutableLiveData<DailyResponse> dailyResponseMutableLiveData = new MutableLiveData<>();

public void dailyWeather(String cityId) {
        new WeatherRepository().dailyWeather(dailyResponseMutableLiveData, failed, cityId);
    }

添加位置如下图所示:

在这里插入图片描述

这里就有一个问题,那就是假如WeatherRepository对象的创建,如果我们请求一次网络就创建一个对象,无疑是有问题的,因此我们可以在WeatherRepository中增加一个单例,在WeatherRepository中添加如下代码:

	private static final class WeatherRepositoryHolder {
        private static final WeatherRepository mInstance = new WeatherRepository();
    }

    public static WeatherRepository getInstance() {
        return WeatherRepositoryHolder.mInstance;
    }

这里通过静态内部类的方式构建单例,然后在MainViewModel中调用,如下图所示:

在这里插入图片描述

那么参考这一个改动的方式,你将SearchCityRepository也改动一下,这个我就不写了,依葫芦画瓢就行。

三、工具类

之前通过实时天气得到的数据里面有一个更新时间的返回值是2021-11-15T16:35+08:00格式的,不同于常规的时间格式,为此我写了一个工具类,后面的代码中会用到这个工具类,在com.llw.goodweather包下新建utils包,utils包下新建EasyDate类,代码如下:

public final class EasyDate {

    public static final String STANDARD_TIME = "yyyy-MM-dd HH:mm:ss";
    public static final String FULL_TIME = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String YEAR_MONTH_DAY = "yyyy-MM-dd";
    public static final String YEAR_MONTH_DAY_CN = "yyyy年MM月dd号";
    public static final String HOUR_MINUTE_SECOND = "HH:mm:ss";
    public static final String HOUR_MINUTE_SECOND_CN = "HH时mm分ss秒";
    public static final String YEAR = "yyyy";
    public static final String MONTH = "MM";
    public static final String DAY = "dd";
    public static final String HOUR = "HH";
    public static final String MINUTE = "mm";
    public static final String SECOND = "ss";
    public static final String MILLISECOND = "SSS";
    public static final String YESTERDAY = "昨天";
    public static final String TODAY = "今天";
    public static final String TOMORROW = "明天";
    public static final String SUNDAY = "星期日";
    public static final String MONDAY = "星期一";
    public static final String TUESDAY = "星期二";
    public static final String WEDNESDAY = "星期三";
    public static final String THURSDAY = "星期四";
    public static final String FRIDAY = "星期五";
    public static final String SATURDAY = "星期六";
    public static final String[] weekDays = {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY};

    /**
     * 获取标准时间
     *
     * @return 例如 2021-07-01 10:35:53
     */
    public static String getDateTime() {
        return new SimpleDateFormat(STANDARD_TIME, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取完整时间
     *
     * @return 例如 2021-07-01 10:37:00.748
     */
    public static String getFullDateTime() {
        return new SimpleDateFormat(FULL_TIME, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取年月日(今天)
     *
     * @return 例如 2021-07-01
     */
    public static String getTheYearMonthAndDay() {
        return new SimpleDateFormat(YEAR_MONTH_DAY, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取年月日
     *
     * @return 例如 2021年07月01号
     */
    public static String getTheYearMonthAndDayCn() {
        return new SimpleDateFormat(YEAR_MONTH_DAY_CN, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取年月日
     *
     * @param delimiter 分隔符
     * @return 例如 2021年07月01号
     */
    public static String getTheYearMonthAndDayDelimiter(CharSequence delimiter) {
        return new SimpleDateFormat(YEAR + delimiter + MONTH + delimiter + DAY, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取时分秒
     *
     * @return 例如 10:38:25
     */
    public static String getHoursMinutesAndSeconds() {
        return new SimpleDateFormat(HOUR_MINUTE_SECOND, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取时分秒
     *
     * @return 例如 10时38分50秒
     */
    public static String getHoursMinutesAndSecondsCn() {
        return new SimpleDateFormat(HOUR_MINUTE_SECOND_CN, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取时分秒
     *
     * @param delimiter 分隔符
     * @return 例如 2021/07/01
     */
    public static String getHoursMinutesAndSecondsDelimiter(CharSequence delimiter) {
        return new SimpleDateFormat(HOUR + delimiter + MINUTE + delimiter + SECOND, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取年
     *
     * @return 例如 2021
     */
    public static String getYear() {
        return new SimpleDateFormat(YEAR, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取月
     *
     * @return 例如 07
     */
    public static String getMonth() {
        return new SimpleDateFormat(MONTH, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取天
     *
     * @return 例如 01
     */
    public static String getDay() {
        return new SimpleDateFormat(DAY, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取小时
     *
     * @return 例如 10
     */
    public static String getHour() {
        return new SimpleDateFormat(HOUR, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取分钟
     *
     * @return 例如 40
     */
    public static String getMinute() {
        return new SimpleDateFormat(MINUTE, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取秒
     *
     * @return 例如 58
     */
    public static String getSecond() {
        return new SimpleDateFormat(SECOND, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取毫秒
     *
     * @return 例如 666
     */
    public static String getMilliSecond() {
        return new SimpleDateFormat(MILLISECOND, Locale.CHINESE).format(new Date());
    }

    /**
     * 获取时间戳
     *
     * @return 例如 1625107306051
     */
    public static long getTimestamp() {
        return System.currentTimeMillis();
    }

    /**
     * 将时间转换为时间戳
     *
     * @param time 例如 2021-07-01 10:44:11
     * @return 1625107451000
     */
    public static long dateToStamp(String time) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(STANDARD_TIME, Locale.CHINESE);
        Date date = null;
        try {
            date = simpleDateFormat.parse(time);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return Objects.requireNonNull(date).getTime();
    }

    /**
     * 将时间戳转换为时间
     *
     * @param timeMillis 例如 1625107637084
     * @return 例如 2021-07-01 10:47:17
     */
    public static String stampToDate(long timeMillis) {
        return new SimpleDateFormat(STANDARD_TIME, Locale.CHINESE).format(new Date(timeMillis));
    }

    /**
     * 格林尼治时间转化为常规时间格式
     *
     * @param dateTime "2020-07-16T09:39+08:00"
     * @return
     */
    public static String greenwichupToSimpleTime(String dateTime) {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
        Date date = null;
        try {
            date = df.parse(dateTime);
            SimpleDateFormat df1 = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
            Date date1 = df1.parse(date.toString());
            DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            return df2.format(date1);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return "转换失败";
    }

    public static String updateTime(String dateTime) {
        String result = null;
        if (dateTime == null) {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
            result = sdf.format(new Date());
        } else {
            result = dateTime.substring(11, 16);
            Log.d("dateTime-->", result);
        }
        return result;
    }

    /**
     * 获取今天是星期几
     *
     * @return 例如 星期四
     */
    public static String getTodayOfWeek() {
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());
        int index = cal.get(Calendar.DAY_OF_WEEK) - 1;
        if (index < 0) {
            index = 0;
        }
        return weekDays[index];
    }

    /**
     * 根据输入的日期时间计算是星期几
     *
     * @param dateTime 例如 2021-06-20
     * @return 例如 星期日
     */
    public static String getWeek(String dateTime) {
        Calendar cal = Calendar.getInstance();
        if ("".equals(dateTime)) {
            cal.setTime(new Date(System.currentTimeMillis()));
        } else {
            SimpleDateFormat sdf = new SimpleDateFormat(YEAR_MONTH_DAY, Locale.getDefault());
            Date date;
            try {
                date = sdf.parse(dateTime);
            } catch (ParseException e) {
                date = null;
                e.printStackTrace();
            }
            if (date != null) {
                cal.setTime(new Date(date.getTime()));
            }
        }
        return weekDays[cal.get(Calendar.DAY_OF_WEEK) - 1];
    }

    /**
     * 获取输入日期的昨天
     *
     * @param date 例如 2021-07-01
     * @return 例如 2021-06-30
     */
    public static String getYesterday(Date date) {
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, -1);
        date = calendar.getTime();
        return new SimpleDateFormat(YEAR_MONTH_DAY, Locale.getDefault()).format(date);
    }

    /**
     * 获取输入日期的明天
     *
     * @param date 例如 2021-07-01
     * @return 例如 2021-07-02
     */
    public static String getTomorrow(Date date) {
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, +1);
        date = calendar.getTime();
        return new SimpleDateFormat(YEAR_MONTH_DAY, Locale.getDefault()).format(date);
    }

    /**
     * 根据年月日计算是星期几并与当前日期判断  非昨天、今天、明天 则以星期显示
     *
     * @param dateTime 例如 2021-07-03
     * @return 例如 星期六
     */
    public static String getDayInfo(String dateTime) {
        String dayInfo;
        String yesterday = getYesterday(new Date());
        String today = getTheYearMonthAndDay();
        String tomorrow = getTomorrow(new Date());

        if (dateTime.equals(yesterday)) {
            dayInfo = YESTERDAY;
        } else if (dateTime.equals(today)) {
            dayInfo = TODAY;
        } else if (dateTime.equals(tomorrow)) {
            dayInfo = TOMORROW;
        } else {
            dayInfo = getWeek(dateTime);
        }
        return dayInfo;
    }

    /**
     * 获取本月天数
     *
     * @return 例如 31
     */
    public static int getCurrentMonthDays() {
        Calendar calendar = Calendar.getInstance();
        //把日期设置为当月第一天
        calendar.set(Calendar.DATE, 1);
        //日期回滚一天,也就是最后一天
        calendar.roll(Calendar.DATE, -1);
        return calendar.get(Calendar.DATE);
    }

    /**
     * 获得指定月的天数
     *
     * @param year  例如 2021
     * @param month 例如 7
     * @return 例如 31
     */
    public static int getMonthDays(int year, int month) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month - 1);
        //把日期设置为当月第一天
        calendar.set(Calendar.DATE, 1);
        //日期回滚一天,也就是最后一天
        calendar.roll(Calendar.DATE, -1);
        return calendar.get(Calendar.DATE);
    }

    /**
     * 时间截取
     * @param date 时间
     * @return
     */
    public static String dateSplit(String date) {//2020-08-07
        String result = null;

        String[] array = date.split("-");
        result = Integer.parseInt(array[1]) + "月" + Integer.parseInt(array[2]) + "号";
        return result;
    }
	
	/**
     * 根据传入的时间显示时间段描述信息
     */
    public static String showTimeInfo(String timeData) {
        String timeInfo;
        int time;

        if (timeData == null || timeData.equals("")) {
            timeInfo = "获取失败";
        } else {
            time = Integer.parseInt(timeData.trim().substring(0, 2));
            if (time >= 0 && time <= 6) {
                timeInfo = "凌晨";
            } else if (time > 6 && time <= 12) {
                timeInfo = "上午";
            } else if (time == 13) {
                timeInfo = "中午";
            } else if (time > 13 && time <= 18) {
                timeInfo = "下午";
            } else if (time > 18 && time <= 24) {
                timeInfo = "晚上";
            } else {
                timeInfo = "未知";
            }
        }
        return timeInfo;
    }
}

然后还需要根据天气状态码显示不同的天气图标,这里也需要一个工具类,在utils包下新建WeatherUtil类,代码如下:

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;
            //阴 V7
            case 104:
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_104);
                break;
            //晴 晚上  V7
            case 150:
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_150);
                break;
            //晴间多云 晚上  V7
            case 153:
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_153);
                break;
            //阴 晚上  V7
            case 154:
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_154);
                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;
            //未知
            default:
                weatherStateIcon.setBackgroundResource(R.mipmap.icon_999);
                break;
        }
    }

    /**
     * 紫外线等级描述
     *
     * @param uvIndex
     * @return
     */
    public static String uvIndexInfo(String uvIndex) {
        String result = null;
        Log.d("uvIndex-->", uvIndex);
        int level = Integer.parseInt(uvIndex);
        if (level <= 2) {
            result = "较弱";
        } else if (level <= 5) {
            result = "弱";
        } else if (level <= 7) {
            result = "中等";
        } else if (level <= 10) {
            result = "强";
        } else if (level <= 15) {
            result = "很强";
        }
        return result;
    }

    /**
     * 根据api的提示转为更为人性化的提醒
     *
     * @param apiInfo
     * @return
     */
    public static String apiToTip(String apiInfo) {
        String result = null;
        String str = null;
        if (apiInfo.contains("AQI ")) {
            str = apiInfo.replace("AQI ", " ");
        } else {
            str = apiInfo;
        }
        //优,良,轻度污染,中度污染,重度污染,严重污染
        switch (str) {
            case "优":
                result = "♪(^∇^*)  空气很好。";
                break;
            case "良":
                result = "ヽ(✿゚▽゚)ノ  空气不错。";
                break;
            case "轻度污染":
                result = "(⊙﹏⊙)  空气有些糟糕。";
                break;
            case "中度污染":
                result = " ε=(´ο`*)))  唉 空气污染较为严重,注意防护。";
                break;
            case "重度污染":
                result = "o(≧口≦)o  空气污染很严重,记得戴口罩哦!";
                break;
            case "严重污染":
                result = "ヽ(*。>Д<)o゜  完犊子了!空气污染非常严重,要减少出门,定期检查身体,能搬家就搬家吧!";
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 紫外线详细描述
     *
     * @param uvIndexInfo
     * @return
     */
    public static String uvIndexToTip(String uvIndexInfo) {
        String result = null;
        switch (uvIndexInfo) {
            case "较弱":
                result = "紫外线较弱,不需要采取防护措施;若长期在户外,建议涂擦SPF在8-12之间的防晒护肤品。";
                break;
            case "弱":
                result = "紫外线弱,可以适当采取一些防护措施,涂擦SPF在12-15之间、PA+的防晒护肤品。";
                break;
            case "中等":
                result = "紫外线中等,外出时戴好遮阳帽、太阳镜和太阳伞等;涂擦SPF高于15、PA+的防晒护肤品。";
                break;
            case "强":
                result = "紫外线较强,避免在10点至14点暴露于日光下.外出时戴好遮阳帽、太阳镜和太阳伞等,涂擦SPF20左右、PA++的防晒护肤品。";
                break;
            case "很强":
                result = "紫外线很强,尽可能不在室外活动,必须外出时,要采取各种有效的防护措施。";
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 早晚温差提示
     *
     * @param height 当天最高温
     * @param low    当天最低温
     */
    public static String differenceTempTip(int height, int low) {
        StringBuffer stringBuffer = new StringBuffer();

        stringBuffer.append("    今天最高温" + height + "℃,最低温" + low + "℃。");
        if (height - low > 5) {//温差大
            if (height > 25) {
                stringBuffer.append("早晚温差较大,加强自我防护,防治感冒,对自己好一点(* ̄︶ ̄)");
            } else if (height > 20) {
                stringBuffer.append("天气转阴温度低,上下班请注意添衣保暖(*^▽^*)");
            } else if (height > 15) {
                stringBuffer.append("关怀不是今天才开始,关心也不是今天就结束,希望你注意保暖ヾ(◍°∇°◍)ノ゙");
            }
        } else {//温差小
            if (low > 20) {
                stringBuffer.append("多运动,多喝水,注意补充水分(* ̄︶ ̄)");
            } else if (low > 10) {
                stringBuffer.append("早睡早起,别熬夜,无论晴天还是雨天,每天都是新的一天(*^▽^*)");
            } else if (low > 0) {
                stringBuffer.append("天气寒冷,注意防寒和保暖,也不要忘记锻炼喔ヾ(◍°∇°◍)ノ゙");
            }
        }
        return stringBuffer.toString();
    }
}

这里面的图标你可以源码中去找,新版和旧版里面都有。

四、天气预报布局和适配器

天气预报可以得到7天的数据,我们通过列表来显示,首先我们修改一下activity_main.xml中的代码:

	<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_daily"
		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/tv_update_time"  />

添加一个RecyclerView,添加位置如下图所示:

在这里插入图片描述

然后我们创建一个Item,用于显示数据,在layout下新建一个item_daily_rv.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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/dp_8"
    android:paddingBottom="@dimen/dp_8">
    <!--日期-->
    <TextView
        android:id="@+id/tv_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:textSize="@dimen/sp_14"
        app:layout_constraintBottom_toBottomOf="@+id/iv_status"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/iv_status" />
    <!--天气状态-->
    <ImageView
        android:id="@+id/iv_status"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:contentDescription="天气状态"
        android:textColor="@color/white"
        android:textSize="@dimen/sp_14"
        app:layout_constraintEnd_toEndOf="@+id/tv_low"
        app:layout_constraintStart_toStartOf="@+id/tv_date"
        app:layout_constraintTop_toTopOf="parent" />
    <!--最高温-->
    <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"
        app:layout_constraintBottom_toBottomOf="@+id/tv_low"
        app:layout_constraintEnd_toStartOf="@+id/tv_low"
        app:layout_constraintTop_toTopOf="@+id/tv_low" />
    <!--最低温-->
    <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"
        app:layout_constraintBottom_toBottomOf="@+id/iv_status"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/iv_status" />
</androidx.constraintlayout.widget.ConstraintLayout>

Item布局写好了,这里面有一个颜色值,你在colors.xml中增加如下代码就可以了:

<color name="temp_min_tx">#B3BCCA</color><!--低温文字颜色-->

下面在com.llw.goodweather下新建adapter包,包下新建一个DailyAdapter类,代码如下:

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

    private final List<DailyResponse.DailyBean> dailyBeans;

    public DailyAdapter(List<DailyResponse.DailyBean> dailyBeans) {
        this.dailyBeans = dailyBeans;
    }

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

    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        DailyResponse.DailyBean dailyBean = dailyBeans.get(position);
        holder.binding.tvDate.setText(EasyDate.dateSplit(dailyBean.getFxDate()) + EasyDate.getDayInfo(dailyBean.getFxDate()));
        WeatherUtil.changeIcon(holder.binding.ivStatus, Integer.parseInt(dailyBean.getIconDay()));
        holder.binding.tvHeight.setText(dailyBean.getTempMax() + "℃");
        holder.binding.tvLow.setText(" / " + dailyBean.getTempMin() + "℃");
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemDailyRvBinding binding;

        public ViewHolder(@NonNull ItemDailyRvBinding itemTextRvBinding) {
            super(itemTextRvBinding.getRoot());
            binding = itemTextRvBinding;
        }
    }
}

这里的适配器我就不再用第三方库了,不搞那些幺蛾子了,减少问题出现,这里是结合了ViewBinding的使用,里面用到了刚才所写的工具类,代码比较简单,下面就是通过MainActivity显示数据就可以了。

五、显示天气预报数据

MainActivity中新增如下代码:

	private final List<DailyResponse.DailyBean> dailyBeanList = new ArrayList<>();
    private final DailyAdapter dailyAdapter = new DailyAdapter(dailyBeanList);
	
	private void initView() {
        binding.rvDaily.setLayoutManager(new LinearLayoutManager(this));
        binding.rvDaily.setAdapter(dailyAdapter);
    }

然后我们在onCreate()方法中调用initView(),如下图所示:

在这里插入图片描述

下面就是回调处理了,在onObserveData()方法中,搜索城市回调中请求天气预报接口,如下图所示:

在这里插入图片描述

然后进行天气预报数据回调的处理,添加如下所示代码:

			viewModel.dailyResponseMutableLiveData.observe(this, dailyResponse -> {
                List<DailyResponse.DailyBean> daily = dailyResponse.getDaily();
                if (daily != null) {
                    if (dailyBeanList.size() > 0) {
                        dailyBeanList.clear();
                    }
                    dailyBeanList.addAll(daily);
                    dailyAdapter.notifyDataSetChanged();
                }
            });

添加代码位置如下图所示:

在这里插入图片描述

下面就可以运行了,手机真机运行。

在这里插入图片描述

六、添加生活指数API

在这里插入图片描述

通过这个接口拿到返回数据,在bean包下新建一个LifestyleResponse类,代码如下:

public class LifestyleResponse {

    private String code;
    private String updateTime;
    private String fxLink;
    private ReferBean refer;
    private List<DailyBean> daily;

    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<DailyBean> getDaily() {
        return daily;
    }

    public void setDaily(List<DailyBean> daily) {
        this.daily = daily;
    }

    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 DailyBean {

        private String date;
        private String type;
        private String name;
        private String level;
        private String category;
        private String text;

        public String getDate() {
            return date;
        }

        public void setDate(String date) {
            this.date = date;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getLevel() {
            return level;
        }

        public void setLevel(String level) {
            this.level = level;
        }

        public String getCategory() {
            return category;
        }

        public void setCategory(String category) {
            this.category = category;
        }

        public String getText() {
            return text;
        }

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

在ApiService中添加API接口,代码如下所示:

	@GET("/v7/indices/1d?key=" + API_KEY)
    Observable<LifestyleResponse> lifestyle(@Query("type") String type, @Query("location") String location);

七、添加生活指数调用方法

下面同样添加API方法调用,在WeatherRepository中增加如下方法代码:

    public void lifestyle(MutableLiveData<LifestyleResponse> responseLiveData,
                             MutableLiveData<String> failed, String cityId) {
        String type = "生活指数-->";
        NetworkApi.createService(ApiService.class, ApiType.WEATHER).lifestyle("1,2,3,4,5,6,7,8,9", cityId)
                .compose(NetworkApi.applySchedulers(new BaseObserver<>() {
                    @Override
                    public void onSuccess(LifestyleResponse lifestyleResponse) {
                        if (lifestyleResponse == null) {
                            failed.postValue("生活指数数据为null,请检查城市ID是否正确。");
                            return;
                        }
                        //请求接口成功返回数据,失败返回状态码
                        if (Constant.SUCCESS.equals(lifestyleResponse.getCode())) {
                            responseLiveData.postValue(lifestyleResponse);
                        } else {
                            failed.postValue(type + lifestyleResponse.getCode());
                        }
                    }

                    @Override
                    public void onFailure(Throwable e) {
                        Log.e(TAG, "onFailure: " + e.getMessage());
                        failed.postValue(type + e.getMessage());
                    }
                }));
    }

下面进入到MainViewModel中,添加如下代码:

	public MutableLiveData<LifestyleResponse> lifestyleResponseMutableLiveData = new MutableLiveData<>();
	
	public void lifestyle(String cityId) {
        WeatherRepository.getInstance().lifestyle(lifestyleResponseMutableLiveData, failed, cityId);
    }

相信你已经知道在哪里添加了,下面构建生活指数的页面显示。

八、生活指数布局和适配器

生活指数也是一个列表,按照常理我们应该直接在activity_main.xml中直接添加RecyclerView就可以了,但是需要考虑一个问题,那就是屏幕大小的问题,当我们的页面内容高度超过屏幕高度时怎么办?

让内容可以上下滑动,因此我们需要修改一下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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/main_bg"
    android:fitsSystemWindows="true"
    tools:context=".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:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="城市天气"
            android:textColor="@color/white"
            android:textSize="@dimen/sp_16" />
    </com.google.android.material.appbar.MaterialToolbar>

    <!--滚动视图-->
    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_0"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="0dp">
            <!--天气状况-->
            <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" />
            <!--城市-->
            <TextView
                android:id="@+id/tv_city"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/dp_32"
                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/tv_temp" />
            <!--上一次更新时间-->
            <TextView
                android:id="@+id/tv_update_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/dp_8"
                android:text="上次更新时间:"
                android:textColor="@color/white"
                android:textSize="@dimen/sp_12"
                app:layout_constraintEnd_toEndOf="@+id/tv_city"
                app:layout_constraintStart_toStartOf="@+id/tv_city"
                app:layout_constraintTop_toBottomOf="@+id/tv_city" />
            <!--天气预报列表-->
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rv_daily"
                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/tv_update_time" />
            <!--生活建议-->
            <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="@dimen/dp_16"
                android:layout_marginTop="@dimen/dp_16"
                android:text="生活建议"
                android:textColor="@color/white"
                android:textSize="@dimen/sp_18"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/rv_daily" />
            <!--生活建议列表-->
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rv_lifestyle"
                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/textView" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.core.widget.NestedScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>

可以好好看一下这个布局的嵌套层级。

在这里插入图片描述

下面我们写适配器item不=布局,在layout下新建item_lifestyle_rv.xml布局,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_lifestyle"
    android:layout_width="match_parent"
    android:paddingTop="@dimen/dp_8"
    android:paddingBottom="@dimen/dp_8"
    android:layout_height="wrap_content"
    android:textColor="@color/white"
    android:textSize="@dimen/sp_14" />

这个内容就比较简单了,只有一个TextView,下面我们在adapter包下新建一个LifestyleAdapter类,代码如下所示:

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

    private final List<LifestyleResponse.DailyBean> dailyBeans;

    public LifestyleAdapter(List<LifestyleResponse.DailyBean> dailyBeans) {
        this.dailyBeans = dailyBeans;
    }

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

    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        LifestyleResponse.DailyBean dailyBean = dailyBeans.get(position);
        holder.binding.tvLifestyle.setText(dailyBean.getName() + ":" + dailyBean.getText());
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemLifestyleRvBinding binding;

        public ViewHolder(@NonNull ItemLifestyleRvBinding lifestyleRvBinding) {
            super(lifestyleRvBinding.getRoot());
            binding = lifestyleRvBinding;
        }
    }
}

常规用法,下面就可以去MainActivity中进行数据的渲染了。

九、显示生活指数数据

在MainActivity中增加如下代码:

	private final List<LifestyleResponse.DailyBean> lifestyleList = new ArrayList<>();
    private final LifestyleAdapter lifestyleAdapter = new LifestyleAdapter(lifestyleList);

然后在initView()方法中添加如下代码:

	binding.rvLifestyle.setLayoutManager(new LinearLayoutManager(this));
	binding.rvLifestyle.setAdapter(lifestyleAdapter);

最后在搜索城市的回调中请求生活指数接口。

在这里插入图片描述

然后在onObserveData()方法中添加生活指数接口回调的数据,代码如下所示:

			viewModel.lifestyleResponseMutableLiveData.observe(this, lifestyleResponse -> {
                List<LifestyleResponse.DailyBean> daily = lifestyleResponse.getDaily();
                if (daily != null) {
                    if (lifestyleList.size() > 0) {
                        lifestyleList.clear();
                    }
                    lifestyleList.addAll(daily);
                    lifestyleAdapter.notifyDataSetChanged();
                }
            });

最后我们运行一下:

在这里插入图片描述

OK,这样就写好了。

十、文章源码

欢迎 StarFork

第六篇文章源码地址:GoodWeather-New-6

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

8. 旋转风车

这个时候就要用到自定义View了,这个工具类的代码也并不是我自己写的,而是网络上找的,

① 样式

在模块的res文件夹下的values文件下新建一个styles.xml
在这里插入图片描述
里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--白色风车-->
    <declare-styleable name="WhiteWindmills">
        <attr name="windColor" format="reference|color" />
    </declare-styleable>
</resources>

② 自定义View

然后就是自定义VIew了,
在这里插入图片描述
在模块的com.llw.mvplibrary下面创建一个view的包,包下创建一个名为WhiteWindmills的类。
代码如下:

package com.llw.mvplibrary.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

import com.llw.mvplibrary.R;

import java.lang.ref.WeakReference;

/**
 * 白色风车
 */
public class WhiteWindmills extends View {

    /**
     * 叶片的长度
     */
    private float mBladeRadius;
    /**
     * 风车叶片旋转中心x
     */
    private int mCenterY;
    /**
     * 风车叶片旋转中心y
     */
    private int mCenterX;
    /**
     * 风车旋转中心点圆的半径
     */
    private float mPivotRadius;
    private Paint mPaint = new Paint();
    /**
     * 风车旋转时叶片偏移的角度
     */
    private int mOffsetAngle;
    private Path mPath = new Path();
    /**
     * 风车支柱顶部和底部为了画椭圆的矩形
     */
    private RectF mRect = new RectF();
    /**
     * 控件的宽
     */
    private int mWid;
    /**
     * 控件高
     */
    private int mHei;
    /**
     * 控件颜色
     */
    private int mColor;
    private MsgHandler mHandler = new MsgHandler(this);

    public WhiteWindmills(Context context) {
        this(context, null);
    }

    public WhiteWindmills(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WhiteWindmills(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

    private void initView(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.WhiteWindmills);
        if (array != null) {
            mColor = array.getColor(R.styleable.WhiteWindmills_windColor, Color.WHITE);

            array.recycle();
        }
        //抗锯齿
        mPaint.setAntiAlias(true);
        mPaint.setColor(mColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int heiMeasure = MeasureSpec.getSize(heightMeasureSpec);
        int heiMode = MeasureSpec.getMode(heightMeasureSpec);
        int widMode = MeasureSpec.getMode(widthMeasureSpec);
        int widMeasure = MeasureSpec.getSize(widthMeasureSpec);

        mWid = widMeasure;
        mHei = heiMeasure;
        mCenterY = mWid / 2;
        mCenterX = mWid / 2;

        mPivotRadius = (float) mWid / (float) 40;
        mBladeRadius = mCenterY - 2 * mPivotRadius;

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //画扇叶旋转的中心
        drawPivot(canvas);

        //画扇叶
        drawWindBlade(canvas);

        //画底部支柱
        drawPillar(canvas);
    }

    /**
     * 画风车支点
     *
     * @param canvas
     */
    private void drawPivot(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(mCenterX, mCenterY, mPivotRadius, mPaint);
    }

    /**
     * 画叶片
     *
     * @param canvas
     */
    private void drawWindBlade(Canvas canvas) {
        canvas.save();
        mPath.reset();
        //根据偏移量画初始时画布的位置
        canvas.rotate(mOffsetAngle, mCenterX, mCenterY);
        //画三角形扇叶
        mPath.moveTo(mCenterX, mCenterY - mPivotRadius);// 此点为多边形的起点
        mPath.lineTo(mCenterX, mCenterY - mPivotRadius - mBladeRadius);
        mPath.lineTo(mCenterX + mPivotRadius, mPivotRadius + mBladeRadius * (float) 2 / (float) 3);
        mPath.close(); // 使这些点构成封闭的多边形
        canvas.drawPath(mPath, mPaint);

        //旋转画布120度,画第二个扇叶
        canvas.rotate(120, mCenterX, mCenterY);
        canvas.drawPath(mPath, mPaint);

        //旋转画布120度,画第三个扇叶
        canvas.rotate(120, mCenterX, mCenterY);
        canvas.drawPath(mPath, mPaint);
        canvas.restore();
    }

    /**
     * 画支柱
     *
     * @param canvas
     */
    private void drawPillar(Canvas canvas) {
        mPath.reset();
        //画上下半圆之间的柱形
        mPath.moveTo(mCenterX - mPivotRadius / 2, mCenterY + mPivotRadius + mPivotRadius / 2);
        mPath.lineTo(mCenterX + mPivotRadius / 2, mCenterY + mPivotRadius + mPivotRadius / 2);
        mPath.lineTo(mCenterX + mPivotRadius, mHei - 2 * mPivotRadius);
        mPath.lineTo(mCenterX - mPivotRadius, mHei - 2 * mPivotRadius);
        mPath.close();

        //画顶部半圆
        mRect.set(mCenterX - mPivotRadius / 2, mCenterY + mPivotRadius, mCenterX + mPivotRadius / 2, mCenterY + 2 * mPivotRadius);
        mPath.addArc(mRect, 180, 180);
        //画底部半圆
        mRect.set(mCenterX - mPivotRadius, mHei - 3 * mPivotRadius, mCenterX + mPivotRadius, mHei - mPivotRadius);
        mPath.addArc(mRect, 0, 180);

        canvas.drawPath(mPath, mPaint);
    }

    /**
     * 开始旋转
     */
    public void startRotate() {
        stop();
        mHandler.sendEmptyMessageDelayed(0, 10);
    }

    /**
     * 停止旋转
     */
    public void stop() {
        mHandler.removeMessages(0);
    }

    static class MsgHandler extends Handler {
        private WeakReference<WhiteWindmills> mView;

        MsgHandler(WhiteWindmills view) {
            mView = new WeakReference<WhiteWindmills>(view);
        }

        @Override
        public void handleMessage(Message msg) {
            WhiteWindmills view = mView.get();
            if (view != null) {
                view.handleMessage(msg);
            }
        }
    }

    private void handleMessage(Message msg) {
        if (mOffsetAngle >= 0 && mOffsetAngle < 360) {
            mOffsetAngle = mOffsetAngle + 1;
        } else {
            mOffsetAngle = 1;
        }
        invalidate();
        startRotate();
    }
}

这个部分完成之后,修改布局,将这一块加进去。

③ 使用与运行显示

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

					<!--风力展示-->
                    <LinearLayout
                        android:orientation="horizontal"
                        android:padding="20dp"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content">
                        <RelativeLayout
                            android:id="@+id/rl_wind"
                            android:layout_width="130dp"
                            android:layout_height="120dp">
                            <!--大风车-->
                            <com.llw.mvplibrary.view.WhiteWindmills
                                android:id="@+id/ww_big"
                                android:layout_width="100dp"
                                android:layout_height="120dp" />
                            <!--小风车-->
                            <com.llw.mvplibrary.view.WhiteWindmills
                                android:id="@+id/ww_small"
                                android:layout_width="50dp"
                                android:layout_height="60dp"
                                android:layout_alignParentBottom="true"
                                android:layout_alignParentRight="true"
                                />
                        </RelativeLayout>

                        <LinearLayout
                            android:gravity="center"
                            android:orientation="vertical"
                            android:layout_weight="1"
                            android:layout_width="0dp"
                            android:layout_height="match_parent">
                            <!--风向-->
                            <TextView
                                android:id="@+id/tv_wind_direction"
                                android:textColor="#FFF"
                                android:textSize="@dimen/sp_14"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"/>
                            <!--风力-->
                            <TextView
                                android:layout_marginTop="20dp"
                                android:id="@+id/tv_wind_power"
                                android:textColor="#FFF"
                                android:textSize="@dimen/sp_14"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"/>
                        </LinearLayout>
                    </LinearLayout>

代码中:
在这里插入图片描述
风力的数据其实在天气数据的返回值就有了,就是第一个接口,接下来修改代码
在这里插入图片描述
代码如下:

			tvWindDirection.setText("风向     " + response.body().getHeWeather6().get(0).getNow().getWind_dir());//风向
            tvWindPower.setText("风力     " + response.body().getHeWeather6().get(0).getNow().getWind_sc() + "级");//风力
            wwBig.startRotate();//大风车开始转动
            wwSmall.startRotate();//小风车开始转动

记得在页面销毁的时候停止这个风车:

	/**
     * 页面销毁时
     */
    @Override
    public void onDestroy() {
        wwBig.stop();//停止大风车
        wwSmall.stop();//停止小风车
        super.onDestroy();
    }

运行一下:

在这里插入图片描述
下一篇将会讲述切换城市

源码地址:GoodWeather
欢迎 StarFork

下一篇:Android 天气APP(七)城市切换 之 城市数据源

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

初学者-Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值