赞
踩
上一篇:Android 天气APP(十四)修复UI显示异常、优化业务代码逻辑、增加详情天气显示
在上一篇文章中完成了管理城市的部分功能,添加城市的功能还没有实现,本篇文章中将做添加城市部分功能,我们在选择城市的时候,通常会选择一些比较热门的城市,例如北上广深之类的,那么我们添加城市的时候也可以从热门城市中去添加。
首先我们需要一些推荐城市数据,可以定义一个数组,在Constant中新增如下代码:
/**
* 推荐城市数组
*/
public static final String[] CITY_ARRAY = {"北京", "上海", "广州", "深圳", "天津", "武汉", "长沙", "重庆", "沈阳", "杭州",
"南京", "沈阳", "哈尔滨", "长春", "呼和浩特", "石家庄", "银川", "乌鲁木齐", "呼和浩特", "拉萨", "西宁", "西安", "兰州", "太原",
"昆明", "南宁", "成都", "济南", "南昌", "厦门", "扬州", "岳阳", "赣州", "苏州", "福州", "贵阳", "桂林", "海口", "三亚", "香港",
"澳门", "台北"};
这是一个数组常量,后面添加城市的时候可以从这里面选。
现在数据有了,创建列表item布局,下面在layout下新建一个item_recommend_city.xml,里面的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_recommend_city_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/dp_1"
android:background="@color/white"
android:foreground="?attr/selectableItemBackground"
android:gravity="center"
android:paddingTop="@dimen/dp_16"
android:paddingBottom="@dimen/dp_16"
android:text="推荐城市"
android:textColor="@color/black"
android:textSize="@dimen/sp_16" />
这里就是一个TextView,我们通过布局管理器变成一行三个,后面会看到,现在item的布局有了,下面写适配器。在adapter包下新建一个RecommendCityAdapter 类,代码如下所示:
public class RecommendCityAdapter extends RecyclerView.Adapter<RecommendCityAdapter.ViewHolder> {
private final List<String> cities;
private OnClickItemCallback onClickItemCallback;//视图点击
public RecommendCityAdapter(List<String> cities) {
this.cities = cities;
}
public void setOnClickItemCallback(OnClickItemCallback onClickItemCallback) {
this.onClickItemCallback = onClickItemCallback;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemRecommendCityBinding binding = ItemRecommendCityBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
ViewHolder viewHolder = new ViewHolder(binding);
//添加视图点击事件
binding.getRoot().setOnClickListener(v -> {
if (onClickItemCallback != null) {
onClickItemCallback.onItemClick(viewHolder.getAdapterPosition());
}
});
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.binding.tvRecommendCityName.setText(cities.get(position));
}
@Override
public int getItemCount() {
return cities.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ItemRecommendCityBinding binding;
public ViewHolder(@NonNull ItemRecommendCityBinding itemRecommendCityBinding) {
super(itemRecommendCityBinding.getRoot());
binding = itemRecommendCityBinding;
}
}
}
我的想法是在点击添加城市按钮时,出现一个底部弹窗,这个弹窗中显示推荐城市数据列表,这样就不用多写一个Activity了,下面我们创建弹窗的布局,在layout下新建一个dialog_add_city.xml,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="@color/bg_color"
android:orientation="vertical"
tools:ignore="MissingDefaultResource">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_round_close_24"
app:title="添加城市"
app:titleCentered="true" />
<!--推荐城市列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_recommend_city"
android:layout_width="@dimen/dp_0"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:background="@color/bg_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
下面utils包下创建一个添加城市弹窗的AddCityDialog类,代码如下所示:
public class AddCityDialog {
/**
* 显示弹窗
*/
public static void show(Context context, List<String> cities, SelectedCityCallback selectedCityCallback) {
BottomSheetDialog dialog = new BottomSheetDialog(context);
DialogAddCityBinding binding = DialogAddCityBinding.inflate(LayoutInflater.from(context), null, false);
RecommendCityAdapter recommendCityAdapter = new RecommendCityAdapter(cities);
recommendCityAdapter.setOnClickItemCallback(position -> {
if (selectedCityCallback != null) {
selectedCityCallback.selectedCity(cities.get(position));
dialog.dismiss();
}
});
//网格布局管理器 一行3个
GridLayoutManager manager = new GridLayoutManager(context, 3);
binding.rvRecommendCity.setLayoutManager(manager);
binding.rvRecommendCity.setAdapter(recommendCityAdapter);
binding.toolbar.setNavigationOnClickListener(v -> dialog.dismiss());
dialog.setContentView(binding.getRoot());
dialog.show();
}
public interface SelectedCityCallback {
void selectedCity(String cityName);
}
}
这个弹窗的代码还是很简单的,又不同于之前的代码,看看怎么使用。
修改一下ManageCityActivity的initView()方法中的添加城市按钮点击监听代码,如下所示:
binding.btnAddCity.setOnClickListener(v ->
AddCityDialog.show(ManageCityActivity.this, Arrays.asList(Constant.CITY_ARRAY), cityName -> showMsg(cityName)));
之前是点击之后就提示添加城市,现在点击之后显示一个弹窗,然后在弹窗中选择了城市之后,会提示选择的城市是什么,运行看一下:
现在虽然可以显示数据,可以提示点击的城市了,但是并没有去搜索城市添加,同时也没有添加到我的城市数据库中,所以,下面要做的就是完成这两个操作。
还记得之前保存城市数据是在哪里吗?在主页面定位返回中,所以如果当前页面要保存城市数据,则页面对应的ViewModel需要先有方法才行,在ManageCityViewModel中添加如下方法代码:
/**
* 添加我的城市数据,在定位之后添加数据
*/
public void addMyCityData(String cityName) {
CityRepository.getInstance().addMyCityData(new MyCity(cityName));
}
然后回到之前的点击按钮方法中,修改代码如下所示:
binding.btnAddCity.setOnClickListener(v ->
AddCityDialog.show(ManageCityActivity.this, Arrays.asList(Constant.CITY_ARRAY), cityName -> {
//保存到数据库中
viewModel.addMyCityData(cityName);
}));
这里就在点击的时候调用了保存到数据库的方法,那么搜索天气只需要再后面再加上几行代码即可。
binding.btnAddCity.setOnClickListener(v ->
AddCityDialog.show(ManageCityActivity.this, Arrays.asList(Constant.CITY_ARRAY), cityName -> {
//保存到数据库中
viewModel.addMyCityData(cityName);
//设置页面返回数据
Intent intent = new Intent();
intent.putExtra(Constant.CITY_RESULT, cityName);
setResult(Activity.RESULT_OK, intent);
finish();
}));
你会发现这里新增的代码和之前类似,只不过值一样,如下图所示:
那么可以将里面的参数抽离出来,做一个方法,代码如下:
/**
* 设置页面返回数据
* @param cityName 城市名
*/
private void setPageResult(String cityName) {
Intent intent = new Intent();
intent.putExtra(Constant.CITY_RESULT, cityName);
setResult(Activity.RESULT_OK, intent);
finish();
}
然后刚才的两个地方就调用即可,如下图所示:
运行看一下:
欢迎 Star 和 Fork
第十五篇文章源码地址: GoodWeather-New-15
效果图如下:
看这篇文章之前,你是否是一路看过来的呢?如果你单独看着一篇的话,有些内容你可能看不懂,所以我建议你一篇一篇的看,这是第十五篇文章了,前面还有十四篇,建议先了解一下,传送门:
天气APP-专栏
在我思虑良久之后决定加一个城市的搜索功能,反正有现成的API,不用白不用,我白嫖侠是不放过任何一个可以白嫖的机会。
城市搜索,我是打算新建一个Activity来专门做这个功能,继续在原来的MainActivity中写的话,就太麻烦了,可能自己看着也会觉得太多代码了,不爽,至于新开启一个页面写的话,就会有两个页面的数据交互方面的问题,这也是本文中的重点讲解对象,至于搜索的那些,都是可以轻松实现的,你说呢?闲话少说,言归正传,这自然又需要一个新的API接口了。
还记得ServiceGenerator吗?这里面要新增一个访问地址了。
一目了然吧,为了你们不用自己敲代码,我粘贴一下:
case 2://搜索城市
BASE_URL = "https://search.heweather.net";
break;
然后是创建一个接收返回数据的实体bean,SearchCityResponse
代码如下:
package com.llw.goodweather.bean;
import java.util.List;
public class SearchCityResponse {
private List<HeWeather6Bean> HeWeather6;
public List<HeWeather6Bean> getHeWeather6() {
return HeWeather6;
}
public void setHeWeather6(List<HeWeather6Bean> HeWeather6) {
this.HeWeather6 = HeWeather6;
}
public static class HeWeather6Bean {
/**
* basic : [{"cid":"CN101010100","location":"北京","parent_city":"北京","admin_area":"北京","cnty":"中国","lat":"39.90498734","lon":"116.4052887","tz":"+8.00","type":"city"},{"cid":"CN101132101","location":"北屯","parent_city":"北屯","admin_area":"新疆","cnty":"中国","lat":"47.35317612","lon":"87.82492828","tz":"+8.00","type":"city"},{"cid":"CN101340101","location":"台北","parent_city":"台北","admin_area":"台湾","cnty":"中国","lat":"25.04000092","lon":"121.51599884","tz":"+8.00","type":"city"},{"cid":"CN101221201","location":"淮北","parent_city":"淮北","admin_area":"安徽","cnty":"中国","lat":"33.97170639","lon":"116.79466248","tz":"+8.00","type":"city"},{"cid":"CN101301301","location":"北海","parent_city":"北海","admin_area":"广西","cnty":"中国","lat":"21.4733429","lon":"109.11925507","tz":"+8.00","type":"city"},{"cid":"CN101090303","location":"张北","parent_city":"张家口","admin_area":"河北","cnty":"中国","lat":"41.15171432","lon":"114.71595001","tz":"+8.00","type":"city"},{"cid":"4A570","location":"北雅加达","parent_city":"北雅加达","admin_area":"雅加达","cnty":"印度尼西亚","lat":"-6.18638897","lon":"106.82944489","tz":"+7.00","type":"city"},{"cid":"CN101091106","location":"北戴河","parent_city":"秦皇岛","admin_area":"河北","cnty":"中国","lat":"39.82512283","lon":"119.48628235","tz":"+8.00","type":"city"},{"cid":"34272","location":"Beitou District","parent_city":"台北市","admin_area":"台湾","cnty":"中国","lat":"25.11669922","lon":"121.5","tz":"+8.00","type":"city"},{"cid":"VN1591449","location":"北宁市","parent_city":"北宁市","admin_area":"北宁省","cnty":"越南","lat":"21.18333244","lon":"106.05000305","tz":"+7.00","type":"city"}]
* status : ok
*/
private String status;
private List<BasicBean> basic;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<BasicBean> getBasic() {
return basic;
}
public void setBasic(List<BasicBean> basic) {
this.basic = basic;
}
public static class BasicBean {
/**
* cid : CN101010100
* location : 北京
* parent_city : 北京
* admin_area : 北京
* cnty : 中国
* lat : 39.90498734
* lon : 116.4052887
* tz : +8.00
* type : city
*/
private String cid;
private String location;
private String parent_city;
private String admin_area;
private String cnty;
private String lat;
private String lon;
private String tz;
private String type;
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getParent_city() {
return parent_city;
}
public void setParent_city(String parent_city) {
this.parent_city = parent_city;
}
public String getAdmin_area() {
return admin_area;
}
public void setAdmin_area(String admin_area) {
this.admin_area = admin_area;
}
public String getCnty() {
return cnty;
}
public void setCnty(String cnty) {
this.cnty = cnty;
}
public String getLat() {
return lat;
}
public void setLat(String lat) {
this.lat = lat;
}
public String getLon() {
return lon;
}
public void setLon(String lon) {
this.lon = lon;
}
public String getTz() {
return tz;
}
public void setTz(String tz) {
this.tz = tz;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
}
}
然后是修改ApiService
/**
* 搜索城市
*/
@GET("/find?key=3086e91d66c04ce588a7f538f917c7f4&group=cn&number=10")
Call<SearchCityResponse> searchCity(@Query("location") String location);
记住要用自己的key
SearchCityContract代码如下:
package com.llw.goodweather.contract;
import android.content.Context;
import com.llw.goodweather.api.ApiService;
import com.llw.goodweather.bean.SearchCityResponse;
import com.llw.mvplibrary.base.BasePresenter;
import com.llw.mvplibrary.base.BaseView;
import com.llw.mvplibrary.net.NetCallBack;
import com.llw.mvplibrary.net.ServiceGenerator;
import retrofit2.Call;
import retrofit2.Response;
public class SearchCityContract {
public static class SearchCityPresenter extends BasePresenter<ISearchCityView> {
/**
* 搜索城市
* @param context
* @param location
*/
public void searchCity(final Context context, String location) {
ApiService service = ServiceGenerator.createService(ApiService.class, 2);//指明访问的地址
service.searchCity(location).enqueue(new NetCallBack<SearchCityResponse>() {
@Override
public void onSuccess(Call<SearchCityResponse> call, Response<SearchCityResponse> response) {
if(getView() != null){
getView().getSearchCityResult(response);
}
}
@Override
public void onFailed() {
if(getView() != null){
getView().getDataFailed();
}
}
});
}
}
public interface ISearchCityView extends BaseView {
//查询城市返回数据
void getSearchCityResult(Response<SearchCityResponse> response);
//错误返回
void getDataFailed();
}
}
适配器里面加载数据和布局文件,数据有了,那么就去创建布局
首先要创建一个item, item_search_city_list.xml
布局中用到的图标
布局效果
布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:background="@color/white"
android:gravity="center_vertical"
android:padding="@dimen/dp_12"
android:text="深圳"
android:drawablePadding="@dimen/dp_8"
android:textSize="@dimen/sp_16"
android:textColor="@color/black"
android:id="@+id/tv_city_name"
android:foreground="@drawable/bg_white"
android:drawableRight="@mipmap/icon_open"
android:drawableLeft="@mipmap/icon_item_city"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<View
android:layout_marginLeft="@dimen/dp_12"
android:layout_marginRight="@dimen/dp_12"
android:background="#EEEEEE"
android:layout_width="match_parent"
android:layout_height="1dp"/>
</LinearLayout>
item的布局写完了,接下来创建适配器SearchCityAdapter.java
代码如下:
package com.llw.goodweather.adapter;
import androidx.annotation.Nullable;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.goodweather.R;
import com.llw.goodweather.bean.SearchCityResponse;
import java.util.List;
/**
* 搜索城市结果列表适配器
*/
public class SearchCityAdapter extends BaseQuickAdapter<SearchCityResponse.HeWeather6Bean.BasicBean, BaseViewHolder> {
public SearchCityAdapter(int layoutResId, @Nullable List<SearchCityResponse.HeWeather6Bean.BasicBean> data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, SearchCityResponse.HeWeather6Bean.BasicBean item) {
helper.setText(R.id.tv_city_name, item.getLocation());
helper.addOnClickListener(R.id.tv_city_name);//绑定点击事件
}
}
适配器写完了,下面写搜索页面的布局已经做数据渲染显示出来。
搜索页面也是有两个图标的
icon_search.png
icon_delete.png
还有两个样式文件
cursor_style.xml 修改输入框的光标颜色
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="1dp" />
<solid android:color="#2C2C2C" />
</shape>
shape_gray_bg_14.xml 修改顶部布局的背景样式
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="@dimen/dp_14"/>
<solid android:color="#F2F2F2"/>
</shape>
都是比较简单的,下面终于可以写页面的布局了
布局预览图
布局代码如下:
<?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:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".ui.SearchCityActivity">
<!--头部-->
<androidx.appcompat.widget.Toolbar xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white"
android:elevation="@dimen/dp_6"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@mipmap/icon_return"
app:popupTheme="@style/AppTheme.PopupOverlay">
<!--输入框布局-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_30"
android:layout_marginRight="@dimen/dp_12"
android:layout_weight="1"
android:background="@drawable/shape_gray_bg_14"
android:gravity="center_vertical"
android:paddingLeft="@dimen/dp_12"
android:paddingRight="@dimen/dp_12">
<!--搜索图标-->
<ImageView
android:layout_width="@dimen/dp_16"
android:layout_height="@dimen/dp_16"
android:src="@mipmap/icon_search" />
<!--输入框-->
<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:hint="输入城市关键字"
android:imeOptions="actionSearch"
android:paddingLeft="@dimen/dp_8"
android:paddingRight="@dimen/dp_4"
android:singleLine="true"
android:textColor="@color/black"
android:textCursorDrawable="@drawable/cursor_style"
android:textSize="@dimen/sp_14" />
<!--清除输入的内容-->
<ImageView
android:id="@+id/iv_clear_search"
android:layout_width="@dimen/dp_16"
android:layout_height="@dimen/dp_16"
android:src="@mipmap/icon_delete"
android:visibility="gone" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
<!--搜索结果展示列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
这个输入框我这里有必要讲解一些
**android:imeOptions=“actionSearch”**就是将软键盘的回车改为搜索,这样可以增加用户的体验
接下来是SearchCityActivity.java页面代码的编写
先绑定布局文件中的控件
@BindView(R.id.edit_query)
AutoCompleteTextView editQuery;
@BindView(R.id.iv_clear_search)
ImageView ivClearSearch;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.rv)
RecyclerView rv;
然后定义
List<SearchCityResponse.HeWeather6Bean.BasicBean> mList = new ArrayList<>();//数据源
SearchCityAdapter mAdapter;//适配器
然后会定义五个方法,当然最开始里面是没有方法和处理逻辑,里面的方法都需要自己写,我下面贴的方法里面都是已经写好的
initData
@Override
public void initData(Bundle savedInstanceState) {
StatusBarUtil.setStatusBarColor(context,R.color.white);//白色状态栏
StatusBarUtil.StatusBarLightMode(context);//黑色字体
Back(toolbar);
initResultList();//初始化列表
initEdit();//初始化输入框
}
getLayoutId
@Override
public int getLayoutId() {
return R.layout.activity_search_city;
}
createPresent
@Override
protected SearchCityContract.SearchCityPresenter createPresent() {
return new SearchCityContract.SearchCityPresenter();
}
getSearchCityResult
/**
* 搜索城市返回的结果数据
* @param response
*/
@Override
public void getSearchCityResult(Response<SearchCityResponse> response) {
dismissLoadingDialog();
if (("ok").equals(response.body().getHeWeather6().get(0).getStatus())) {
if (response.body().getHeWeather6().get(0).getBasic().size() > 0) {
mList.clear();
mList.addAll(response.body().getHeWeather6().get(0).getBasic());
mAdapter.notifyDataSetChanged();
runLayoutAnimation(rv);
} else {
ToastUtils.showShortToast(context, "很抱歉,未找到相应的城市");
}
} else {
ToastUtils.showShortToast(context, CodeToStringUtils.WeatherCode(response.body().getHeWeather6().get(0).getStatus()));
}
}
getDataFailed
/**
* 网络请求异常返回提示
*/
@Override
public void getDataFailed() {
dismissLoadingDialog();//关闭弹窗
ToastUtils.showShortToast(context, "网络异常");//这里的context是框架中封装好的,等同于this
}
初始化列表
//初始化列表
private void initResultList() {
mAdapter = new SearchCityAdapter(R.layout.item_search_city_list, mList);
rv.setLayoutManager(new LinearLayoutManager(context));
rv.setAdapter(mAdapter);
mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
ToastUtils.showShortToast(context, "点击了第 " + position + " 个");
}
});
}
初始化输入框
//初始化输入框
private void initEdit() {
editQuery.addTextChangedListener(textWatcher);//添加输入监听
//监听软件键盘搜索按钮
editQuery.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String location = editQuery.getText().toString();
if (!TextUtils.isEmpty(location)) {
showLoadingDialog();
mPresent.searchCity(context, location);
} else {
ToastUtils.showShortToast(context, "请输入搜索关键词");
}
}
return false;
}
});
}
textWatcher
//输入监听
private TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (!s.toString().equals("")) {//输入后,显示清除按钮
ivClearSearch.setVisibility(View.VISIBLE);
} else {//隐藏按钮
ivClearSearch.setVisibility(View.GONE);
}
}
};
点击事件
//点击事件
@OnClick(R.id.iv_clear_search)
public void onViewClicked() {//清除输入框的内容
ivClearSearch.setVisibility(View.GONE);//清除内容隐藏清除按钮
editQuery.setText("");
}
你可以将原来的onCreate()方法给删掉了。然后将我粘贴出来的代码复制进去就OK了,轻松愉快。
对了,还要修改主页面,右上角点击加号,出现的弹窗布局
这个截图应该就一目了然了吧,既然布局改动了,代码自然也要改。
先找到showAddWindow这个方法,在里面增加
那么既然代码写完了,结果怎么样呢?运行看一下效果吧!
以后能用GIF演示的我尽量不用静态图,这样看起来更直观一些,不是吗?
很好,我现在搜索城市地区是已经完成了,但是怎么去查看这个搜索到的城市的天气呢?我的想法是点击下面的某一项的通知将数据传递给MainActivity,同时关闭搜索的这个Activity。这就涉及到两个活动之间的数据传递通讯了,这里不建议你采用startActivity(intent)来跳转MainActivity。这里我们使用EventBus进行页面间的通讯,至于为什么用这个呢?我不告诉你,如果你真想知道,就留言,我再做解释
。
首先是引入依赖库文件
//EventBus
api 'org.greenrobot:eventbus:3.1.1'
改动build.gradle记得Sync一下
其实这个EventBus和广播差不多,不过也有区别,它的用法是,在哪个页面接收消息,就在哪个页面绑定和解绑消息。
下面来运用一下,首先是在项目包下创建一个eventbus包,包下创建一个SearchCityEvent的消息类
代码如下:
package com.llw.goodweather.eventbus;
/**
* 搜索城市消息事件
*/
public class SearchCityEvent {
public final String mLocation;
public final String mCity;
public SearchCityEvent(String location,String city) {
this.mLocation = location;
this.mCity = city;
}
}
然后就是使用了,发送消息方,我们在SearchCityActivity中的item点击的时候发送消息
//发送消息
EventBus.getDefault().post(new SearchCityEvent(mList.get(position).getLocation(),
mList.get(position).getParent_city()));
然后是接收消息方
OK,现在运行一下看看效果吧!
然后我们在这里面放入接口请求
再运行一次
很好,基本功能已经实现了,接下来就是关于这个历史搜索记录的实现了。然后再修改MainActivity中点击跳转到搜索城市页面的代码
SPUtils.putBoolean(Constant.FLAG_OTHER_RETURN, false, context);//缓存标识
首先使我们点击输入框的时候出现上一次输入的文字,可以设置一个默认的值,比如深圳有两个方法,一个是初始化数据,另一个是保存输入到的数据,
首先创建item的布局
item_tv_history.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center_vertical"
android:paddingLeft="@dimen/dp_16"
android:layout_width="match_parent"
android:singleLine="true"
android:ellipsize="marquee"
android:background="@color/white"
android:textSize="@dimen/sp_14"
android:textColor="@color/black"
android:layout_height="@dimen/dp_40">
</TextView>
也比较简单,就一个TextView
然后就会业务代码了,代码如下
/**
* 使 AutoCompleteTextView在一开始获得焦点时自动提示
*
* @param field 保存在sharedPreference中的字段名
* @param autoCompleteTextView 要操作的AutoCompleteTextView
*/
private void initAutoComplete(String field, AutoCompleteTextView autoCompleteTextView) {
SharedPreferences sp = getSharedPreferences("sp_history", 0);
String etHistory = sp.getString("history", "深圳");//获取缓存
String[] histories = etHistory.split(",");//通过,号分割成String数组
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.item_tv_history, histories);
// 只保留最近的50条的记录
if (histories.length > 50) {
String[] newHistories = new String[50];
System.arraycopy(histories, 0, newHistories, 0, 50);
adapter = new ArrayAdapter<String>(this, R.layout.item_tv_history, newHistories);
}
//AutoCompleteTextView可以直接设置数据适配器,并且在获得焦点的时候弹出,
//通常是在用户第一次进入页面的时候,点击输入框输入的时候出现,如果每次都出现
//是会应用用户体验的,这里不推荐这么做
autoCompleteTextView.setAdapter(adapter);
autoCompleteTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
AutoCompleteTextView view = (AutoCompleteTextView) v;
if (hasFocus) {//出现历史输入记录
view.showDropDown();
}
}
});
}
/**
* 把指定AutoCompleteTextView中内容保存到sharedPreference中指定的字符段
* 每次输入完之后调用此方法保存输入的值到缓存里
*
* @param field 保存在sharedPreference中的字段名
* @param autoCompleteTextView 要操作的AutoCompleteTextView
*/
private void saveHistory(String field, AutoCompleteTextView autoCompleteTextView) {
String text = autoCompleteTextView.getText().toString();//输入的值
SharedPreferences sp = getSharedPreferences("sp_history", 0);
String tvHistory = sp.getString(field, "深圳");
if (!tvHistory.contains(text + ",")) {//如果历史缓存中不存在输入的值则
StringBuilder sb = new StringBuilder(tvHistory);
sb.insert(0, text + ",");
sp.edit().putString("history", sb.toString()).commit();//写入缓存
}
}
然后就是使用这两个方法了。
在点击软件盘搜索的时候,进行输入值的保存,然后在initData里面调用初始化方法
那么现在运行一下
OK,下面就要实现另一个功能了,就是搜索记录的动态布局展示,这个地方跟淘宝的那个搜索有点相似,实现这个功能需要自定义一个控件,还有样式,会比较麻烦,请一步一步来看。
这个样式和自定义控件的代码我都会放在mvplibrary下,首先是样式
样式代码:
<!--历史记录-->
<declare-styleable name="TagFlowLayout">
<!--最大选择数量-->
<attr name="max_select" format="integer" />
<!--最大可显示行数-->
<attr name="limit_line_count" format="integer" />
<!--是否设置多行隐藏-->
<attr name="is_limit" format="boolean" />
<attr name="tag_gravity">
<enum name="left" value="-1" />
<enum name="center" value="0" />
<enum name="right" value="1" />
</attr>
</declare-styleable>
接下来是自定义控件,我在view包下又建了一个flowlayout包,这个用于防止自定义控件需要用到的代码,这个代码来源于网络,并不是我自己敲出来的,这里我说明一下,以免造成不必要的麻烦,你只管复制粘贴即可。
FlowLayout
package com.llw.mvplibrary.view.flowlayout;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.LayoutDirection;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.text.TextUtilsCompat;
import com.llw.mvplibrary.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class FlowLayout extends ViewGroup {
private static final String TAG = "FlowLayout";
private static final int LEFT = -1;
private static final int CENTER = 0;
private static final int RIGHT = 1;
private int limitLineCount; //默认显示3行 断词条显示3行,长词条显示2行
private boolean isLimit; //是否有行限制
private boolean isOverFlow; //是否溢出2行
private int mGravity;
protected List<List<View>> mAllViews = new ArrayList<List<View>>();
protected List<Integer> mLineHeight = new ArrayList<Integer>();
protected List<Integer> mLineWidth = new ArrayList<Integer>();
private List<View> lineViews = new ArrayList<>();
public boolean isOverFlow() {
return isOverFlow;
}
private void setOverFlow(boolean overFlow) {
isOverFlow = overFlow;
}
public boolean isLimit() {
return isLimit;
}
public void setLimit(boolean limit) {
if (!limit) {
setOverFlow(false);
}
isLimit = limit;
}
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout);
mGravity = ta.getInt(R.styleable.TagFlowLayout_tag_gravity, LEFT);
limitLineCount = ta.getInt(R.styleable.TagFlowLayout_limit_line_count, 3);
isLimit = ta.getBoolean(R.styleable.TagFlowLayout_is_limit, false);
int layoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault());
if (layoutDirection == LayoutDirection.RTL) {
if (mGravity == LEFT) {
mGravity = RIGHT;
} else {
mGravity = LEFT;
}
}
ta.recycle();
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context) {
this(context, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
// wrap_content
int width = 0;
int height = 0;
int lineWidth = 0;
int lineHeight = 0;
//在每一次换行之后记录,是否超过了行数
int lineCount = 0;//记录当前的行数
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
if (i == cCount - 1) {
if (isLimit) {
if (lineCount == limitLineCount) {
setOverFlow(true);
break;
} else {
setOverFlow(false);
}
}
width = Math.max(lineWidth, width);
height += lineHeight;
lineCount++;
}
continue;
}
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int childWidth = child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin
+ lp.bottomMargin;
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
if (isLimit) {
if (lineCount == limitLineCount) {
setOverFlow(true);
break;
} else {
setOverFlow(false);
}
}
width = Math.max(width, lineWidth);
lineWidth = childWidth;
height += lineHeight;
lineHeight = childHeight;
lineCount++;
} else {
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == cCount - 1) {
if (isLimit) {
if (lineCount == limitLineCount) {
setOverFlow(true);
break;
} else {
setOverFlow(false);
}
}
width = Math.max(lineWidth, width);
height += lineHeight;
lineCount++;
}
}
setMeasuredDimension(
modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeight.clear();
mLineWidth.clear();
lineViews.clear();
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
//如果超过规定的行数则不进行绘制
int lineCount = 0;//记录当前的行数
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) continue;
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) {
if (isLimit) {
if (lineCount == limitLineCount) {
break;
}
}
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
mLineWidth.add(lineWidth);
lineWidth = 0;
lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
lineViews = new ArrayList<View>();
lineCount++;
}
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
+ lp.bottomMargin);
lineViews.add(child);
}
mLineHeight.add(lineHeight);
mLineWidth.add(lineWidth);
mAllViews.add(lineViews);
int left = getPaddingLeft();
int top = getPaddingTop();
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++) {
lineViews = mAllViews.get(i);
lineHeight = mLineHeight.get(i);
// set gravity
int currentLineWidth = this.mLineWidth.get(i);
switch (this.mGravity) {
case LEFT:
left = getPaddingLeft();
break;
case CENTER:
left = (width - currentLineWidth) / 2 + getPaddingLeft();
break;
case RIGHT:
// 适配了rtl,需要补偿一个padding值
left = width - (currentLineWidth + getPaddingLeft()) - getPaddingRight();
// 适配了rtl,需要把lineViews里面的数组倒序排
Collections.reverse(lineViews);
break;
}
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
}
top += lineHeight;
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
}
RecordsDao
package com.llw.mvplibrary.view.flowlayout;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 历史记录搜索操作类
*/
public class RecordsDao {
private final String TABLE_NAME = "records";
private SQLiteDatabase recordsDb;
private RecordSQLiteOpenHelper recordHelper;
private NotifyDataChanged mNotifyDataChanged;
private String mUsername;
public RecordsDao(Context context, String username) {
recordHelper = new RecordSQLiteOpenHelper(context);
mUsername = username;
}
public interface NotifyDataChanged {
void notifyDataChanged();
}
/**
* 设置数据变化监听
*/
public void setNotifyDataChanged(NotifyDataChanged notifyDataChanged) {
mNotifyDataChanged = notifyDataChanged;
}
/**
* 移除数据变化监听
*/
public void removeNotifyDataChanged() {
if (mNotifyDataChanged != null) {
mNotifyDataChanged = null;
}
}
private synchronized SQLiteDatabase getWritableDatabase() {
return recordHelper.getWritableDatabase();
}
private synchronized SQLiteDatabase getReadableDatabase() {
return recordHelper.getReadableDatabase();
}
/**
* 如果考虑操作频繁可以到最后不用数据库时关闭
* <p>
* 关闭数据库
*/
public void closeDatabase() {
if (recordsDb != null) {
recordsDb.close();
}
}
/**
* 添加搜索记录
*
* @param record 记录
*/
public void addRecords(String record) {
//如果这条记录没有则添加,有则更新时间
int recordId = getRecordId(record);
try {
recordsDb = getReadableDatabase();
if (-1 == recordId) {
ContentValues values = new ContentValues();
values.put("username", mUsername);
values.put("keyword", record);
//添加搜索记录
recordsDb.insert(TABLE_NAME, null, values);
} else {
Date d = new Date();
@SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//更新搜索历史数据时间
ContentValues values = new ContentValues();
values.put("time", sdf.format(d));
recordsDb.update(TABLE_NAME, values, "_id = ?", new String[]{Integer.toString(recordId)});
}
if (mNotifyDataChanged != null) {
mNotifyDataChanged.notifyDataChanged();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判断是否含有该搜索记录
*
* @param record 记录
* @return true | false
*/
public boolean isHasRecord(String record) {
boolean isHasRecord = false;
Cursor cursor = null;
try {
recordsDb = getReadableDatabase();
cursor = recordsDb.query(TABLE_NAME, null, "username = ?", new String[]{mUsername}, null, null, null);
while (cursor.moveToNext()) {
if (record.equals(cursor.getString(cursor.getColumnIndexOrThrow("keyword")))) {
isHasRecord = true;
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
if (cursor != null) {
//关闭游标
cursor.close();
}
}
return isHasRecord;
}
/**
* 判断是否含有该搜索记录
*
* @param record 记录
* @return id
*/
public int getRecordId(String record) {
int isHasRecord = -1;
Cursor cursor = null;
try {
recordsDb = getReadableDatabase();
cursor = recordsDb.query(TABLE_NAME, null, "username = ?", new String[]{mUsername}, null, null, null);
while (cursor.moveToNext()) {
if (record.equals(cursor.getString(cursor.getColumnIndexOrThrow("keyword")))) {
isHasRecord = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
if (cursor != null) {
//关闭游标
cursor.close();
}
}
return isHasRecord;
}
/**
* 获取当前用户全部搜索记录
*
* @return 记录集合
*/
public List<String> getRecordsList() {
List<String> recordsList = new ArrayList<>();
Cursor cursor = null;
try {
recordsDb = getReadableDatabase();
cursor = recordsDb.query(TABLE_NAME, null, "username = ?", new String[]{mUsername}, null, null, "time desc");
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndexOrThrow("keyword"));
recordsList.add(name);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
if (cursor != null) {
//关闭游标
cursor.close();
}
}
return recordsList;
}
/**
* 获取指定数量搜索记录
*
* @return 记录集合
*/
public List<String> getRecordsByNumber(int recordNumber) {
List<String> recordsList = new ArrayList<>();
if (recordNumber < 0) {
throw new IllegalArgumentException();
} else if (0 == recordNumber) {
return recordsList;
} else {
Cursor cursor = null;
try {
recordsDb = getReadableDatabase();
cursor = recordsDb.query(TABLE_NAME, null, "username = ?", new String[]{mUsername}, null, null, "time desc limit " + recordNumber);
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndexOrThrow("keyword"));
recordsList.add(name);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
if (cursor != null) {
//关闭游标
cursor.close();
}
}
}
return recordsList;
}
/**
* 模糊查询
*
* @param record 记录
* @return 返回类似记录
*/
public List<String> querySimlarRecord(String record) {
List<String> similarRecords = new ArrayList<>();
Cursor cursor = null;
try {
recordsDb = getReadableDatabase();
cursor = recordsDb.query(TABLE_NAME, null, "username = ? and keyword like '%?%'", new String[]{mUsername, record}, null, null, "order by time desc");
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndexOrThrow("keyword"));
similarRecords.add(name);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
if (cursor != null) {
//关闭游标
cursor.close();
}
}
return similarRecords;
}
/**
* 清除指定用户的搜索记录
*/
public void deleteUsernameAllRecords() {
try {
recordsDb = getWritableDatabase();
recordsDb.delete(TABLE_NAME, "username = ?", new String[]{mUsername});
if (mNotifyDataChanged != null) {
mNotifyDataChanged.notifyDataChanged();
}
} catch (SQLException e) {
e.printStackTrace();
Log.e(TABLE_NAME, "清除所有历史记录失败");
} finally {
}
}
/**
* 清空数据库所有的历史记录
*/
public void deleteAllRecords() {
try {
recordsDb = getWritableDatabase();
recordsDb.execSQL("delete from " + TABLE_NAME);
if (mNotifyDataChanged != null) {
mNotifyDataChanged.notifyDataChanged();
}
} catch (SQLException e) {
e.printStackTrace();
Log.e(TABLE_NAME, "清除所有历史记录失败");
} finally {
}
}
/**
* 通过id删除记录
*
* @param id 记录id
* @return 返回删除id
*/
public int deleteRecord(int id) {
int d = -1;
try {
recordsDb = getWritableDatabase();
d = recordsDb.delete(TABLE_NAME, "_id = ?", new String[]{Integer.toString(id)});
if (mNotifyDataChanged != null) {
mNotifyDataChanged.notifyDataChanged();
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TABLE_NAME, "删除_id:" + id + "历史记录失败");
}
return d;
}
/**
* 通过记录删除记录
*
* @param record 记录
*/
public int deleteRecord(String record) {
int recordId = -1;
try {
recordsDb = getWritableDatabase();
recordId = recordsDb.delete(TABLE_NAME, "username = ? and keyword = ?", new String[]{mUsername, record});
if (mNotifyDataChanged != null) {
mNotifyDataChanged.notifyDataChanged();
}
} catch (SQLException e) {
e.printStackTrace();
Log.e(TABLE_NAME, "清除所有历史记录失败");
}
return recordId;
}
}
RecordSQLiteOpenHelper
package com.llw.mvplibrary.view.flowlayout;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* 数据库帮助类
*/
public class RecordSQLiteOpenHelper extends SQLiteOpenHelper {
private final static String DB_NAME = "search_history.db";
private final static int DB_VERSION = 1;
public RecordSQLiteOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sqlStr = "CREATE TABLE IF NOT EXISTS records (_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, keyword TEXT, time NOT NULL DEFAULT (datetime('now','localtime')));";
db.execSQL(sqlStr);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
TagAdapter
package com.llw.mvplibrary.view.flowlayout;
import android.util.Log;
import android.view.View;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 布局适配器
*/
public abstract class TagAdapter<T> {
private List<T> mTagData;
private OnDataChangedListener mOnDataChangedListener;
@Deprecated
private HashSet<Integer> mCheckedPosList = new HashSet<Integer>();
public TagAdapter(List<T> datas) {
mTagData = datas;
}
public void setData(List<T> datas) {
mTagData = datas;
}
@Deprecated
public TagAdapter(T[] datas) {
mTagData = new ArrayList<T>(Arrays.asList(datas));
}
interface OnDataChangedListener {
void onChanged();
}
void setOnDataChangedListener(OnDataChangedListener listener) {
mOnDataChangedListener = listener;
}
@Deprecated
public void setSelectedList(int... poses) {
Set<Integer> set = new HashSet<>();
for (int pos : poses) {
set.add(pos);
}
setSelectedList(set);
}
@Deprecated
public void setSelectedList(Set<Integer> set) {
mCheckedPosList.clear();
if (set != null) {
mCheckedPosList.addAll(set);
}
notifyDataChanged();
}
@Deprecated
HashSet<Integer> getPreCheckedList() {
return mCheckedPosList;
}
public int getCount() {
return mTagData == null ? 0 : mTagData.size();
}
public void notifyDataChanged() {
if (mOnDataChangedListener != null)
mOnDataChangedListener.onChanged();
}
public T getItem(int position) {
return mTagData.get(position);
}
public abstract View getView(FlowLayout parent, int position, T t);
public void onSelected(int position, View view) {
Log.d("llw", "onSelected " + position);
}
public void unSelected(int position, View view) {
Log.d("llw", "unSelected " + position);
}
public boolean setSelected(int position, T t) {
return false;
}
}
TagFlowLayout
package com.llw.mvplibrary.view.flowlayout;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.llw.mvplibrary.R;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* 自定义控件
*/
public class TagFlowLayout extends FlowLayout
implements TagAdapter.OnDataChangedListener {
private static final String TAG = "TagFlowLayout";
private TagAdapter mTagAdapter;
private int mSelectedMax = -1;//-1为不限制数量
private Set<Integer> mSelectedView = new HashSet<Integer>();
private OnSelectListener mOnSelectListener;
private OnTagClickListener mOnTagClickListener;
private OnLongClickListener mOnLongClickListener;
public interface OnSelectListener {
void onSelected(Set<Integer> selectPosSet);
}
public interface OnTagClickListener {
void onTagClick(View view, int position, FlowLayout parent);
}
public interface OnLongClickListener {
void onLongClick(View view, int position);
}
public TagFlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout);
mSelectedMax = ta.getInt(R.styleable.TagFlowLayout_max_select, -1);
ta.recycle();
}
public TagFlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TagFlowLayout(Context context) {
this(context, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
TagView tagView = (TagView) getChildAt(i);
if (tagView.getVisibility() == View.GONE) {
continue;
}
if (tagView.getTagView().getVisibility() == View.GONE) {
tagView.setVisibility(View.GONE);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void setOnSelectListener(OnSelectListener onSelectListener) {
mOnSelectListener = onSelectListener;
}
public void setOnTagClickListener(OnTagClickListener onTagClickListener) {
mOnTagClickListener = onTagClickListener;
}
public void setOnLongClickListener(OnLongClickListener onLongClickListener) {
mOnLongClickListener = onLongClickListener;
}
public void setAdapter(TagAdapter adapter) {
mTagAdapter = adapter;
mTagAdapter.setOnDataChangedListener(this);
mSelectedView.clear();
changeAdapter();
}
@SuppressWarnings("ResourceType")
private void changeAdapter() {
removeAllViews();
TagAdapter adapter = mTagAdapter;
TagView tagViewContainer = null;
HashSet preCheckedList = mTagAdapter.getPreCheckedList();
for (int i = 0; i < adapter.getCount(); i++) {
View tagView = adapter.getView(this, i, adapter.getItem(i));
tagViewContainer = new TagView(getContext());
tagView.setDuplicateParentStateEnabled(true);
if (tagView.getLayoutParams() != null) {
tagViewContainer.setLayoutParams(tagView.getLayoutParams());
} else {
ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
lp.setMargins(dip2px(getContext(), 5),
dip2px(getContext(), 5),
dip2px(getContext(), 5),
dip2px(getContext(), 5));
tagViewContainer.setLayoutParams(lp);
}
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
tagView.setLayoutParams(lp);
tagViewContainer.addView(tagView);
addView(tagViewContainer);
if (preCheckedList.contains(i)) {
setChildChecked(i, tagViewContainer);
}
if (mTagAdapter.setSelected(i, adapter.getItem(i))) {
setChildChecked(i, tagViewContainer);
}
tagView.setClickable(false);
final TagView finalTagViewContainer = tagViewContainer;
final int position = i;
tagViewContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSelect(finalTagViewContainer, position);
if (mOnTagClickListener != null) {
mOnTagClickListener.onTagClick(finalTagViewContainer, position,
TagFlowLayout.this);
}
}
});
tagViewContainer.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnLongClickListener != null) {
mOnLongClickListener.onLongClick(finalTagViewContainer, position);
//消费事件,不让事件继续下去
return true;
}
return false;
}
});
}
mSelectedView.addAll(preCheckedList);
}
public void setMaxSelectCount(int count) {
if (mSelectedView.size() > count) {
Log.w(TAG, "you has already select more than " + count + " views , so it will be clear .");
mSelectedView.clear();
}
mSelectedMax = count;
}
public Set<Integer> getSelectedList() {
return new HashSet<Integer>(mSelectedView);
}
private void setChildChecked(int position, TagView view) {
view.setChecked(true);
mTagAdapter.onSelected(position, view.getTagView());
}
private void setChildUnChecked(int position, TagView view) {
view.setChecked(false);
mTagAdapter.unSelected(position, view.getTagView());
}
private void doSelect(TagView child, int position) {
if (!child.isChecked()) {
//处理max_select=1的情况
if (mSelectedMax == 1 && mSelectedView.size() == 1) {
Iterator<Integer> iterator = mSelectedView.iterator();
Integer preIndex = iterator.next();
TagView pre = (TagView) getChildAt(preIndex);
setChildUnChecked(preIndex, pre);
setChildChecked(position, child);
mSelectedView.remove(preIndex);
mSelectedView.add(position);
} else {
if (mSelectedMax > 0 && mSelectedView.size() >= mSelectedMax) {
return;
}
setChildChecked(position, child);
mSelectedView.add(position);
}
} else {
setChildUnChecked(position, child);
mSelectedView.remove(position);
}
if (mOnSelectListener != null) {
mOnSelectListener.onSelected(new HashSet<Integer>(mSelectedView));
}
}
public TagAdapter getAdapter() {
return mTagAdapter;
}
private static final String KEY_CHOOSE_POS = "key_choose_pos";
private static final String KEY_DEFAULT = "key_default";
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_DEFAULT, super.onSaveInstanceState());
String selectPos = "";
if (mSelectedView.size() > 0) {
for (int key : mSelectedView) {
selectPos += key + "|";
}
selectPos = selectPos.substring(0, selectPos.length() - 1);
}
bundle.putString(KEY_CHOOSE_POS, selectPos);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
String mSelectPos = bundle.getString(KEY_CHOOSE_POS);
if (!TextUtils.isEmpty(mSelectPos)) {
String[] split = mSelectPos.split("\\|");
for (String pos : split) {
int index = Integer.parseInt(pos);
mSelectedView.add(index);
TagView tagView = (TagView) getChildAt(index);
if (tagView != null) {
setChildChecked(index, tagView);
}
}
}
super.onRestoreInstanceState(bundle.getParcelable(KEY_DEFAULT));
return;
}
super.onRestoreInstanceState(state);
}
@Override
public void onChanged() {
mSelectedView.clear();
changeAdapter();
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
TagView
package com.llw.mvplibrary.view.flowlayout;
import android.content.Context;
import android.view.View;
import android.widget.Checkable;
import android.widget.FrameLayout;
/**
* 自定义控件
*/
public class TagView extends FrameLayout implements Checkable {
private boolean isChecked;
private static final int[] CHECK_STATE = new int[]{android.R.attr.state_checked};
public TagView(Context context) {
super(context);
}
public View getTagView() {
return getChildAt(0);
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
int[] states = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(states, CHECK_STATE);
}
return states;
}
/**
* @param checked The new checked state
*/
@Override
public void setChecked(boolean checked) {
if (this.isChecked != checked) {
this.isChecked = checked;
refreshDrawableState();
}
}
/**
* @return The current checked state of the view
*/
@Override
public boolean isChecked() {
return isChecked;
}
/**
* Change the checked state of the view to the inverse of its current state
*/
@Override
public void toggle() {
setChecked(!isChecked);
}
}
现在创建布局和样式
shape_gray_bg_16.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="16dp"/>
<solid android:color="#F8F8F8" />
</shape>
tv_history.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:gravity="center"
android:layout_height="@dimen/dp_30"
android:paddingLeft="@dimen/dp_12"
android:paddingRight="@dimen/dp_12"
android:paddingTop="@dimen/dp_4"
android:paddingBottom="@dimen/dp_4"
android:layout_margin="5dp"
android:background="@drawable/shape_gray_bg_16"
android:singleLine="true"
android:text="搜索历史"
android:textColor="@color/black"
android:textSize="14sp"/>
然后在activity_search_city.xml中增加历史记录布局的代码
布局中用到了两个图标,分别是
icon_bottom.png
icon_delete_history.png
然后是历史搜索的布局代码
<!--历史搜索-->
<LinearLayout
android:id="@+id/ll_history_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_4"
android:layout_marginBottom="@dimen/dp_8"
android:background="@color/white"
android:orientation="vertical"
android:paddingLeft="@dimen/dp_16"
android:paddingTop="@dimen/dp_8"
android:paddingRight="@dimen/dp_16">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="搜索历史"
android:textColor="@color/black"
android:textSize="@dimen/sp_16" />
<ImageView
android:id="@+id/clear_all_records"
android:layout_width="@dimen/dp_24"
android:layout_height="@dimen/dp_24"
android:background="@mipmap/icon_delete_history" />
</LinearLayout>
<com.llw.mvplibrary.view.flowlayout.TagFlowLayout
android:id="@+id/fl_search_records"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/dp_8"
app:is_limit="true"
app:limit_line_count="3"
app:max_select="1" />
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@mipmap/icon_bottom"
android:visibility="gone" />
</LinearLayout>
布局有了,下面就是写代码了,
然后就是在SearchCity中使用了
首先绑定视图
@BindView(R.id.clear_all_records)
ImageView clearAllRecords;//清理所有历史记录
@BindView(R.id.fl_search_records)
TagFlowLayout flSearchRecords;//搜索历史布局
@BindView(R.id.iv_arrow)
ImageView ivArrow;//超过三行就会出现,展开显示更多
@BindView(R.id.ll_history_content)
LinearLayout llHistoryContent;//搜索历史主布局
然后编写代码
我把之前初始化列表数据的代码也放到这个initView里面了,下面我贴一下代码
private void initView() {
//默认账号
String username = "007";
//初始化数据库
mRecordsDao = new RecordsDao(this, username);
initTagFlowLayout();
//创建历史标签适配器
//为标签设置对应的内容
mRecordsAdapter = new TagAdapter<String>(recordList) {
@Override
public View getView(FlowLayout parent, int position, String s) {
TextView tv = (TextView) LayoutInflater.from(context).inflate(R.layout.tv_history,
flSearchRecords, false);
//为标签设置对应的内容
tv.setText(s);
return tv;
}
};
editQuery.addTextChangedListener(textWatcher);//添加输入监听
editQuery.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String location = editQuery.getText().toString();
if (!TextUtils.isEmpty(location)) {
showLoadingDialog();
//添加数据
mRecordsDao.addRecords(location);
mPresent.searchCity(context, location);
//数据保存
saveHistory("history", editQuery);
} else {
ToastUtils.showShortToast(context, "请输入搜索关键词");
}
}
return false;
}
});
flSearchRecords.setAdapter(mRecordsAdapter);
flSearchRecords.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {
@Override
public void onTagClick(View view, int position, FlowLayout parent) {
//清空editText之前的数据
editQuery.setText("");
//将获取到的字符串传到搜索结果界面,点击后搜索对应条目内容
editQuery.setText(recordList.get(position));
editQuery.setSelection(editQuery.length());
}
});
//长按删除某个条目
flSearchRecords.setOnLongClickListener(new TagFlowLayout.OnLongClickListener() {
@Override
public void onLongClick(View view, final int position) {
showDialog("确定要删除该条历史记录?", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//删除某一条记录
mRecordsDao.deleteRecord(recordList.get(position));
initTagFlowLayout();
}
});
}
});
//view加载完成时回调
flSearchRecords.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
boolean isOverFlow = flSearchRecords.isOverFlow();
boolean isLimit = flSearchRecords.isLimit();
if (isLimit && isOverFlow) {
ivArrow.setVisibility(View.VISIBLE);
} else {
ivArrow.setVisibility(View.GONE);
}
}
});
//初始化搜索返回的数据列表
mAdapter = new SearchCityAdapter(R.layout.item_search_city_list, mList);
rv.setLayoutManager(new LinearLayoutManager(context));
rv.setAdapter(mAdapter);
mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
SPUtils.putString(Constant.LOCATION, mList.get(position).getLocation(), context);
//发送消息
EventBus.getDefault().post(new SearchCityEvent(mList.get(position).getLocation(),
mList.get(position).getParent_city()));
finish();
}
});
}
//历史记录布局
private void initTagFlowLayout() {
Observable.create(new ObservableOnSubscribe<List<String>>() {
@Override
public void subscribe(ObservableEmitter<List<String>> emitter) throws Exception {
emitter.onNext(mRecordsDao.getRecordsByNumber(DEFAULT_RECORD_NUMBER));
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<String>>() {
@Override
public void accept(List<String> s) throws Exception {
recordList.clear();
recordList = s;
if (null == recordList || recordList.size() == 0) {
llHistoryContent.setVisibility(View.GONE);
} else {
llHistoryContent.setVisibility(View.VISIBLE);
}
if (mRecordsAdapter != null) {
mRecordsAdapter.setData(recordList);
mRecordsAdapter.notifyDataChanged();
}
}
});
}
这里面还有一个提示弹窗
//提示弹窗 后续我可能会改,因为原生的太丑了
private void showDialog(String dialogTitle, @NonNull DialogInterface.OnClickListener onClickListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(dialogTitle);
builder.setPositiveButton("确定", onClickListener);
builder.setNegativeButton("取消", null);
builder.create().show();
}
当然还有点击事件也要修改
//点击事件
@OnClick({R.id.iv_clear_search,R.id.clear_all_records, R.id.iv_arrow})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.iv_clear_search://清空输入的内容
ivClearSearch.setVisibility(View.GONE);
editQuery.setText("");
break;
case R.id.clear_all_records://清除所有记录
showDialog("确定要删除全部历史记录?", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
flSearchRecords.setLimit(true);
//清除所有数据
mRecordsDao.deleteUsernameAllRecords();
llHistoryContent.setVisibility(View.GONE);
}
});
break;
case R.id.iv_arrow://向下展开
flSearchRecords.setLimit(false);
mRecordsAdapter.notifyDataChanged();
break;
}
}
这个点击事件的代码你可以把原来的点击事件直接覆盖掉,
为了不造成误会,我再粘贴一下SearchCityActivity的代码
package com.llw.goodweather.ui;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.llw.goodweather.R;
import com.llw.goodweather.adapter.SearchCityAdapter;
import com.llw.goodweather.bean.SearchCityResponse;
import com.llw.goodweather.contract.SearchCityContract;
import com.llw.goodweather.eventbus.SearchCityEvent;
import com.llw.goodweather.utils.CodeToStringUtils;
import com.llw.goodweather.utils.Constant;
import com.llw.goodweather.utils.SPUtils;
import com.llw.goodweather.utils.StatusBarUtil;
import com.llw.goodweather.utils.ToastUtils;
import com.llw.mvplibrary.mvp.MvpActivity;
import com.llw.mvplibrary.view.flowlayout.FlowLayout;
import com.llw.mvplibrary.view.flowlayout.RecordsDao;
import com.llw.mvplibrary.view.flowlayout.TagAdapter;
import com.llw.mvplibrary.view.flowlayout.TagFlowLayout;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Response;
import static com.llw.mvplibrary.utils.RecyclerViewAnimation.runLayoutAnimation;
/**
* 搜索城市
*/
public class SearchCityActivity extends MvpActivity<SearchCityContract.SearchCityPresenter>
implements SearchCityContract.ISearchCityView {
@BindView(R.id.edit_query)
AutoCompleteTextView editQuery;//输入框
@BindView(R.id.iv_clear_search)
ImageView ivClearSearch;//清空输入的内容图标
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.rv)
RecyclerView rv;//数据显示列表
@BindView(R.id.clear_all_records)
ImageView clearAllRecords;//清理所有历史记录
@BindView(R.id.fl_search_records)
TagFlowLayout flSearchRecords;//搜索历史布局
@BindView(R.id.iv_arrow)
ImageView ivArrow;//超过三行就会出现,展开显示更多
@BindView(R.id.ll_history_content)
LinearLayout llHistoryContent;//搜索历史主布局
List<SearchCityResponse.HeWeather6Bean.BasicBean> mList = new ArrayList<>();//数据源
SearchCityAdapter mAdapter;//适配器
private RecordsDao mRecordsDao;
//默然展示词条个数
private final int DEFAULT_RECORD_NUMBER = 10;
private List<String> recordList = new ArrayList<>();
private TagAdapter mRecordsAdapter;
private LinearLayout mHistoryContent;
@Override
public void initData(Bundle savedInstanceState) {
StatusBarUtil.setStatusBarColor(context, R.color.white);//白色状态栏
StatusBarUtil.StatusBarLightMode(context);//黑色字体
Back(toolbar);
initView();//初始化页面数据
initAutoComplete("history", editQuery);
}
private void initView() {
//默认账号
String username = "007";
//初始化数据库
mRecordsDao = new RecordsDao(this, username);
initTagFlowLayout();
//创建历史标签适配器
//为标签设置对应的内容
mRecordsAdapter = new TagAdapter<String>(recordList) {
@Override
public View getView(FlowLayout parent, int position, String s) {
TextView tv = (TextView) LayoutInflater.from(context).inflate(R.layout.tv_history,
flSearchRecords, false);
//为标签设置对应的内容
tv.setText(s);
return tv;
}
};
editQuery.addTextChangedListener(textWatcher);//添加输入监听
editQuery.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String location = editQuery.getText().toString();
if (!TextUtils.isEmpty(location)) {
showLoadingDialog();
//添加数据
mRecordsDao.addRecords(location);
mPresent.searchCity(context, location);
//数据保存
saveHistory("history", editQuery);
} else {
ToastUtils.showShortToast(context, "请输入搜索关键词");
}
}
return false;
}
});
flSearchRecords.setAdapter(mRecordsAdapter);
flSearchRecords.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {
@Override
public void onTagClick(View view, int position, FlowLayout parent) {
//清空editText之前的数据
editQuery.setText("");
//将获取到的字符串传到搜索结果界面,点击后搜索对应条目内容
editQuery.setText(recordList.get(position));
editQuery.setSelection(editQuery.length());
}
});
//长按删除某个条目
flSearchRecords.setOnLongClickListener(new TagFlowLayout.OnLongClickListener() {
@Override
public void onLongClick(View view, final int position) {
showDialog("确定要删除该条历史记录?", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//删除某一条记录
mRecordsDao.deleteRecord(recordList.get(position));
initTagFlowLayout();
}
});
}
});
//view加载完成时回调
flSearchRecords.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
boolean isOverFlow = flSearchRecords.isOverFlow();
boolean isLimit = flSearchRecords.isLimit();
if (isLimit && isOverFlow) {
ivArrow.setVisibility(View.VISIBLE);
} else {
ivArrow.setVisibility(View.GONE);
}
}
});
//初始化搜索返回的数据列表
mAdapter = new SearchCityAdapter(R.layout.item_search_city_list, mList);
rv.setLayoutManager(new LinearLayoutManager(context));
rv.setAdapter(mAdapter);
mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
SPUtils.putString(Constant.LOCATION, mList.get(position).getLocation(), context);
//发送消息
EventBus.getDefault().post(new SearchCityEvent(mList.get(position).getLocation(),
mList.get(position).getParent_city()));
finish();
}
});
}
//历史记录布局
private void initTagFlowLayout() {
Observable.create(new ObservableOnSubscribe<List<String>>() {
@Override
public void subscribe(ObservableEmitter<List<String>> emitter) throws Exception {
emitter.onNext(mRecordsDao.getRecordsByNumber(DEFAULT_RECORD_NUMBER));
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<String>>() {
@Override
public void accept(List<String> s) throws Exception {
recordList.clear();
recordList = s;
if (null == recordList || recordList.size() == 0) {
llHistoryContent.setVisibility(View.GONE);
} else {
llHistoryContent.setVisibility(View.VISIBLE);
}
if (mRecordsAdapter != null) {
mRecordsAdapter.setData(recordList);
mRecordsAdapter.notifyDataChanged();
}
}
});
}
/**
* 使 AutoCompleteTextView在一开始获得焦点时自动提示
*
* @param field 保存在sharedPreference中的字段名
* @param autoCompleteTextView 要操作的AutoCompleteTextView
*/
private void initAutoComplete(String field, AutoCompleteTextView autoCompleteTextView) {
SharedPreferences sp = getSharedPreferences("sp_history", 0);
String etHistory = sp.getString("history", "深圳");//获取缓存
String[] histories = etHistory.split(",");//通过,号分割成String数组
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.item_tv_history, histories);
// 只保留最近的50条的记录
if (histories.length > 50) {
String[] newHistories = new String[50];
System.arraycopy(histories, 0, newHistories, 0, 50);
adapter = new ArrayAdapter<String>(this, R.layout.item_tv_history, newHistories);
}
//AutoCompleteTextView可以直接设置数据适配器,并且在获得焦点的时候弹出,
//通常是在用户第一次进入页面的时候,点击输入框输入的时候出现,如果每次都出现
//是会应用用户体验的,这里不推荐这么做
autoCompleteTextView.setAdapter(adapter);
autoCompleteTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
AutoCompleteTextView view = (AutoCompleteTextView) v;
if (hasFocus) {//出现历史输入记录
view.showDropDown();
}
}
});
}
/**
* 把指定AutoCompleteTextView中内容保存到sharedPreference中指定的字符段
* 每次输入完之后调用此方法保存输入的值到缓存里
*
* @param field 保存在sharedPreference中的字段名
* @param autoCompleteTextView 要操作的AutoCompleteTextView
*/
private void saveHistory(String field, AutoCompleteTextView autoCompleteTextView) {
String text = autoCompleteTextView.getText().toString();//输入的值
SharedPreferences sp = getSharedPreferences("sp_history", 0);
String tvHistory = sp.getString(field, "深圳");
if (!tvHistory.contains(text + ",")) {//如果历史缓存中不存在输入的值则
StringBuilder sb = new StringBuilder(tvHistory);
sb.insert(0, text + ",");
sp.edit().putString("history", sb.toString()).commit();//写入缓存
}
}
//提示弹窗 后续我可能会改,因为原生的太丑了
private void showDialog(String dialogTitle, @NonNull DialogInterface.OnClickListener onClickListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(dialogTitle);
builder.setPositiveButton("确定", onClickListener);
builder.setNegativeButton("取消", null);
builder.create().show();
}
@Override
public int getLayoutId() {
return R.layout.activity_search_city;
}
@Override
protected SearchCityContract.SearchCityPresenter createPresent() {
return new SearchCityContract.SearchCityPresenter();
}
//输入监听
private TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (!s.toString().equals("")) {//输入后,显示清除按钮
ivClearSearch.setVisibility(View.VISIBLE);
} else {//隐藏按钮
ivClearSearch.setVisibility(View.GONE);
}
}
};
//点击事件
@OnClick({R.id.iv_clear_search,R.id.clear_all_records, R.id.iv_arrow})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.iv_clear_search://清空输入的内容
ivClearSearch.setVisibility(View.GONE);
editQuery.setText("");
break;
case R.id.clear_all_records://清除所有记录
showDialog("确定要删除全部历史记录?", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
flSearchRecords.setLimit(true);
//清除所有数据
mRecordsDao.deleteUsernameAllRecords();
llHistoryContent.setVisibility(View.GONE);
}
});
break;
case R.id.iv_arrow://向下展开
flSearchRecords.setLimit(false);
mRecordsAdapter.notifyDataChanged();
break;
}
}
/**
* 搜索城市返回的结果数据
*
* @param response
*/
@Override
public void getSearchCityResult(Response<SearchCityResponse> response) {
dismissLoadingDialog();
if (("ok").equals(response.body().getHeWeather6().get(0).getStatus())) {
if (response.body().getHeWeather6().get(0).getBasic().size() > 0) {
mList.clear();
mList.addAll(response.body().getHeWeather6().get(0).getBasic());
mAdapter.notifyDataSetChanged();
runLayoutAnimation(rv);
} else {
ToastUtils.showShortToast(context, "很抱歉,未找到相应的城市");
}
} else {
ToastUtils.showShortToast(context, CodeToStringUtils.WeatherCode(response.body().getHeWeather6().get(0).getStatus()));
}
}
/**
* 网络请求异常返回提示
*/
@Override
public void getDataFailed() {
dismissLoadingDialog();//关闭弹窗
ToastUtils.showShortToast(context, "网络异常");//这里的context是框架中封装好的,等同于this
}
}
最终效果图
源码地址:GoodWeather
欢迎 Star 和 Fork
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。