【Android】模糊搜索与数据处理
【Android】模糊搜索与数据处理
本篇博客主要以根据输入内容动态获取城市为例进行讲解。
获取城市
这一部分主要是根据输入的信息去动态获取城市信息
首先定义了一个名为 NetUtil
的类,主要用于通过 HTTP 请求获取城市信息。
public class NetUtil {
private static final String URL_CITY_DAY = "https://geoapi.qweather.com/v2/city/lookup?";
private static final String API_KEY = "";
public static String doGet(String urlStr) throws IOException {
String result = "";
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(urlStr)
.build();
Response response = client.newCall(request).execute();
result = response.body().string();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
return result;
}
}
public static String getCitys(String name) throws IOException {
String weatherUrl = URL_CITY_DAY + "location=" + name + "&key=" + API_KEY + "&range=cn";
String cityResult = doGet(weatherUrl);
return cityResult;
}
}
URL 定义
URL_WEATHER
是一个常量,定义了用于获取天气信息的 API URL。这个 API 来自“易客天气”平台。URL_CITY_DAY
是另一个常量,定义了查询城市的 API URL。这个 API 来自“和风天气”平台。API_KEY
用于存放和风天气的 API 密钥,但是目前是空的,需要填入实际的 API 密钥才能使用。
doGet
方法
- 这个方法用于发起 HTTP GET 请求。
- 使用了 OkHttp 库来发送请求,
urlStr
是请求的 URL。 - 方法首先创建一个
OkHttpClient
对象,然后构建一个Request
,并使用client.newCall(request).execute()
来执行该请求。 - 请求的响应 (
Response
) 通过response.body().string()
获取响应内容,将其存储到result
变量中。 - 如果在请求过程中发生异常,会捕获
IOException
并抛出一个RuntimeException
。 - 最后,无论是否有异常,都会返回
result
,即请求的响应结果。
getCitys
方法
- 这个方法用于根据城市名称查询城市的相关信息。
- 首先,它将城市名
name
通过 URL 参数形式与 API 密钥和range=cn
参数一起拼接成完整的 API URL,目的是查询中国范围内的城市。 - 然后,它调用
doGet
方法来发送请求,并获取请求的响应内容cityResult
。 - 最后,记录获取到的响应数据,并返回查询结果
cityResult
。
下面我们要在MainActivity中去实现根据输入信息发起网络请求,并通过异步线程获取该输入文字相关的城市数据:
private void fetchCitys(String city) {
if (city == null || city.trim().isEmpty()) {
return; // 如果输入为空,不进行网络请求
}
new Thread(() -> {
try {
String weatherOfCity = NetUtil.getCitys(city);
if (weatherOfCity != null) {
Message message = Message.obtain();
message.what = 0;
message.obj = weatherOfCity;
mHandler.sendMessage(message); // 将获取到的城市数据通过Handler发送到主线程
} else {
runOnUiThread(() -> Toast.makeText(SearchForCitysActivity.this, "未能获取到城市数据", Toast.LENGTH_SHORT).show());
}
} catch (IOException e) {
e.printStackTrace();
runOnUiThread(() -> Toast.makeText(SearchForCitysActivity.this, "网络请求失败:" + e.getMessage(), Toast.LENGTH_SHORT).show());
}
}).start();
}
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == 0) {
String city = (String) msg.obj;
if (city == null) {
Toast.makeText(SearchForCitysActivity.this, "网络请求失败!!", Toast.LENGTH_SHORT).show();
return;
}
parseToCityString(city);
cityItemRecyclerView.getAdapter().notifyDataSetChanged();
}
}
};
获取到了 JSON 格式的城市数据,下面我们定义了一个名为 parseToCityString
的方法,负责解析从 API 返回的城市信息 JSON 数据,并将其转换为一个城市信息列表:
private void parseToCityString(String cityJson) {
List<String> cityList = new ArrayList<>();
// 将 JSON 字符串解析为 JsonObject
JsonObject jsonObject = JsonParser.parseString(cityJson).getAsJsonObject();
// 获取 "location" 数组
JsonArray jsonArray = jsonObject.getAsJsonArray("location");
if (jsonArray == null) {
Log.e(TAG, "Location array is null");
runOnUiThread(() -> Toast.makeText(SearchForCitysActivity.this, "未能获取到城市信息", Toast.LENGTH_SHORT).show());
return;
}
// 遍历数组中的每个元素
for (JsonElement jsonElement : jsonArray) {
JsonObject cityObject = jsonElement.getAsJsonObject();
// 获取城市名称和行政区信息
String name = cityObject.get("name").getAsString();
String adm1 = cityObject.get("adm1").getAsString();
String adm2 = cityObject.get("adm2").getAsString();
// 组合为所需的格式
String cityInfo = name + "——" + adm1 + "——" + adm2;
cityList.add(cityInfo);
}
citys = cityList;
runOnUiThread(() -> {
SearchCityItemsAdapter adapter = (SearchCityItemsAdapter) cityItemRecyclerView.getAdapter();
if (adapter != null) {
adapter.updateCityList(citys);
}
});
}
初始化城市列表
- 创建一个空的
cityList
,用于存储解析出来的城市信息字符串。
解析 JSON 字符串
- 使用
JsonParser.parseString
方法将传入的cityJson
字符串转换为JsonObject
。这个对象包含从服务器获取的城市数据。
获取 “location” 数组
- 从
JsonObject
中提取名为"location"
的数组,它包含多个城市的信息。每个元素代表一个城市的数据。
检测数据有效性
- 检查
"location"
数组是否为null
。如果是null
,表示服务器没有返回有效的城市数据。此时通过记录日志和在主线程显示 Toast 提示用户,通知未能获取到城市信息,并结束方法的执行。
解析每个城市的信息
- 遍历
"location"
数组中的每个元素。每个元素都是一个包含城市信息的JsonObject
。 - 从每个城市的
JsonObject
中提取城市名称(name
)、省级行政区(adm1
)和地级行政区(adm2
)。 - 将提取的信息组合成一个字符串,格式为
城市名——省级行政区——地级行政区
,并将其添加到cityList
中。
保存解析结果
- 将生成的
cityList
赋值给类的成员变量citys
,这使得其他部分可以访问到最新的城市数据。
更新 UI
- 使用
runOnUiThread
将解析后的城市信息更新到 UI 中。因为 UI 操作只能在主线程上进行,所以通过这个方法确保在主线程执行更新操作。 - 获取
RecyclerView
的适配器SearchCityItemsAdapter
,并调用适配器的updateCityList(citys)
方法,传入最新的城市列表,更新RecyclerView
中的城市列表显示。
现在我们就把根据输入的信息去搜索相应的城市就实现了,下面我们就要开始进行数据处理了。
数据处理与信息传递
SearchView
下面大致说一下SearchView用法:
SearchView
的主要功能是为用户提供一个搜索输入框,通常用于在应用中实现搜索功能。以下是其核心功能:
- 搜索输入:允许用户输入搜索关键词。
- 实时建议:支持根据用户输入的关键词动态提供搜索建议。
- 语音搜索:可以配置成支持语音输入功能(需要设备支持)。
- 图标化显示:可以在初始状态下以搜索图标的形式显示,用户点击图标时展开为输入框。
- 提交搜索请求:当用户输入完查询后,可以点击提交按钮或键盘上的回车键触发搜索请求。
- 显示占位提示:可以设置
queryHint
来显示输入提示,引导用户输入。
我们在使用的时候,主要使用的是监控输入的信息。
在创建searchView并获取实例后,可以设置监听事件:
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
fetchCitys(newText); // 调用获取城市的方法
return true;
}
});
onQueryTextSubmit(String query) 这个方法在用户提交搜索请求时触发,即用户输入完关键词并按下“提交”键(通常是键盘上的回车键)在这个方法中,你可以处理用户的完整搜索请求,比如开始执行搜索或查询操作。
onQueryTextChange(String newText) 这个方法在用户每次修改搜索框内容时触发,比如用户键入或删除字符时。通常用于实现实时搜索或提供动态建议。
我们在进行模糊搜索的时候就要使用的是onQueryTextChange()
这个方法,这里我们调用了上面的fetchCitys()
去获取相关的城市。
RecyclerView绑定
我们获取了输入信息所对应的城市之后,就需要把相应的信息显示在搜索框下面,我们这里就需要使用到RecyclerView。
当然,我们并不能仅仅只创建一个简单的RecyclerView,因为我们会对想要查询的城市进行点击,所以要设置点击事件,并且还需要把选中的城市返回到Activity方便进行其他操作。
先创建Adapter:
public class SearchCityItemsAdapter extends RecyclerView.Adapter<SearchCityItemsAdapter.SearchCityItemsViewHolder> {
List<String> citys;
private OnItemClickListener onItemClickListener;
// 定义点击事件接口
public interface OnItemClickListener {
void onItemClick(String cityInfo);
}
// 构造函数中传入点击监听器
public SearchCityItemsAdapter(List<String> citys, OnItemClickListener listener) {
this.citys = citys;
this.onItemClickListener = listener;
}
@NonNull
@Override
public SearchCityItemsAdapter.SearchCityItemsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.city_recyclerview_item, parent, false);
return new SearchCityItemsViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SearchCityItemsAdapter.SearchCityItemsViewHolder holder, int position) {
String cityInfo = citys.get(position);
holder.cityItem.setText(cityInfo);
// 设置点击事件
holder.itemView.setOnClickListener(v -> {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(cityInfo);
}
});
}
@Override
public int getItemCount() {
return citys == null ? 0 : citys.size();
}
public class SearchCityItemsViewHolder extends RecyclerView.ViewHolder {
TextView cityItem;
public SearchCityItemsViewHolder(@NonNull View itemView) {
super(itemView);
cityItem = itemView.findViewById(R.id.cityName_item);
}
}
public void updateCityList(List<String> newCityList) {
this.citys = newCityList;
notifyDataSetChanged();
}
}
我们自定义了一个点击事件监听器接口 OnItemClickListener
,并在 RecyclerView.Adapter
中传递了这个监听器。
上面代码我们可以实现点击RecyclerView中的item后,通过 onItemClickListener.onItemClick(cityInfo);
,我们就可以在Activity中获取点击的城市名称。
Activity中实现如下:
cityItemRecyclerView = findViewById(R.id.search_recyclerview);
LinearLayoutManager layoutManager1 = new LinearLayoutManager(this);
cityItemRecyclerView.setLayoutManager(layoutManager1);
// 创建适配器并传入点击事件监听器
SearchCityItemsAdapter adapter = new SearchCityItemsAdapter(citys, new SearchCityItemsAdapter.OnItemClickListener() {
@Override
public void onItemClick(String cityInfo) {
Intent intent1 = new Intent(SearchForCitysActivity.this, AddCityActivity.class);
intent1.putExtra("CityName", cityInfo);
intent1.putStringArrayListExtra("CityNames", (ArrayList<String>) cityNames);
startActivity(intent1);
}
});
cityItemRecyclerView.setAdapter(adapter);
我们在创建RecyclerView的Adapter的时候就创建了点击事件监听器,重写了onItemClick(String cityInfo)
方法,使我们在点击item后可以把城市名称传递给下一个活动。
这样我们就完整实现了模糊搜索与数据处理。
已经到底啦!!