上一篇: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,这样就写好了。
十、文章源码
欢迎 Star 和 Fork
第六篇文章源码地址: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
欢迎 Star 和 Fork