首先看一下效果动图:
数据来源是公司的一个api,网址就不贴了,数据格式大概是这样:
数据Bean:
public class BaseBean implements Serializable {
private String Code;
private String Message;
public String getCode() {
return Code;
}
public void setCode(String code) {
Code = code;
}
public String getMessage() {
return Message;
}
public void setMessage(String message) {
Message = message;
}
public boolean isSuccessful() {
return TextUtils.equals("1", getCode());
}
}
public class Areas extends BaseBean {
private Map> Area;
private List HotCities;
private String Version;
public Map> getArea() {
return Area;
}
public void setArea(Map> area) {
Area = area;
}
public List getHotCities() {
return HotCities;
}
public void setHotCities(List hotCities) {
HotCities = hotCities;
}
public String getVersion() {
return Version;
}
public boolean isSucceed() {
return "1".equals(Version);
}
public void setVersion(String version) {
Version = version;
}
}
@Entity(tableName = "city_table")
public class Cities {
/**
* Province : 四川省
* ProvinceId : 23
* City : 阿坝藏族羌族自治州
* CityId : 2362
* District : 阿坝藏族羌族自治州
* DistrictId : 2362
* PinYin : aba
*/
@PrimaryKey(autoGenerate = true)
private int Id;
private int CityId;
private String Province;
private int ProvinceId;
private String City;
private String District;
private int DistrictId;
private String PinYin;
/**
* 忽略该属性
*/
@Ignore
private List mtags;
/**
* 忽略该属性
*/
@Ignore
public int position;
/**
* @param tag 属于哪个字母
* @param tagid -9 代表当前定位城市标题、热门城市标题、 字母组标题
* -8 代表当前定位城市
* -7 热门城市
*/
public static Cities newCities(String tag, int tagid) {
Cities cit = new Cities();
cit.setDistrict(tag);
cit.setId(tagid);
return cit;
}
public static Cities newCities(String tag, int tagid,int position) {
Cities cit = new Cities();
cit.setDistrict(tag);
cit.setId(tagid);
cit.position = position;
return cit;
}
public int getId() {
return Id;
}
public int getViewType() {
int cityid;
switch (Id) {
case -9:
cityid = 0;
break;
case -8:
cityid = 1;
break;
case -7:
cityid = 2;
break;
case -10:
cityid = 4;
break;
default:
cityid = 3;
break;
}
return cityid;
}
public void setId(int id) {
Id = id;
}
public List getMtags() {
return mtags;
}
public void setMtags(List mtags) {
this.mtags = mtags;
}
public String getProvince() {
return Province;
}
public void setProvince(String Province) {
this.Province = Province;
}
public int getProvinceId() {
return ProvinceId;
}
public void setProvinceId(int ProvinceId) {
this.ProvinceId = ProvinceId;
}
public String getCity() {
return City;
}
public void setCity(String City) {
this.City = City;
}
public int getCityId() {
return CityId;
}
public void setCityId(int CityId) {
this.CityId = CityId;
}
public String getDistrict() {
return District;
}
public void setDistrict(String District) {
this.District = District;
}
public int getDistrictId() {
return DistrictId;
}
public void setDistrictId(int DistrictId) {
this.DistrictId = DistrictId;
}
public String getPinYin() {
return PinYin;
}
public void setPinYin(String PinYin) {
this.PinYin = PinYin;
}
}
可以看出在city类中配置了一些属性,主要是为了缓存它,整个的设计框架采用的是ViewModel+LiveData+Room,关于这个框架之前我已经详细写过一篇了,所以,这里不做详述;
实现的主要思路是:
1.使用retrofit+rxjava+okhttp3进行网络请求,
api接口:
public interface CitiesService {
/**
* 选择城市相关的网络请求
*/
@FormUrlEncoded
@POST("api地址")
Observable getCities(@Field("version") String version);
}
网络获取工具
public class NetWork {
private static SparseArray retrofitManagers = new SparseArray<>();
private Retrofit mRetrofit;
private NetWork(int hostType) {
Gson gson = new GsonBuilder()
//配置你的Gson
.setDateFormat("yyyy-MM-dd hh:mm:ss")
.create();
mRetrofit = new Retrofit.Builder()
.client(getFreeClient())
.baseUrl(Constant.getHost(hostType))
.addConverterFactory(ScalarsConverterFactory.create())//直接将数据转换为String
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
private OkHttpClient getFreeClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
X509TrustManager[] trustManager = new X509TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws
CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws
CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManager, new SecureRandom());
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
if (sslSocketFactory != null)
builder.sslSocketFactory(sslSocketFactory);
builder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logging);
builder.connectTimeout(20, TimeUnit.SECONDS);
builder.writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS);
builder.retryOnConnectionFailure(true);
return builder.build();
}
public static NetWork getInstance(int hostType) {
NetWork retrofitManager = retrofitManagers.get(hostType);
if (retrofitManager == null) {
retrofitManager = new NetWork(hostType);
retrofitManagers.put(hostType, retrofitManager);
return retrofitManager;
}
return retrofitManager;
}
/**
* @param service 服务接口
* @return T
*/
public T createService(final Class service) {
return mRetrofit.create(service);
}
}
这里为了避免有多个retrofit生成,造成内存不足,使用了SparyArray去将每一个网络工具实例类存储 ,可以达到复用的效果;
2.数据库存储
BaseDb:数据库配置:
@Database(entities = {Cities.class}, version = 1)
public abstract class BaseDB extends RoomDatabase {
public abstract CityDao cityDao();
private static BaseDB INSTANCE;
private static final Object sLock = new Object();
public static BaseDB getInstance(Context context) {
synchronized (sLock) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
BaseDB.class, "city").allowMainThreadQueries().build();
}
return INSTANCE;
}
}
}
数据库操作类:CityDao
@Dao
public interface CityDao {
@Query("SELECT COUNT(1) from city_table")
int getDBDataCount();
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertCitys(List cities);
@Query("SELECT * from city_table where PinYin like '%' + :searStr +'%' or District like '%'+:searStr+ '%'" )
List getAllCitiesData(String searStr);
}
viewModel类:数据的容器
public class BaseViewModel extends ViewModel {
//Disposable 容器,拿到一个Disposable就执行clear,解除订阅事件
private final CompositeDisposable mDisposable = new CompositeDisposable();
//LiveData封装类,添加/消除数据源
private MutableLiveData mTMutableLiveData = new MutableLiveData<>();
//LiveData的set,post方法,同步,异步设置数据
protected void setValue(T value) {
mTMutableLiveData.setValue(value);
}
protected void postValue(T value) {
mTMutableLiveData.postValue(value);
}
//添加一个Disposable到集合中
protected void add(Disposable disposable){
if (disposable!=null){
mDisposable.add(disposable);
}
}
//获取LiveData
public LiveData getData(){
return mTMutableLiveData;
}
//当Activity被销毁时,取消所有订阅事件
@Override
protected void onCleared() {
super.onCleared();
if(mDisposable!=null){
mDisposable.clear();
}
}
}
当接收到一个Disposable时,就添加到集合中,当Activity被销毁时,就clear,取消所有订阅事件;
城市的viewModel主要有 个方法:
1.从网络加载数据
public void loadNetData(){
add(NetWork.getInstance(1)
.createService(CitiesService.class)
.getCities("5.3.1")
.filter(getPredicateByNet())
.filter(getPredicate())
.map(changeData())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer>() {
@Override
public void accept(List cities) throws Exception {
if (cities!=null&&cities.size()>0){
setValue(cities);
}
}
}));
}
在加载过程中,作类两层过滤:
(1)判断网络地址库版本号是否一致
/**
* 判断网络地址库版本号是否一致
* @return
*/
private Predicate getPredicateByNet() {
return new Predicate() {
@Override
public boolean test(Areas areas) throws Exception {
if (areas!=null&&areas.isSucceed()){
//Todo: 判断版本号
return true;
}
return true;
}
};
}
(2)过滤数据为空的
/**
* 过滤数据为null的
* @return
*/
private Predicate getPredicate() {
return new Predicate() {
@Override
public boolean test(Areas areas) throws Exception {
return areas!=null;
}
};
}
还做了一次数据的转换,将通过网络json封装好的对象,转换为列表页需要的数据,简而言之就是,将由各自字母打头的数据封装到一个集合中,然后排序,这个过程还比较复杂:
/**
* 转换数据
* @return
*/
private Function> changeData() {
return new Function>() {
@Override
public List apply(Areas areas) throws Exception {
//用于存数据库的数据
List allData=new ArrayList<>();
//文字索引对应的position
HashMap alphaIndexer=new HashMap<>();
//文字索引集合
List sections=new ArrayList<>();
//所有城市数据
List cities=new ArrayList<>();
//定位标题的逻辑
cities.add(Cities.newCities("定位城市",-9,0));
sections.add("定");
alphaIndexer.put("定",0);
//定位城市的逻辑
cities.add((Cities.newCities("定位中",-8,0)));
//热门城市的逻辑
List hotCities = areas.getHotCities();
if (hotCities!=null&&hotCities.size()>0){
cities.add(Cities.newCities("热门城市",-9,2));
Cities rmcity = Cities.newCities("热门城市", -7, 2);
sections.add("热");
alphaIndexer.put("热",2);
rmcity.setMtags(hotCities);
cities.add(rmcity);
}
//填充所有城市数据
if (areas!=null){
Map> mCitiess=areas.getArea();
//排序方案,便于之后数据的使用
//存储key值
List keys=new ArrayList<>();
//循环遍历所有城市的key(A,B,C....)
for (Map.Entry> entry:mCitiess.entrySet()){
String key=entry.getKey();//获取key值
sections.add(key);//将所有(A,B,C...)存入sections,(定,热,A,B,...)
keys.add(key);//(A,B,C...)
cities.add(Cities.newCities(key,-9,cities.size()));
List mCitys=entry.getValue();
int position=cities.size();
for (int i=0;i
2.从数据库加载数据:
(1)得到数据库状态:
/**
* 得到数据库数据的状态
*/
private void getSaveDBState() {
//超过50条城市数据代表保存成功
if (cityDao.getDBDataCount()>DBSAVESUCCESSCOUNT){
mSaveDBSuccess.setValue(true);
}else {
mSaveDBSuccess.setValue(false);
}
}
(2)从数据库加载数据:
public void loadLocalData(){
getSaveDBState();
add(selectJsonDatasByKey()
.filter(getPredicateByNet())
.filter(getPredicate())
.map(changeData())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer>() {
@Override
public void accept(List cities) throws Exception {
setValue(cities);
loadNetData();
}
}, new Consumer() {
@Override
public void accept(Throwable throwable) throws Exception {
setValue(null);
loadNetData();
}
}));
}
每次从数据库中取完数据,都再请求一次数据,然后重新存到数据库中,这样可以保证数据库中数据每次都是最新的数据,同时,如果没有网络时,也可以显示出数据;
同时,因为样本数据量过大,可以再本地也保存一份文件,这样就可以保证一定可以取到数据了:
public Observable selectJsonDatasByKey(){
return Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter e) throws Exception {
if (!e.isDisposed()){
String json=null;
if (TextUtils.isEmpty(json)) {
json = AssetsUtil.getFromAssets("tuhucity.data", MyApplication.getContext().getAssets());
}
Areas data = new Gson().fromJson(json, Areas.class);
if (data != null) {
e.onNext(data);
e.onComplete();
} else {
e.onError(new Throwable("数据不存在"));
}
}
}
});
}
数据已经获取到了,接下来就是数据的展示了:
总体页面是这个样子:
主体时使用一个RecycleView,看看他的适配器:
public class ChoiceCityOldAdapter extends RecyclerView.Adapter{
/**
* 标题类型
*/
public static final int TYPE_TITLE=0;
/**
* 定位城市类型
*/
public static final int TYPE_LOCATION=1;
/**
* 热门城市类型
*/
public static final int TYPE_HOT=2;
/**
* 显示城市列表项
*/
public static final int TYPE_ITEM=3;
/**
* 空布局类型
*/
public static final int TYPE_EMPTY=4;
private Context context;
private List data=new ArrayList<>();
private LayoutInflater inflater;
private IClickCityListener iClickCityListener;
private Cities locationData = new Cities();
public ChoiceCityOldAdapter(Context context) {
this.context=context;
inflater=LayoutInflater.from(context);
if (context instanceof IClickCityListener) {
iClickCityListener = (IClickCityListener) context;
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType==TYPE_TITLE){
return new TitleViewHolder(inflater.inflate(R.layout.item_choice_title,parent,false));
}else if (viewType==TYPE_HOT){
return new HotCityViewHolder(context,iClickCityListener,inflater.inflate(R.layout.item_choice_context2,parent,false));
}else if (viewType==TYPE_ITEM){
return new CityViewHolder(inflater.inflate(R.layout.item_choice_context3,parent,false));
}else if (viewType==TYPE_LOCATION){
return new LocationCityViewHolder(inflater.inflate(R.layout.item_choice_context1,parent,false));
}else if (viewType==TYPE_EMPTY){
return new NoDataViewHolder(inflater.inflate(R.layout.item_choice_nodata,parent,false));
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Cities currentdata = data.get(position);
if (holder instanceof TitleViewHolder) {
TitleViewHolder holder1 = (TitleViewHolder) holder;
holder1.bindDataandListener(currentdata.getDistrict());
} else if (holder instanceof LocationCityViewHolder) {
LocationCityViewHolder holder1 = (LocationCityViewHolder) holder;
// 先后顺序问题,定位先完成,或者数据解析先完成
holder1.bindDataandListener(iClickCityListener, TextUtils.isEmpty(locationData.getCity())?currentdata:locationData);
} else if (holder instanceof HotCityViewHolder) {
HotCityViewHolder holder1 = (HotCityViewHolder) holder;
holder1.bindDataandListener(currentdata);
} else if (holder instanceof CityViewHolder) {
CityViewHolder holder1 = (CityViewHolder) holder;
holder1.bindDataandListener(iClickCityListener, currentdata);
} else if (holder instanceof NoDataViewHolder) {
NoDataViewHolder holder1 = (NoDataViewHolder) holder;
holder1.bindDataandListener("true".equals(currentdata.getDistrict()));
}
}
@Override
public int getItemViewType(int position) {
if (position>data.size()){
return -1;
}
return data.get(position).getViewType();
}
@Override
public int getItemCount() {
return data.size();
}
public Object getItem(int position) {
if (data.size() <= position) {
return null;
}
return data.get(position);
}
public List getData(){
return data;
}
public void setLocation(String city, String pr, String district) {
this.locationData.setCity(city);
this.locationData.setProvince(pr);
this.locationData.setDistrict(district);
notifyDataSetChanged();
}
public void setData(List data) {
if (data != null) {
this.data = data;
} else {
this.data.clear();
}
notifyDataSetChanged();
}
public void clear() {
if (data != null) {
data.clear();
notifyDataSetChanged();
}
}
public void addData(List datas) {
if (datas != null) {
data.addAll(datas);
notifyDataSetChanged();
}
}
}
Item分为5种状态:标题,定位城市,热门城市,显示城市的Item,还有一个空布局:
对应的就是5个ViewHolder
TitleViewHolder,HotCityViewHolder,CityViewHolder,LocationCityViewHolder,NoDataViewHolder
ViewHolder都比较简单,根据数据一一填充到控件中就可以了;
这里有一个标题悬浮栏的控制类:主要就是通过监听RecycleView的滑动事件去实现的:
public class SuspendView {
/**
* h1用于保存title分组的实际高度
*/
private int h1 = 0;
/**
* 用于保存当前RecycleView顶部第一个可见的position
*/
private int mCurrentPosition = 0;
/**
* 用于记录,当前悬浮显示的组内容对应的position
*/
private int mCurrentTitlePositioin = 0;
private RecyclerView mRecyclerView;
private TitleViewHolder mTitleViewHolder;
private ChoiceCityOldAdapter mChoiceCityAdapter;
private LinearLayoutManager mLinearLayoutManager;
public SuspendView(RecyclerView recyclerView, TitleViewHolder titleViewHolder, ChoiceCityOldAdapter choiceCityAdapter) {
this.mRecyclerView = recyclerView;
this.mTitleViewHolder = titleViewHolder;
this.mChoiceCityAdapter = choiceCityAdapter;
this.mLinearLayoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
addOnScrollListener();
}
public void addOnScrollListener() {
if (this.mRecyclerView != null) {
this.mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//title分组的实际高度赋值的地方
h1 = mTitleViewHolder.itemView.getHeight();
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(mChoiceCityAdapter.getItemViewType(mCurrentPosition + 1)==-1){
return;
}
//通过第二个可见的position去判断下一个view是否是标题
if (mChoiceCityAdapter.getItemViewType(mCurrentPosition + 1) == ChoiceCityOldAdapter.TYPE_TITLE) {
//如果是标题就获取這个view
View view = mLinearLayoutManager.findViewByPosition(mCurrentPosition + 1);
//如果这个view不为null
if (view != null) {
//判断这个view距离顶部的top是否小于h1(标题的高度)
if (view.getTop() <= h1) {
//主要代码:小于等于h1说明,悬浮的view需要往上滑动(h1-view.top)的距离,就可以实现将view1挤上去的效果
mTitleViewHolder.itemView.setY(-(h1 - view.getTop()));
} else {
//距离不够的情况,判断view1是否有滑动过,如果滑动过就归位。
if (mTitleViewHolder.itemView.getY() != 0f) {
mTitleViewHolder.itemView.setY(0f);
}
}
}
}
if (mCurrentPosition != mLinearLayoutManager.findFirstVisibleItemPosition()) {
//判断当前记录的可见positon是否一致
mCurrentPosition = mLinearLayoutManager.findFirstVisibleItemPosition();
//不一致就重新赋值
if (mTitleViewHolder.itemView.getY() != 0f) {
//同时判断view1是否有滑动过,如果滑动过就归位。
mTitleViewHolder.itemView.setY(0f);
}
if (mCurrentPosition < mChoiceCityAdapter.getData().size() && mCurrentTitlePositioin != (mChoiceCityAdapter.getData()).get(mCurrentPosition).position) {
//用于更新标题的ViewHolder,即滑动之后更新
mCurrentTitlePositioin = mChoiceCityAdapter.getData().get(mCurrentPosition).position;
mTitleViewHolder.bindDataandListenerBySuspendView(mChoiceCityAdapter.getData().get(mCurrentTitlePositioin).getDistrict());
}
}
}
});
}
}
/**
* 绑定数据
*/
public void bindData() {
mTitleViewHolder.itemView.setVisibility(View.VISIBLE);
if (0 < mChoiceCityAdapter.getData().size() && mChoiceCityAdapter.getData().get(0).getViewType() == ChoiceCityOldAdapter.TYPE_TITLE) {
//用于更新标题的ViewHolder,即第一次更新
mTitleViewHolder.bindDataandListenerBySuspendView(mChoiceCityAdapter.getData().get(0).getDistrict());
}
}
}
还有右侧滑动栏,他是一个自定义控件,这个实现起来也比较简单,根据字母数量计算出每个字母的高度,再一个个画上去就好了:
public class LetterListView extends View {
private Context context;
List chars;//字母集合
int choose = -1; //选择位置
Paint paint = new Paint();//画笔
boolean showBkg = false;//是否显示字母框
OnTouchingLetterChangedListener onTouchingLetterChangedListener;
public LetterListView(Context context) {
this(context, null);
}
public LetterListView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LetterListView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
public void setChars(List chars) {
this.chars = chars;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (chars != null && chars.size() > 0) {
if (showBkg) {
canvas.drawColor(Color.parseColor("#00FFFFFF"));
}
int height = getHeight();
int width = getWidth();
int singleHeight = height / chars.size();//每个字母的高度
for (int i = 0; i < chars.size(); i++) {
paint.setColor(Color.parseColor("#4A90E2"));
paint.setTextAlign(Paint.Align.CENTER);
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(DensityUtils.sp2px(context, 11));
if (i == choose) {
paint.setColor(Color.parseColor("#3399ff"));
paint.setFakeBoldText(true);//设置粗体
}
float xPos = width / 2 - paint.measureText(chars.get(i)) / 2;
float yPos = singleHeight * i + singleHeight;
canvas.drawText(chars.get(i), xPos, yPos, paint);
paint.reset();
}
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (chars != null && chars.size() > 0) {
final int action = event.getAction();
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
final int c = (int) (y / getHeight() * chars.size());
switch (action) {
case MotionEvent.ACTION_DOWN:
showBkg = true;
if (oldChoose != c && listener != null) {
if (c >= 0 & c < chars.size()) {
listener.onTouchingLetterChanged(chars.get(c));
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_MOVE:
if (oldChoose != c && listener != null) {
if (c >= 0 && c < chars.size()) {
listener.onTouchingLetterChanged(chars.get(c));
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
showBkg = false;
choose = -1;
invalidate();
break;
}
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
public void setOnTouchingLetterChangedListener(OnTouchingLetterChangedListener listener){
this.onTouchingLetterChangedListener=listener;
}
//触摸到字母的接口
public interface OnTouchingLetterChangedListener {
void onTouchingLetterChanged(String s);
}
}
大致的思路就是这样了,最后贴一下主活动的代码:
public class CityChooseActivity1 extends AppCompatActivity {
public static final int ACCESS_LOCATION = 0;
public final static int LocationOK_Msg = 1;
public final static int LocationError_Msg = 2;
public final static String LOCATION_STATE1 = "定位中";
public final static String LOCATION_STATE2 = "失败";
private Dialog dialog;
@BindView(R.id.img_back)
ImageView img_back;
@BindView(R.id.choicecity_txt_ts)
LinearLayout choicecity_txt_ts;
@BindView(R.id.view_choicecity_topserach)
View view_choicecity_topserach;
@BindView(R.id.choicecity_topsearch_ed)
TextView choicecity_topsearch_ed;
@BindView(R.id.view_choicecity_serach)
View view_choicecity_serach;
@BindView(R.id.choicecity_search_del)
ImageView choicecity_search_del;
@BindView(R.id.choicecity_search_cancel)
TextView choicecity_search_cancel;
@BindView(R.id.choicecity_result)
FrameLayout choicecity_result;
@BindView(R.id.choicecity_layout)
RelativeLayout choicecity_layout;
@BindView(R.id.choicecity_recyclerview)
RecyclerView choicecity_recyclerview;
@BindView(R.id.choicecity_title)
View choicecity_title;
@BindView(R.id.item_choice_text)
TextView item_choice_text;
@BindView(R.id.choicecity_city_letterlistview)
LetterListView choicecity_city_letterlistview;
@BindView(R.id.choicecity_overlay)
TextView choicecity_overlay;
@BindView(R.id.choicecity_srecyclerview)
RecyclerView choicecity_srecyclerview;
@BindView(R.id.choicecity_search_ed)
EditText choicecity_search_ed;
@BindView(R.id.choicecity_close)
LinearLayout choicecity_close;
/**
* 索引相关的集合
*/
private HashMap mStringIntegerHashMap;
/**
* 数据库查询Dao
*/
CityDao cityDao= BaseDB.getInstance(MyApplication.getContext()).cityDao();
/**
* 服务器返回的数据,作适配器
*/
private ChoiceCityOldAdapter mChoiceCityAdapter1;
/**
* 本地搜索的数据,作适配器
*/
private ChoiceCityOldAdapter mChoiceCityAdapter2;
private Handler handler;
private OverlayThread overlayThread;
private Observer mObserver;
/**
* 标题滑动模块
*/
private TitleViewHolder mTitleViewHolder;
private SuspendView mSuspendView;
private MyHandler mHandler = null;
/**
* 右边栏点击抬起后将中间文字1秒后消失
*/
private class OverlayThread implements Runnable {
@Override
public void run() {
choicecity_overlay.setVisibility(View.GONE);
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置状态栏为白色
StatusBarUtil.setColor(this, getResources().getColor(R.color.white));
setContentView(R.layout.activity_choicecity);
ButterKnife.bind(this);
StatusBarUtil.StatusBarLightMode(this);
initView();
initData();
initViewModel();
}
private void initViewModel() {
CitiesViewModel mCitiesViewModel = ViewModelProviders.of(this).get(CitiesViewModel.class);
mCitiesViewModel.getData().observe(this, new android.arch.lifecycle.Observer>() {
@Override
public void onChanged(@Nullable List cities) {
mChoiceCityAdapter1.setData(cities);
mSuspendView.bindData();
}
});
mCitiesViewModel.mAlphaIndexer.observe(this, new android.arch.lifecycle.Observer>() {
@Override
public void onChanged(@Nullable HashMap stringIntegerHashMap) {
mStringIntegerHashMap = stringIntegerHashMap;
}
});
mCitiesViewModel.mSections.observe(this, new android.arch.lifecycle.Observer>() {
@Override
public void onChanged(@Nullable List strings) {
choicecity_city_letterlistview.setChars(strings);
}
});
mCitiesViewModel.mSaveDBSuccess.observe(this, new android.arch.lifecycle.Observer() {
@Override
public void onChanged(@Nullable Boolean aBoolean) {
choicecity_txt_ts.setVisibility(aBoolean ? View.GONE : View.VISIBLE);
}
});
mCitiesViewModel.loadLocalData();
}
private void initData() {
handler = new Handler();
mHandler = new MyHandler(new WeakReference(this));
mChoiceCityAdapter1 = new ChoiceCityOldAdapter(this);
init(mChoiceCityAdapter1,choicecity_recyclerview);
mSuspendView = new SuspendView(choicecity_recyclerview, mTitleViewHolder, mChoiceCityAdapter1);
mChoiceCityAdapter2 = new ChoiceCityOldAdapter(this);
init(mChoiceCityAdapter2,choicecity_srecyclerview);
}
private void initView() {
//监听搜索框EditText的内容改变事件
//使用RxTextView监听,可以过滤用户输入太快的文字,这样就可以减小查询数据库的次数
Observable subscription = RxTextView.textChanges(choicecity_search_ed)
.debounce(400, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread());
mObserver = new Observer() {
@Override
public void onSubscribe(@NonNull Disposable disposable) {
}
@Override
public void onError(@NonNull Throwable throwable) {
}
@Override
public void onComplete() {
}
@Override
public void onNext(@NonNull CharSequence charSequence) {
if (choicecity_search_ed.getText().length() > 0) {
choicecity_search_del.setVisibility(View.VISIBLE);
String SearStr = (charSequence + "").replaceAll("'", "")
.replace(" while ", "")
.replace(" in ", "")
.replace(" like ", "")
.trim();
List list = cityDao.getAllCitiesData(SearStr);
if (list != null && list.size() > 0) {
mChoiceCityAdapter2.setData(list);
mChoiceCityAdapter2.notifyDataSetChanged();
} else {
addNoDataViewHolder("true");
}
} else {
choicecity_search_del.setVisibility(View.GONE);
addNoDataViewHolder("false");
}
}
};
subscription.subscribe(mObserver);
mTitleViewHolder = new TitleViewHolder(findViewById(R.id.choicecity_title));
mTitleViewHolder.setVisibilityViewH(false);
overlayThread=new OverlayThread();
choicecity_city_letterlistview.setOnTouchingLetterChangedListener(new LetterListView.OnTouchingLetterChangedListener() {
@Override
public void onTouchingLetterChanged(String s) {
if (mStringIntegerHashMap!=null&&mStringIntegerHashMap.get(s)!=null){
int position=mStringIntegerHashMap.get(s);
choicecity_recyclerview.scrollToPosition(position);
choicecity_overlay.setVisibility(View.VISIBLE);
choicecity_overlay.setText(s);
handler.removeCallbacks(overlayThread);
//延迟一秒后执行,让overlay消失
handler.postDelayed(overlayThread,1000);
}
}
});
choicecity_close.setVisibility(View.VISIBLE);
}
public void init(RecyclerView.Adapter mAdapter,RecyclerView recyclerView) {
if (mAdapter == null) {
return;
}
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView,
int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(mAdapter);
}
public class MyHandler extends Handler {
WeakReference weakReference;
public MyHandler(WeakReference reference) {
weakReference = reference;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Activity activity = weakReference.get();
if (null == activity || activity.isFinishing()) {
return;
}
switch (msg.what) {
case LocationOK_Msg:
Bundle bundle = msg.getData();
String city = bundle.getString("city");
String province = bundle.getString("province");
String district = bundle.getString("district");
onLocationOK(city, province, district);
break;
case LocationError_Msg:
onLocationError();
break;
default:
break;
}
}
}
private void onLocationError() {
mChoiceCityAdapter1.setLocation(LOCATION_STATE2, LOCATION_STATE2, LOCATION_STATE2);
if (NetworkUtil.checkNetWork(this)) {
showOrderDialog();
}
}
/**
* 显示定位失败弹框
*/
private void showOrderDialog() {
if (this.isFinishing()) {
return;
}
cancel();
dialog = new Dialog(this, R.style.MyDialogStyleBottomtishi);
dialog.setContentView(R.layout.order_estimate_exit_dialog);
TextView btn_ok_tips_title = dialog.findViewById(R.id.btn_ok_tips_title);
btn_ok_tips_title.setText("定位失败");
TextView textView = dialog.findViewById(R.id.tv_tips);
textView.setText("定位服务已被关闭,\n\n请点击\"设置\"-\"权限\"-打开所需权限。\n\n最后点击两次后退按钮,即可返回。");
textView.setVisibility(View.VISIBLE);
RelativeLayout layout = dialog.findViewById(R.id.ordet_text_layout);
layout.setVisibility(View.GONE);
Button cancel_tips = dialog.findViewById(R.id.btn_cancel_tips);
cancel_tips.setText("取消");
cancel_tips.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
mChoiceCityAdapter1.setLocation(LOCATION_STATE2, LOCATION_STATE2, LOCATION_STATE2);
}
});
Button ok_tips = dialog.findViewById(R.id.btn_ok_tips);
ok_tips.setText("确定");
ok_tips.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
}
});
setWidthShowDialog(dialog);
}
public static void setWidthShowDialog(Dialog dialog) {
if (dialog != null && !dialog.isShowing()) {
dialog.show();
WindowManager.LayoutParams layoutParams = dialog.getWindow().getAttributes();
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
dialog.getWindow().getDecorView().setPadding(0, 0, 0, 0);
dialog.getWindow().setAttributes(layoutParams);
}
}
/**
* 取消定位失败弹框
*/
private void cancel() {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
}
/**
* 定位成功
*/
private void onLocationOK(String city, String locationProvince, String district) {
if (locationProvince == null || locationProvince.equals("")) {
mChoiceCityAdapter1.setLocation(LOCATION_STATE2, LOCATION_STATE2, LOCATION_STATE2);
if (NetworkUtil.checkNetWork(this)) {
showOrderDialog();
}
} else {
if (TextUtils.isEmpty(district)
|| (!TextUtils.isEmpty(district)
&& !(district.endsWith("县")
|| district.endsWith("市")
|| district.endsWith("旗")))) {
district = locationProvince;
}
mChoiceCityAdapter1.setLocation(city, locationProvince, district);
}
}
@OnClick({R.id.view_choicecity_topserach,R.id.choicecity_search_cancel,R.id.choicecity_search_del
,R.id.choicecity_search_ed,R.id.choicecity_close})
public void onClick(View view){
switch (view.getId()){
case R.id.view_choicecity_topserach:
//搜索框点击事件
if (choicecity_txt_ts.getVisibility()==View.GONE){
view_choicecity_serach.setVisibility(View.VISIBLE);
view_choicecity_topserach.setVisibility(View.GONE);
choicecity_srecyclerview.setVisibility(View.VISIBLE);
addNoDataViewHolder("false");
//显示软键盘
KeyboardUtil.showKeyboard(choicecity_search_ed);
}
break;
case R.id.choicecity_search_cancel:
//搜索框取消按钮
cancelSearch();
break;
case R.id.choicecity_search_del:
//搜索框内“叉”按钮
//将EditText内容清空,并且将叉按钮隐藏
choicecity_search_ed.setText("");
choicecity_search_del.setVisibility(View.GONE);
break;
case R.id.choicecity_search_ed:
//搜索框EditText
KeyboardUtil.showKeyboard(choicecity_search_ed);
break;
case R.id.choicecity_close:
if(!onBack()) {
finish();
}
break;
}
}
public boolean onBack() {
if (view_choicecity_serach.getVisibility() == View.VISIBLE) {
cancelSearch();
return true;
}
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getKeyCode()==KeyEvent.KEYCODE_BACK&&event.getRepeatCount()==0){
if (onBack()){
return true;
}else {
RegexUtil.showInfo(this,"请选择您所在的地区",false);
return true;
}
}
return super.onKeyDown(keyCode, event);
}
/**
* 取消搜索
*/
private void cancelSearch() {
//1.带取消搜索框,搜索结果RecyclerView都消失,不带取消的搜索框显示
view_choicecity_serach.setVisibility(View.GONE);
choicecity_srecyclerview.setVisibility(View.GONE);
view_choicecity_topserach.setVisibility(View.VISIBLE);
//2.清空EditText内容
choicecity_search_ed.setText("");
//3.清空Adapter数据
if (mChoiceCityAdapter2!=null){
mChoiceCityAdapter2.clear();
}
}
/**
* 添加没有数据的情况
* @param tag 没数据,才打开两种情况,true为没数据
*/
private void addNoDataViewHolder(String tag) {
//1.清除RecyclerView的适配器数据
mChoiceCityAdapter2.getData().clear();
//2.获取数据
List datas=new ArrayList<>();
datas.add(Cities.newCities(tag,-10));
//3.将数据添加到数据适配器
mChoiceCityAdapter2.addData(datas);
//4.通知数据改变
mChoiceCityAdapter2.notifyDataSetChanged();
}
}