效果图
前言
天气预报光有普通天气总感觉好少了点什么,所以和风天气给补上了,那就是灾害天气的预警,灾害预警API可以获取指定城市的极端天气预警数据。
一、灾害预警
首先当然是从API下手了。测试API地址如下:
https://devapi.heweather.net/v7/warning/now?key=3086e91d66c04ce588a7f538f917c7f4&location=101200106
随便打开一个网页,复制进去回车你就能拿到返回数据,还是比较简单的。
1.数据实体
通过这些数据生成一个实体bean,在app的bean包下新建一个WarningResponse
代码如下:
package com.llw.goodweather.bean; import java.util.List; public class WarningResponse { /** * code : 200 * updateTime : 2020-08-14T10:10+08:00 * fxLink : http://hfx.link/2fh5 * warning : [{"id":"23062241600000_20200813132751","sender":"肇源县气象局","pubTime":"2020-08-13T13:28+08:00","title":"肇源县气象局发布大风蓝色预警[IV级/一般]","startTime":"2020-08-13T13:30+08:00","endTime":"2020-08-14T13:30+08:00","status":"active","level":"蓝色","type":"11B06","typeName":"大风","text":"肇源县气象台2020年8月13日13时28分发布大风蓝色预警信号:预计未来24小时肇源县受大风影响,平均风力可达5-6级,阵风可达7-8级,请有关单位和个人注意做好预防工作。防御指南:1.政府及相关部门按照职责做好防大风工作;2.关好门窗,加固围板、棚架、广告牌等易被风吹动的搭建物,妥善安置易受大风影响的室外物品,遮盖建筑物资;3.行人注意尽量少骑自行车,刮风时不要在广告牌、临时搭建物等下面逗留;4.有关部门和单位请注意森林、草原防火等防火,个人请注意室外和野外用火安全。 ","related":""}] * refer : {"sources":["12379","Weather China"],"license":["no commercial use"]} */ private String code; private String updateTime; private String fxLink; private ReferBean refer; private List<WarningBean> warning; 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<WarningBean> getWarning() { return warning; } public void setWarning(List<WarningBean> warning) { this.warning = warning; } 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 WarningBean { /** * id : 23062241600000_20200813132751 * sender : 肇源县气象局 * pubTime : 2020-08-13T13:28+08:00 * title : 肇源县气象局发布大风蓝色预警[IV级/一般] * startTime : 2020-08-13T13:30+08:00 * endTime : 2020-08-14T13:30+08:00 * status : active * level : 蓝色 * type : 11B06 * typeName : 大风 * text : 肇源县气象台2020年8月13日13时28分发布大风蓝色预警信号:预计未来24小时肇源县受大风影响,平均风力可达5-6级,阵风可达7-8级,请有关单位和个人注意做好预防工作。防御指南:1.政府及相关部门按照职责做好防大风工作;2.关好门窗,加固围板、棚架、广告牌等易被风吹动的搭建物,妥善安置易受大风影响的室外物品,遮盖建筑物资;3.行人注意尽量少骑自行车,刮风时不要在广告牌、临时搭建物等下面逗留;4.有关部门和单位请注意森林、草原防火等防火,个人请注意室外和野外用火安全。 * related : */ private String id; private String sender; private String pubTime; private String title; private String startTime; private String endTime; private String status; private String level; private String type; private String typeName; private String text; private String related; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getSender() { return sender; } public void setSender(String sender) { this.sender = sender; } public String getPubTime() { return pubTime; } public void setPubTime(String pubTime) { this.pubTime = pubTime; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getStartTime() { return startTime; } public void setStartTime(String startTime) { this.startTime = startTime; } public String getEndTime() { return endTime; } public void setEndTime(String endTime) { this.endTime = endTime; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getLevel() { return level; } public void setLevel(String level) { this.level = level; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getTypeName() { return typeName; } public void setTypeName(String typeName) { this.typeName = typeName; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getRelated() { return related; } public void setRelated(String related) { this.related = related; } } }
2.新增API和方法
打开ApiService
新增了一个api接口,然后进入WeatherContract中,新增请求方法和返回
然后进入到MainActivity中增加
完成到这里,然后做数据的请求,在搜索城市的返回值中获取id,然后写入请求。
3.数据渲染
然后在返回的地方做数据的展示处理就可以了。先进入到activity_main.xml中,增加一个TextView,这个TextView做了一个简单的自定义修改,在mvplibrary下的view包下新建一个MarqueeTextView,然后继承TextView,代码如下:
package com.llw.mvplibrary.view; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.widget.TextView; import androidx.annotation.Nullable; @SuppressLint("AppCompatCustomView") public class MarqueeTextView extends TextView { public MarqueeTextView(Context context) { super(context); } public MarqueeTextView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MarqueeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean isFocused() { return true; } }
然后在布局中使用。
注意我放的位置,是在星期的上面,这个布局也要算在滑动的高度计算里面
<!--灾害预警--> <com.llw.mvplibrary.view.MarqueeTextView android:id="@+id/tv_warn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/dp_16" android:layout_marginEnd="@dimen/dp_16" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:paddingTop="@dimen/dp_4" android:paddingBottom="@dimen/dp_4" android:singleLine="true" android:textColor="@color/white" android:textSize="@dimen/sp_12" />
android:marqueeRepeatLimit="marquee_forever"表示一直滚动
android:singleLine="true"表示单行
android:ellipsize="marquee"这里设置为超出文本后滚动显示
在MainActivity中声明
在返回值中做显示处理
这个灾害预警是存在没有数据的情况,所以没有数据的时候就隐藏掉这个TextView,这个我要说明一下不是所有城市都有灾害预警的,看你的运行了,如果没有数据你得到的返回就是这样的。
{ "code": "200", "updateTime": "2020-08-14T10:59+08:00", "fxLink": "http://hfx.link/1u0z5", "warning": [], "refer": { "sources": [ "12379", "Weather China" ], "license": [ "no commercial use" ] } }
这个我还特地问过和风那边是怎么回事,他们是这样回复我的。
如果你查询的城市预警信息返回为空,则代表这个城市当前没有预警信息。你也可以通过预警城市列表获得当前所有发生预警的城市id。即如果你想知道现在深圳市是否有预警,可以直接访问深圳市的预警信息,返回为空则当前无预警,或者访问预警城市列表,如果深圳城市id不在这个列表中,也代表当前深圳无预警。
他这里提到的预警城市列表,你可以用这个请求地址去获取,
https://devapi.heweather.net/v7/warning/list?range=cn&key=3086e91d66c04ce588a7f538f917c7f4
返回值就是当前有灾害预警的城市id列表,但是这个里面如果没有你当前定位到的城市的id,那你这个城市就没有灾害预警的信息,当然你想看数据的话,可以这样。下面我用这个101200106来演示这些数据。否则不够直观。
先这样赋值,然后你现在就可以运行了。
OK,效果就是上面那样,下面要做这个点击之后打开详情页面
4.灾害预报详情
在app下的ui包中新建一个WarnActivity,页面的布局如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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/img_6" android:fitsSystemWindows="true" android:orientation="vertical" tools:context=".ui.WarnActivity"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:contentInsetLeft="@dimen/dp_16" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" app:navigationIcon="@mipmap/icon_return_white" app:popupTheme="@style/AppTheme.PopupOverlay"> <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_18" /> </androidx.appcompat.widget.Toolbar> <androidx.recyclerview.widget.RecyclerView android:paddingTop="@dimen/dp_10" android:id="@+id/rv" android:overScrollMode="never" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
布局很简单就是一个标题和一个列表。
然后创建item的布局,在layout下新建一个item_warn_list.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/dp_10" android:layout_marginLeft="@dimen/dp_20" android:layout_marginRight="@dimen/dp_20" android:background="@drawable/shape_transparent_12" android:gravity="center_horizontal" android:orientation="vertical" android:padding="@dimen/dp_20"> <TextView android:id="@+id/tv_city" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="城市" android:textColor="@color/white" android:textSize="@dimen/sp_16" /> <TextView android:id="@+id/tv_type_name_and_level" 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_14" /> <TextView android:id="@+id/tv_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_12" android:text="预警详细内容" android:textColor="@color/white" android:textSize="@dimen/sp_14" /> <TextView android:id="@+id/tv_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_8" android:gravity="right" android:text="预警发布时间" android:textColor="@color/white" android:textSize="@dimen/sp_12" /> </LinearLayout>
也是比较的简单。
然后就要建适配器了,但是这个用内部类来解决好了。
在WarnActivity中,写入
/** * 内部适配器 */ public class WarnAdapter extends BaseQuickAdapter<WarningResponse.WarningBean, BaseViewHolder> { public WarnAdapter(int layoutResId, @Nullable List<WarningResponse.WarningBean> data) { super(layoutResId, data); } @RequiresApi(api = Build.VERSION_CODES.O) @Override protected void convert(BaseViewHolder helper, WarningResponse.WarningBean item) { TextView tvTime = helper.getView(R.id.tv_time); String time = DateUtils.updateTime(item.getPubTime()); tvTime.setText("预警发布时间:" + WeatherUtil.showTimeInfo(time) + time); helper.setText(R.id.tv_city, item.getSender())//地区 .setText(R.id.tv_type_name_and_level, item.getTypeName() + item.getLevel() + "预警")//预警类型名称和等级 .setText(R.id.tv_content, item.getText());//预警详情内容 } }
这段代码和onCreate是平级的。现在就要考虑数据的来源了,因为我们是在MainActivity中做请求的,但是详情数据在WarnActivity中显示,所以要先把数据传过来。
回到MainActivity,定义一个全局变量
private String warnBodyString = null;//灾害预警数据字符串
然后在返回值中将实体转换为JSON字符串,
再点击时传递到WarnActivity。
进入到WarnActivity,先继承BaseActivity,然后实现两个构造方法。将布局复制到getLayoutId的返回中,删掉onCreate方法。
然后在initData方法中实例化适配器,并填入数据
@RequiresApi(api = Build.VERSION_CODES.O) @Override public void initData(Bundle savedInstanceState) { StatusBarUtil.transparencyBar(context);//透明状态栏 Back(toolbar); WarningResponse data = new Gson().fromJson(getIntent().getStringExtra("warnBodyString"), WarningResponse.class); WarnAdapter mAdapter = new WarnAdapter(R.layout.item_warn_list,data.getWarning()); rv.setLayoutManager(new LinearLayoutManager(context)); rv.setAdapter(mAdapter); mAdapter.notifyDataSetChanged(); runLayoutAnimation(rv); }
大功告成,运行一下。
完成后别忘了,将之前那个locationId = "101200106"改成
locationId = response.body().getLocation().get(0).getId();//城市Id
二、UI优化
1.主页面布局优化
打开activity_main.xml
请根据这种图来修改。然后看一下ImageView的内容
<ImageView android:id="@+id/bg" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/img_5" android:scaleType="centerCrop" />
回到MainActivity中,修改
改成ImageView。
获取缓存时
必应返回时。然后运行,看起来就会舒服一些。
因为我用的是每日一图,所以就不会是默认的背景。
总结
总的来说还是不错的,我本意还是不希望一篇文章过长,否则会显得很乱,没有条理性,所以这篇文章也就要结束了。如果你有兴趣的话可以继续往下看