Compose Android开发终极挑战赛: 写一个天气应用
https://juejin.cn/user/3913917127985240
第一期挑战是做一个领养宠物的应用,全球一共有五百份礼品。第一个我参加了,做了一个很简单的应用,只有一个列表和一个详情页面。但是看了 Google 官方发出的别人写的之后,又看了看自己写的,这是个啥。。。。醉了
第二期挑战是做一个倒计时的应用,全球也是一共五百份。看见之后就写了一个,也是非常简单,只有一个输入框和一个显示倒计时用的 Text 。在看了别人发的之后,又看了看自己写的,这是个啥。。。。醉了
第三期挑战是 Google 官方出设计图,开发照着官方给出的图做,全球只有三份礼品。三份?全球?别说全球,就是全国、全市都不容易啊!还得看编写的速度。。。算了算了,果断没参加!自己几斤几两还是有点 b 数的。。。。
第四期挑战是开发一个天气应用,全球只有五份礼品。但和第三期不同的是,这回不比速度,不比速度就好,我没那么快。。。那就搞一搞吧!
地址:这必须有吧,显示在第一行的,光有个天气谁知道是哪的天气,虽然是模拟的,也得像真的是不?
天气:这更得有,天气预报没有天气哪能行!
当前温度:这也是必要的,天气预报应用基本都有这个功能。
空气质量:人们都非常关心的东西,加上吧。
24小时天气:每个小时具体的天气预报,这也得有
未来一周天气:预报嘛,肯定得预报啊
天气基本信息:比如降水概率啊,湿度啊,紫外线啊什么的
val weather: Int = R.string.weather_sunny,
val address: Int = R.string.city_new_york,
val currentTemperature: Int = 0,
val quality: Int = 0,
@DrawableRes val background: Int = R.drawable.home_bg_1,
@DrawableRes val backgroundGif: Int = R.drawable.bg_topgif_2,
val twentyFourHours: List<TwentyFourHour> = arrayListOf(),
val weekWeathers: List<WeekWeather> = arrayListOf(),
val basicWeathers: List<BasicWeather> = arrayListOf()
)
val time: String = "",
@DrawableRes val icon: Int,
val temperature: String
)
data class WeekWeather(
val weekStr: String = "",
@DrawableRes val icon: Int,
val temperature: String = ""
)
data class BasicWeather(
val name: Int,
val value: String = ""
)
<string name="weather_cloudy">多云</string>
<string name="weather_overcast">阴</string>
<string name="weather_small_rain">小雨</string>
<string name="weather_mid_rain">中雨</string>
<string name="weather_big_rain">大雨</string>
<string name="weather_rainstorm">暴雨</string>
<string name="weather_small_snow">小雪</string>
<string name="weather_mid_snow">中雪</string>
<string name="weather_big_snow">大雪</string>
<string name="weather_snowstorm">暴风雪</string>
<string name="weather_foggy">雾</string>
<string name="weather_ice">结冰</string>
<string name="weather_haze">阴霾</string>
@StringRes val weather: Int,
@DrawableRes val icon: Int,
@DrawableRes val background: Int,
@DrawableRes val backgroundGif: Int,
) {
SUNNY(
R.string.weather_sunny,
R.drawable.n_weather_icon_sunny,
R.drawable.home_bg_1,
R.drawable.bg_topgif_10
),
CLOUDY(
R.string.weather_cloudy,
R.drawable.n_weather_icon_cloud,
R.drawable.home_bg_4,
R.drawable.bg_topgif_10
),
OVERCAST(
R.string.weather_overcast,
R.drawable.n_weather_icon_overcast,
R.drawable.home_bg_6,
R.drawable.bg_topgif_10
),
}
private val _weatherLiveData = MutableLiveData<Weather>()
val weatherLiveData: LiveData<Weather> = _weatherLiveData
private fun onWeatherChanged(weather: Weather) {
_weatherLiveData.value = weather
}
}
val random = Random()
val city = cityArray[random.nextInt(5)]
val weatherEnums = WeatherEnum.values()
val weatherEnum = weatherEnums[random.nextInt(14)]
val calendar = Calendar.getInstance()
val hours: Int = calendar.get(Calendar.HOUR)
val twentyFourHours = arrayListOf<TwentyFourHour>()
val weekWeathers = arrayListOf<WeekWeather>()
for (index in hours + 1..24) {
twentyFourHours.add(
TwentyFourHour(
"$index:00",
weatherEnum.icon,
"${random.nextInt(29)}°"
)
)
}
val week = calendar.get(Calendar.DAY_OF_WEEK)
val weekListString = DateUtils.getWeekListString(week = week)
for (index in weekListString.indices) {
val small = random.nextInt(10)
weekWeathers.add(
WeekWeather(
weekListString[index],
getWeatherIcon(random.nextInt(35)), "$small°/${small + 7}°"
)
)
}
val basicWeathers = arrayListOf<BasicWeather>()
basicWeathers.add(BasicWeather(R.string.basic_rain, "${random.nextInt(100)}%"))
basicWeathers.add(BasicWeather(R.string.basic_humidity, "${random.nextInt(100)}%"))
val weather = Weather(
weatherEnum.weather,
address = city,
currentTemperature = random.nextInt(30),
quality = random.nextInt(100),
background = weatherEnum.background,
backgroundGif = weatherEnum.backgroundGif,
twentyFourHours = twentyFourHours,
weekWeathers = weekWeathers,
basicWeathers = basicWeathers
)
onWeatherChanged(weather)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
BarUtils.transparentStatusBar(this)
setContent {
MyTheme {
WeatherPage()
}
}
}
}
fun WeatherPage() {
val refreshingState = remember { mutableStateOf(REFRESH_STOP) }
val weatherPageViewModel: WeatherPageViewModel = viewModel()
val weather by weatherPageViewModel.weatherLiveData.observeAsState(Weather())
var loadState by remember { mutableStateOf(false) }
if (!loadState) {
loadState = true
weatherPageViewModel.getWeather()
}
Surface(color = MaterialTheme.colors.background) {
SwipeToRefreshLayout(
refreshingState = refreshingState.value,
onRefresh = {
refreshingState.value = REFRESH_START
weatherPageViewModel.getWeather()
loadState = true
refreshingState.value = REFRESH_STOP
},
progressIndicator = {
ProgressIndicator()
}
) {
WeatherBackground(weather)
WeatherContent(weather)
}
}
}
refreshingState 就是是否正在刷新的状态,在 onRefresh 开始时设置为 REFRESH_START,刷新完成之后设置为 REFRESH_STOP,默认状态也是 REFRESH_STOP。
weatherPageViewModel 就是上面咱们写的,不过多解释
weather 这个就是将 ViewModel 中的 LiveData 转为 Compose 中支持观察的 State 。
loadState 是记住是否加载过,避免重复加载数据。
fun WeatherBackground(weather: Weather) {
Box {
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(weather.background),
contentDescription = stringResource(id = weather.weather),
contentScale = ContentScale.Crop
)
val context = LocalContext.current
val glide = Glide.with(context)
CompositionLocalProvider(LocalRequestManager provides glide) {
GlideImage(
modifier = Modifier.fillMaxSize(),
data = weather.backgroundGif,
contentDescription = stringResource(id = weather.weather),
contentScale = ContentScale.Crop
)
}
}
}
fun WeatherContent(weather: Weather) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 10.dp)
.verticalScroll(scrollState),
) {
WeatherBasic(weather, scrollState)
WeatherDetails(weather)
WeatherWeek(weather)
WeatherOther(weather)
}
}
fun WeatherBasic(weather: Weather, scrollState: ScrollState) {
val offset = (scrollState.value / 2)
val fontSize = (100f / offset * 70).coerceAtLeast(30f).coerceAtMost(75f).sp
val modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally)
.graphicsLayer { translationY = offset.toFloat() }
val context = LocalContext.current
Text(
modifier = modifier.padding(top = 100.dp, bottom = 5.dp),
text = stringResource(id = weather.address), fontSize = 20.sp,
color = Color.White,
)
AnimatedVisibility(visible = fontSize == 75f.sp) {
Text(
modifier = modifier.padding(top = 5.dp, bottom = 5.dp),
text = "${weather.currentTemperature}°",
fontSize = fontSize,
color = Color.White
)
}
Text(
modifier = modifier.padding(top = 5.dp, bottom = 2.5.dp),
text = stringResource(id = weather.weather), fontSize = 25.sp,
color = Color.White
)
AnimatedVisibility(visible = fontSize == 75f.sp) {
Text(
modifier = modifier.padding(top = 2.5.dp),
text = stringResource(id = R.string.weather_air_quality) + " " + weather.quality,
fontSize = 15.sp,
color = Color.White
)
}
Text(
modifier = Modifier.padding(top = 45.dp, start = 10.dp),
text = DateUtils.getDefaultDate(context, System.currentTimeMillis()),
fontSize = 16.sp,
color = Color.White
)
}
fun WeatherDetails(weather: Weather) {
val twentyFourHours = weather.twentyFourHours
LazyRow(modifier = Modifier.fillMaxWidth()) {
items(twentyFourHours) { twentyFourHour ->
WeatherHour(twentyFourHour)
}
}
}
@Composable
fun WeatherHour(twentyFourHour: TwentyFourHour) {
val modifier = Modifier.padding(top = 9.dp)
Column(modifier = Modifier.width(50.dp), horizontalAlignment = Alignment.CenterHorizontally) {
Text(modifier = modifier, text = twentyFourHour.time, color = Color.White, fontSize = 15.sp)
Image(
modifier = modifier.size(25.dp),
painter = painterResource(id = twentyFourHour.icon),
contentDescription = twentyFourHour.temperature
)
Text(
modifier = modifier,
text = twentyFourHour.temperature,
color = Color.White,
fontSize = 15.sp
)
}
}
fun WeatherWeek(weather: Weather) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 10.dp)
.padding(horizontal = 10.dp)
) {
for (weekWeather in weather.weekWeathers) {
WeatherWeekDetails(weekWeather)
}
}
}
fun WeatherOther(weather: Weather) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 10.dp)
.padding(horizontal = 10.dp)
) {
for (weekWeather in weather.basicWeathers) {
WeatherOtherDetails(weekWeather)
}
}
}
到这里基本上就结束了,就这么点内容,就写出了我自认为挺好看的一个天气应用。大家如果想要代码的话直接去 Github 中看:
https://github.com/zhujiang521/Weather