简易的安卓天气app(一)——解析Json数据、数据类封装
简易的安卓天气app(二)——适配器、每小时数据展示
简易的安卓天气app(三)——城市管理、数据库操作
📌简易的安卓天气app(四)——搜索城市、完善页面

需求🏷️

前三篇重要的功能已经实现的差不多了,完成了api获取数据,封装数据,展示数据,和一些数据库操作,接着就是按照自己的意愿,搜索城市,查看此城市天气,并决定将此城市加入数据库操作,方法是尽可能地简化的,本次项目共涉及三个页面之间的跳转,逻辑清晰,条理明朗,后续更多复杂化操作,和更多重复性操作有待探索,本质上都是已有代码的延申。
先获取到全国各个城市的信息,展示在搜索城市页面,方便查找。
实现步骤:

  1. AutoCompleteTextView输入提示文本框
  2. 实现读取全部城市展示
  3. 搜索框搜索指定城市

实现效果:
gif

涉及内容

  • AutoCompleteTextView输入提示文本框
  • 文件读取,Json数据封装,RecyclerView数据展示
  • 根据城市名称刷新天气

项目结构

2此文为项目开发第四篇文章,故前面文章已经讲完一部分内容,想了解详细步骤移步页首,每一篇文章都已经给出独立源码,可自行根据需要模拟;;

界面设计

搜索页面设计:
4大概就是三层的线性布局:
第一层TextView接受主页面传进来的当前天气的城市名称。
第二层就是一个搜索框,使用到的是AutoCompleteTextView,带有提示信息的输入框,EditView也可以,右边搜索图标设置点击监听事件。
第三层就是一个RecyclerView展示全部城市名称,也可以设置点击事件监听,或者输入框输入,此处作为提示出现,都是可行方案

搜索城市页面输入框代码:
输入框AutoCompleteTextView此次用到的属性:

1
2
3
4
5
android:completionThreshold="1" //输入一个字符就给出提示
android:dropDownHorizontalOffset://提示菜单与文本起始的水平间距
android:dropDownHeight://设置提示框的高度,太小可能会遮盖部分提示,不过可以上下滚动显示
android:dropDownWidth://设置提示框的宽度,太小可能会遮盖部分提示
android:imeOptions="actionSearch" //键盘点击搜索事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<LinearLayout
android:layout_width="380dp"
android:layout_height="40dp"
android:gravity="center_vertical"
android:layout_gravity="center"
android:background="@drawable/blackground">
<LinearLayout
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:paddingLeft="12dp"
android:paddingRight="12dp">
<!--输入框-->
<AutoCompleteTextView
android:id="@+id/edit_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@null"
android:completionThreshold="1"
android:dropDownHorizontalOffset="5dp"
android:dropDownWidth="200dp"
android:hint="请输入城市名称"
android:imeOptions="actionSearch"
android:paddingLeft="8dp"
android:paddingRight="4dp"
android:textColor="@color/white"
android:singleLine="true"
android:textSize="18sp" />
<!--搜索图标-->
<ImageView
android:layout_width="26dp"
android:layout_height="26dp"
android:id="@+id/iv_search"
android:src="@mipmap/icon_search" />
</LinearLayout>
</LinearLayout>

输入城市显示提示文本:
5

省、城市数据类封装

既然需求中需要输入一个字就给出相关城市提示信息,那么就要有全部城市数据,才能以此为根据提示城市名称,由于获取全国全部城市的API太难找,而且免费版的还有使用上限,所以此处直接根据文件读取,并封装。前几篇文章由于网络请求api封装用到的是Gson第三方工具,也提到了使用JsonObject等封装,所以此文会使用JsonArray,JsonObject来进行Json数据封装。
给出的City.txt文件放在main文件夹下的assets文件夹(res同级)下6里面给出的就是全国省份,各省下辖市,以及市下的区和县。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[{
"pname": "北京市",
"city": [{
"name": "北京市",
"area": [
"东城区",
"西城区",
.........
]
}]
},
{
"pname": "天津市",
"city": [{
"name": "天津市",
"area": [
"和平区",
"河东区",
.........
]
}]
},
{
"pname": "河北省",
"city": [{
"name": "石家庄市",
"area": [
"长安区",
"桥西区",
.........
]
},
{
"name": "唐山市",
"area": [
"路北区",
"路南区",
.........
]
},
{
"name": "秦皇岛市",
"area": [
"海港区",
"山海关区",
.........
]
},
.........

前往下面地址自取(永久有效):
1.百度网盘:City.txt

  • 提取码:mnmp

2.阿里云盘:City.txt

  • 提取码:su08

前往json在线解析网站观察此文件结构

  1. 先观察省,此处就以北京和天津为例,前面文章也提到了观察法,先是一个中括号”[“括住了全部省份,每个省份都是一个Object对象(大括号”{“阔了起来)。
  2. 再观察市,每个省里面的city属性名表示此省下辖的市,也是”[“包裹起来,表示是个数组,里面包含了省下全部市的信息(包括name市名称,area数组:区/县),这里就解析到各个城市,因为套法一样。
    6接着就是封装(连带着省份也一起封装吧),需要两个数据类,因为只封装到各个省下的城市。
    7
    ProvinceBean封装省份名称pname和市city,市是集合List
1
2
3
4
5
public class ProvinceBean {//省
private String pname;
private List<CityBean> city;
//set、get、toString、构造。。。。。。
}

CityBean封装市名称name和区/县area,县是数组String[]

1
2
3
4
5
6
public class CityBean {
private String name;
private String[] area;//县/区
private String tem;//方便数据库操作
private String updateTime;//方便数据库操作
//set、get、toString、构造。。。。。。

Json数据解析

接着就是从文件City.txt读取信息
下面是读取方法

1
2
3
4
5
6
7
8
9
10
InputStream inputStream = getResources().getAssets().open("City.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String lines = bufferedReader.readLine();
while (lines != null) {
sb.append(lines);
lines = bufferedReader.readLine();
}
String resultCity = sb.toString();
Log.d("SelectCity", "resultCity>>>>>>>>>" + resultCity);

控制台显示(原txt有空格和换行,这样的格式很正常):7然后得到了一个字符串resultCity ,接着就是根据resultCity 进行数据封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
JSONArray ja= new JSONArray(resultCity);
//获取省,封装
for (int i = 0; i < ja.length(); i++) {//这个长度就是省的个数
JSONObject provinceJsonObject = ja.getJSONObject(i);
String provinceName = provinceJsonObject.getString("pname");
ProvinceBean provinceBean = new ProvinceBean();
provinceBean.setPname(provinceName);
mProvinceBeanList.add(provinceBean);
//添加市,也可以用省里封装的List<CityBean>,直接set,以后不免用到CityBean,就1并封装了
JSONArray cityArray = provinceJsonObject.getJSONArray("city");
for (int j = 0; j < cityArray.length(); j++) {
JSONObject cityObj = cityArray.getJSONObject(j);
String cityName = cityObj.getString("name");
CityBean cityBean = new CityBean();
cityBean.setName(cityName);
mCityBeanList.add(cityBean);
}
// 添加区/县待拓展。。。。。。。。。
}
Log.d("SelectCity", "mCityBeanList>>>>>>>>>" + mCityBeanList.toString());

到此省和市的数据就已经封装好了,可以查看控制台,发现area=null,因为用不到此数据,就没有对area进行封装,需要封装直接cityBean.set完事,没有难度。

数据解析封装好,就是设置适配器,既然用到RecyclerView展示城市,那么城市的适配器就得写,毕竟封装好的城市数据不是String数组,是一个集合,为了规范(凑复杂度)直接写适配器吧(List转成String数组就可以不用写适配器,直接用ArrayAdapter)。

CityAdapter适配器:

前面文章提到过,略过过了就,,

指路==>适配器写法:
简易的安卓天气app(二)——适配器、每小时数据展示
根据目录适配器HourWeatherAdapter索引

/**也可以在此适配器添加点击事件,拿到天气,此方法前面文章(根据目录适配器AddCityAdapter索引)也提到过,此处略过/

SelectCityActivity.java

搜索框

搜索城市页面首先我们来设计搜索框输入文字提示框,首先,我们已经在xml布局中运用了AutoCompleteTextView,会自动根据输入的一个字匹配传入的值,有就显示提示,如下,这个弹出提示框其实是可以自定义样式的,这里就用默认了。后续会更新,适配器也是安卓提供的ArrayAdapter,传入的是全国所有市的String[]数组。
8
现在,先在SelectCityActivity.java中定义AutoCompleteTextView;
private AutoCompleteTextView query;
然后绑定组件

1
query = (AutoCompleteTextView) findViewById(R.id.edit_query);

接着就是设置一个ArrayAdapter适配器,里面设置样式为android.R.layout.simple_list_item_1安卓提供的样式,就是简单的白框, 然后传入城市的数组;
在此之前,这个城市数组还得定义好,前面Json数据解析我们已经知道,从City.txt文件已经拿到了全部城市,并成功传值给mCityBeanList
72然后我们把这个List转成String数组,放在适配器ArrayAdapter中;

1
2
3
4
5
String[] cityArray = new String[mCityBeanList.size()];
for (int i = 0; i < mCityBeanList.size(); i++) {
//城市名称定义为一个数组
cityArray[i] = mCityBeanList.get(i).getName().substring(0, mCityBeanList.get(i).getName().length() - 1);
}

这里带了substring方法,主要是由于我们的天气api查询城市时传入的城市名称不能带市,只能北京,天津,上海,不可北京市,上海市。
然后适配器就可以传参了,如下:

1
2
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, cityArray);
query.setAdapter(adapter);//

然后添加输入框右面的搜索图标的点击事件,输入框的城市名就传到了主页并显示天气

1
2
3
4
5
6
7
8
9
10
11
12
13
	ivSearch = (ImageView) findViewById(R.id.iv_search);
ivSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//拿到输入框的值
String cityName = query.getText().toString();
// ToastUtil.showLongToast(SelectCityActivity.this, cityName);
Intent intent = new Intent(SelectCityActivity.this, MainActivity.class);
intent.putExtra("inputCity", cityName);
setResult(200, intent);
finish();
}
});

城市显示

用到RecyclerView,只要把此类中已经封装好的全部城市集合传进去就行了,前面文章已经讲述过RecyclerView的用法,同时,也可以实现点击item跳转到主页获取天气的操作,前面文章也已经提到,不想过多赘述了;

若是嫌弃城市列表太多,都显示在一个页面还得滑动屏幕一个个找;
那么,二级RecyclerView不妨考虑一下:先显示全部省,点击省时,弹出市;
亦或者Spinner、两个RecyclerView联动,等等方法;==(源码已给,自行探索)==

9>实现上图的效果,用到了左右两个RecyclerView,点击左面,就对应显示有点数据,把数据解析那一步改改就行,省和市完整封装在一起,省不止要serPname了,还要把此省的全部市封装一下provinceBean.setCity(mCityBeanList);
下面是封装部分的源码,都是ArrayList,查询速度快。
为了保证搜索框还有提示功能,重新定义actureCityBeanList,传入集合actureCityBeanList转的数组;;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
final JSONArray Data = new JSONArray(resultCity);
//获取省,封装
for (int i = 0; i < Data.length(); i++) {
JSONObject provinceJsonObject = Data.getJSONObject(i);
String provinceName = provinceJsonObject.getString("pname");
ProvinceBean provinceBean = new ProvinceBean();
provinceBean.setPname(provinceName);
mCityBeanList = new ArrayList<>();
//添加市
final JSONArray cityArray = provinceJsonObject.getJSONArray("city");
for (int j = 0; j < cityArray.length(); j++) {
JSONObject cityObj = cityArray.getJSONObject(j);
String cityName = cityObj.getString("name");
CityBean cityBean = new CityBean();
cityBean.setName(cityName);
// mCityNameList.add(cityName);
mCityBeanList.add(cityBean);
actureCityBeanList.add(cityBean);
}
provinceBean.setCity(mCityBeanList);
mProvinceBeanList.add(provinceBean);

// 添加县待拓展。。。。。。。。。
}