?简易的安卓天气app(一)——解析Json数据、数据类封装 ?简易的安卓天气app(二)——适配器、每小时数据展示 ?简易的安卓天气app(三)——城市管理、数据库操作 📌简易的安卓天气app(四)——搜索城市、完善页面
需求🏷?
前三篇重要的功能已经实现的差不多了,完成了api获取数据,封装数据,展示数据,和一些数据库操作,接着就是按照自己的意愿,搜索城市,查看此城市天气,并决定将此城市加入数据库操作,方法是尽可能地简化的,本次项目共涉及三个页面之间的跳转,逻辑清晰,条理明朗,后续更多复杂化操作,和更多重复性操作有待探索,本质上都是已有代码的延申。 先获取到全国各个城市的信息,展示在搜索城市页面,方便查找。 实现步骤:
- AutoCompleteTextView输入提示文本框
- 实现读取全部城市展示
- 搜索框搜索指定城市
实现效果:
涉及内容
- AutoCompleteTextView输入提示文本框
- 文件读取,Json数据封装,RecyclerView数据展示
- 根据城市名称刷新天气
项目结构
此文为项目开发第四篇文章,故前面文章已经讲完一部分内容,想了解详细步骤移步页首,每一篇文章都已经给出独立源码,可自行根据需要模拟;;
界面设计
搜索页面设计: 大概就是三层的线性布局: 第一层TextView接受主页面传进来的当前天气的城市名称。 第二层就是一个搜索框,使用到的是AutoCompleteTextView,带有提示信息的输入框,EditView也可以,右边搜索图标设置点击监听事件。 第三层就是一个RecyclerView展示全部城市名称,也可以设置点击事件监听,或者输入框输入,此处作为提示出现,都是可行方案
搜索城市页面输入框代码: 输入框AutoCompleteTextView此次用到的属性:
android:completionThreshold="1" //输入一个字符就给出提示
android:dropDownHorizontalOffset://提示菜单与文本起始的水平间距
android:dropDownHeight://设置提示框的高度,太小可能会遮盖部分提示,不过可以上下滚动显示
android:dropDownWidth://设置提示框的宽度,太小可能会遮盖部分提示
android:imeOptions="actionSearch" //键盘点击搜索事件
<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>
输入城市显示提示文本:
省、城市数据类封装
既然需求中需要输入一个字就给出相关城市提示信息,那么就要有全部城市数据,才能以此为根据提示城市名称,由于获取全国全部城市的API太难找,而且免费版的还有使用上限,所以此处直接根据文件读取,并封装。前几篇文章由于网络请求api封装用到的是Gson第三方工具,也提到了使用JsonObject等封装,所以此文会使用JsonArray,JsonObject来进行Json数据封装。 给出的City.txt文件放在main文件夹下的assets文件夹(res同级)下里面给出的就是全国省份,各省下辖市,以及市下的区和县。 例如:
[{
"pname": "北京市",
"city": [{
"name": "北京市",
"area": [
"东城区",
"西城区",
.........
]
}]
},
{
"pname": "天津市",
"city": [{
"name": "天津市",
"area": [
"和平区",
"河东区",
.........
]
}]
},
{
"pname": "河北省",
"city": [{
"name": "石家庄市",
"area": [
"长安区",
"桥西区",
.........
]
},
{
"name": "唐山市",
"area": [
"路北区",
"路南区",
.........
]
},
{
"name": "秦皇岛市",
"area": [
"海港区",
"山海关区",
.........
]
},
.........
前往下面地址自取(永久有效): 1.百度网盘:City.txt
2.阿里云盘:City.txt
前往json在线解析网站观察此文件结构
- 先观察省,此处就以北京和天津为例,前面文章也提到了观察法,先是一个中括号"[“括住了全部省份,每个省份都是一个Object对象(大括号”{"阔了起来)。
- 再观察市,每个省里面的city属性名表示此省下辖的市,也是"["包裹起来,表示是个数组,里面包含了省下全部市的信息(包括name市名称,area数组:区/县),这里就解析到各个城市,因为套法一样。
接着就是封装(连带着省份也一起封装吧),需要两个数据类,因为只封装到各个省下的城市。 ProvinceBean封装省份名称pname和市city,市是集合List
public class ProvinceBean {
private String pname;
private List<CityBean> city;
}
CityBean封装市名称name和区/县area,县是数组String[]
public class CityBean {
private String name;
private String[] area;
private String tem;
private String updateTime;
Json数据解析
接着就是从文件City.txt读取信息 下面是读取方法
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有空格和换行,这样的格式很正常):然后得到了一个字符串resultCity ,接着就是根据resultCity 进行数据封装
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);
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[]数组。 现在,先在SelectCityActivity.java中定义AutoCompleteTextView; private AutoCompleteTextView query; 然后绑定组件
query = (AutoCompleteTextView) findViewById(R.id.edit_query);
接着就是设置一个ArrayAdapter适配器,里面设置样式为android.R.layout.simple_list_item_1安卓提供的样式,就是简单的白框, 然后传入城市的数组; 在此之前,这个城市数组还得定义好,前面Json数据解析我们已经知道,从City.txt文件已经拿到了全部城市,并成功传值给mCityBeanList; 然后我们把这个List转成String数组,放在适配器ArrayAdapter中;
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查询城市时传入的城市名称不能带市,只能北京,天津,上海,不可北京市,上海市。 然后适配器就可以传参了,如下:
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, cityArray);
query.setAdapter(adapter);
然后添加输入框右面的搜索图标的点击事件,输入框的城市名就传到了主页并显示天气
ivSearch = (ImageView) findViewById(R.id.iv_search);
ivSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String cityName = query.getText().toString();
Intent intent = new Intent(SelectCityActivity.this, MainActivity.class);
intent.putExtra("inputCity", cityName);
setResult(200, intent);
finish();
}
});
城市显示
用到RecyclerView,只要把此类中已经封装好的全部城市集合传进去就行了,前面文章已经讲述过RecyclerView的用法,同时,也可以实现点击item跳转到主页获取天气的操作,前面文章也已经提到,不想过多赘述了;
若是嫌弃城市列表太多,都显示在一个页面还得滑动屏幕一个个找; 那么,二级RecyclerView不妨考虑一下:先显示全部省,点击省时,弹出市; 亦或者Spinner、两个RecyclerView联动,等等方法;(源码已给,自行探索)
>实现上图的效果,用到了左右两个RecyclerView,点击左面,就对应显示有点数据,把数据解析那一步改改就行,省和市完整封装在一起,省不止要serPname了,还要把此省的全部市封装一下provinceBean.setCity(mCityBeanList); 下面是封装部分的源码,都是ArrayList,查询速度快。 为了保证搜索框还有提示功能,重新定义actureCityBeanList,传入集合actureCityBeanList转的数组;;
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);
mCityBeanList.add(cityBean);
actureCityBeanList.add(cityBean);
}
provinceBean.setCity(mCityBeanList);
mProvinceBeanList.add(provinceBean);
}
|