短短的国庆8天假期一眨眼就过去了,下次长假只有等到过年了,本宝宝不开心。既然已经开始工作了,就要好好多学习点新知识,来提高自己的代码能力,今天带大家去实现简易的选择城市界面,并且可以根据城市首字母或者拼音搜索。先来看下我的效果图:
在写代码之前先准备好一个城市列表的json文件以及去百度或者高德申请定位功能,Demo在文章最后。(Demo里没有写定位功能,需要自己实现哦~)
实现方法也比较简单,我就简单的给大家说一下,整个城市列表是用的一个ListView,方便我们后面监听它的滚动状态,右边的快速定位栏是自定义View,代码比较简单,我就直接放代码了。不知道大家发现没有,有些字母开头的城市是没有的,比如 I,O,U,V等等,为了节约空间我就把那几个字母去掉了,有的软件是把26个字母全部保留了。
(其实都不影响,看你个人吧,我反正是有强迫症的。)注意,在自定义View的时候三种构造方法都要写上,千万不要偷懒!!!哎,都是泪 o(╥﹏╥)o
public class LetterListView extends View {
OnTouchingLetterChangedListener onTouchingLetterChangedListener;
public static String[] b = {"定位", "热门", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K",
"L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"};
int choose = -1;
Paint paint = new Paint();
boolean showBkg = false;
private Context mContext;
public LetterListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public LetterListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public LetterListView(Context context) {
super(context);
init(context);
}
private void init(Context context){
this.mContext = context;
paint.setColor(Color.parseColor("#50B3DA"));
paint.setTextSize(DisplayUtil.sp2px(mContext, 12));
paint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (showBkg) {
canvas.drawColor(Color.parseColor("#40000000"));
}
int height = getHeight();
int width = getWidth();
int singleHeight = height / b.length;
for (int i = 0; i < b.length; i++) {
float xPos = width / 2 - paint.measureText(b[i]) / 2;
float yPos = singleHeight * i + singleHeight;
canvas.drawText(b[i], xPos, yPos, paint);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
final int c = (int) (y / getHeight() * b.length);
switch (action) {
case MotionEvent.ACTION_DOWN:
showBkg = true;
if (oldChoose != c && listener != null) {
if (c >= 0 && c < b.length) {
listener.onTouchingLetterChanged(b[c]);
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_MOVE:
if (oldChoose != c && listener != null) {
if (c >= 0 && c < b.length) {
listener.onTouchingLetterChanged(b[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 onTouchingLetterChangedListener) {
this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}
public interface OnTouchingLetterChangedListener {
void onTouchingLetterChanged(String s);
}
}
使用的时候设置好宽度和高度:
<com.kairui.kyb.ui.view.widget.LetterListView
android:id="@+id/total_city_letters_lv"
android:layout_width="25dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_marginRight="2dp"
android:layout_marginTop="7dp"
android:layout_marginBottom="7dp"/>
接下来就是关键的地方了,咳咳~
一、 既然左边用到了ListView,那就少不了适配器和城市模型。
①、城市模型:其中拼音和首字母是方便用户搜索的时候进行筛选,CityCode你们可以不用去管,我主要是用来请求数据。
public class CityEntity {
private String name;
private String key;
private String pinyin; //全拼
private String first; //首字母
private String cityCode;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getPinyin() {
return pinyin;
}
public void setPinyin(String pinyin) {
this.pinyin = pinyin;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getCityCode() {
return cityCode;
}
public void setCityCode(String cityCode) {
this.cityCode = cityCode;
}
}
②、全部城市列表的适配器:我这里将全部城市列表分成了三种类型,第一种就是当前定位城市的布局,第二种就是热门城市的布局,(当然了,热门城市你们可以从后台获取,我图方便就写死在本地了),第三种就是全部城市的布局了
/**
* 总城市适配器
*/
private class CityListAdapter extends BaseAdapter {
private Context context;
private List<CityEntity> totalCityList;
private List<CityEntity> hotCityList;
private LayoutInflater inflater;
final int VIEW_TYPE = 3;
CityListAdapter(Context context,
List<CityEntity> totalCityList,
List<CityEntity> hotCityList) {
this.context = context;
this.totalCityList = totalCityList;
this.hotCityList = hotCityList;
inflater = LayoutInflater.from(context);
alphaIndexer = new HashMap<>();
for (int i = 0; i < totalCityList.size(); i++) {
// 当前汉语拼音首字母
String currentStr = totalCityList.get(i).getKey();
String previewStr = (i - 1) >= 0 ? totalCityList.get(i - 1).getKey() : " ";
if (!previewStr.equals(currentStr)) {
String name = getAlpha(currentStr);
alphaIndexer.put(name, i);
}
}
}
@Override
public int getViewTypeCount() {
return VIEW_TYPE;
}
@Override
public int getItemViewType(int position) {
return position < 2 ? position : 2;
}
@Override
public int getCount() {
return totalCityList == null ? 0 : totalCityList.size();
}
@Override
public Object getItem(int position) {
return totalCityList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final TextView curCityNameTv;
ViewHolder holder;
int viewType = getItemViewType(position);
if (viewType == 0) { // 定位
convertView = inflater.inflate(R.layout.select_city_location_item, null);
LinearLayout noLocationLl = (LinearLayout) convertView.findViewById(R.id.cur_city_no_data_ll);
TextView getLocationTv = (TextView) convertView.findViewById(R.id.cur_city_re_get_location_tv);
curCityNameTv = (TextView) convertView.findViewById(R.id.cur_city_name_tv);
if (TextUtils.isEmpty(locationCity)) {
noLocationLl.setVisibility(View.VISIBLE);
curCityNameTv.setVisibility(View.GONE);
getLocationTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
initLocation();
}
});
} else {
noLocationLl.setVisibility(View.GONE);
curCityNameTv.setVisibility(View.VISIBLE);
curCityNameTv.setText(locationCity);
curCityNameTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!locationCity.equals(UserConstant.curSelCity)) {
//设置城市代码
String cityCode = "";
for (CityEntity cityEntity : AppCache.getInstance().getCurCityList()) {
if (cityEntity.getName().equals(locationCity)) {
cityCode = cityEntity.getCityCode();
break;
}
}
showSetCityDialog(locationCity, cityCode);
} else {
ToastUtils.show("当前定位城市" + curCityNameTv.getText().toString());
}
}
});
}
} else if (viewType == 1) { //热门城市
convertView = inflater.inflate(R.layout.recent_city_item, null);
GridView hotCityGv = (GridView) convertView.findViewById(R.id.recent_city_gv);
hotCityGv.setAdapter(new HotCityListAdapter(context, this.hotCityList));
hotCityGv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
CityEntity cityEntity = hotCityList.get(position);
showSetCityDialog(cityEntity.getName(), cityEntity.getCityCode());
}
});
} else {
if (null == convertView) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.city_list_item_layout, null);
ViewBinder.bind(holder, convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
CityEntity cityEntity = totalCityList.get(position);
holder.cityKeyTv.setVisibility(View.VISIBLE);
holder.cityKeyTv.setText(getAlpha(cityEntity.getKey()));
holder.cityNameTv.setText(cityEntity.getName());
if (position >= 1) {
CityEntity preCity = totalCityList.get(position - 1);
if (preCity.getKey().equals(cityEntity.getKey())) {
holder.cityKeyTv.setVisibility(View.GONE);
} else {
holder.cityKeyTv.setVisibility(View.VISIBLE);
}
}
}
return convertView;
}
private class ViewHolder {
@Bind(R.id.city_name_tv)
TextView cityNameTv;
@Bind(R.id.city_key_tv)
TextView cityKeyTv;
}
}
1)、当前定位城市布局:定位失败的话显示出来,并且提示用户去开启GPS
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical">
<TextView
android:id="@+id/city_item_hint_tv"
android:layout_width="match_parent"
android:layout_height="25dp"
android:background="@color/mainGray"
android:gravity="center_vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:drawablePadding="5dp"
android:drawableLeft="@drawable/ic_location"
android:text="当前定位城市"
android:textColor="@color/gray_9"
android:textSize="14sp" />
<TextView
android:id="@+id/cur_city_name_tv"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:background="@drawable/shape_gray_border_pres_style"
android:ellipsize="end"
android:gravity="center"
android:textColor="@color/mainColor"
android:textSize="14sp"
android:visibility="gone" />
<LinearLayout
android:id="@+id/cur_city_no_data_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:text="无法获取您的定位地址" />
<TextView
android:id="@+id/cur_city_re_get_location_tv"
android:layout_width="120dp"
android:layout_height="35dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:background="@drawable/round_btn_pres_style"
android:gravity="center"
android:text="重新获取"
android:textColor="@color/white" />
</LinearLayout>
</LinearLayout>
2)、热门城市布局:记住,这里直接使用GridView的话只会显示一行数据,需要继承GridView并重写OnMeasure方法,网上有很多案例,就不放代码了。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="25dp"
android:background="@color/mainGray"
android:drawableLeft="@drawable/ic_hot"
android:drawablePadding="5dp"
android:gravity="center_vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="热门城市"
android:textColor="@color/gray_9"/>
<com.kairui.kyb.ui.view.widget.ScrollWithGridView
android:id="@+id/recent_city_gv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="30dp"
android:layout_marginTop="10dp"
android:horizontalSpacing="10dp"
android:listSelector="@android:color/transparent"
android:numColumns="3"
android:verticalSpacing="10dp" />
</LinearLayout>
3)、全部城市的单行布局:每绘制一条数据判断前一个数据的Key是否与现在的相同,true则不显示 city_key_tv,保证同一个Key的城市只有一个显示。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/recycler_bg"
android:orientation="vertical">
<TextView
android:id="@+id/city_key_tv"
android:layout_width="match_parent"
android:layout_height="25dp"
android:background="@color/mainGray"
android:gravity="center_vertical"
android:paddingLeft="32dp"
android:paddingRight="32dp"
android:textColor="@color/gray_9"
android:textSize="14sp" />
<TextView
android:id="@+id/city_name_tv"
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center_vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:textColor="@color/gray_6"
android:textSize="15sp" />
<View
style="@style/default_line"
android:layout_marginLeft="@dimen/common_20"
android:layout_marginRight="@dimen/common_20" />
</LinearLayout>
③、热门城市列表适配器:比较简单,不过多说明。
/**
* 热门城市适配器
*/
private class HotCityListAdapter extends BaseAdapter {
private List<CityEntity> cityEntities;
private LayoutInflater inflater;
HotCityListAdapter(Context mContext, List<CityEntity> cityEntities) {
this.cityEntities = cityEntities;
inflater = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
return cityEntities == null ? 0 : cityEntities.size();
}
@Override
public Object getItem(int position) {
return cityEntities.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (null == convertView) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.city_list_grid_item_layout, null);
ViewBinder.bind(holder, convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
CityEntity cityEntity = cityEntities.get(position);
holder.cityNameTv.setText(cityEntity.getName());
return convertView;
}
private class ViewHolder {
@Bind(R.id.city_list_grid_item_name_tv)
TextView cityNameTv;
}
}
二、初始化首字母提示框,并且设置ListVIew的滚动监听以及自定义View的Touch事件
/**
* 初始化汉语拼音首字母弹出提示框
*/
private void initOverlay() {
mReady = true;
LayoutInflater inflater = LayoutInflater.from(this);
overlay = (TextView) inflater.inflate(R.layout.overlay, null);
overlay.setVisibility(View.INVISIBLE);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
WindowManager windowManager = (WindowManager) this
.getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(overlay, lp);
}
private class LetterListViewListener implements
LetterListView.OnTouchingLetterChangedListener {
@Override
public void onTouchingLetterChanged(final String s) {
isScroll = false;
if (alphaIndexer.get(s) != null) {
int position = alphaIndexer.get(s);
totalCityLv.setSelection(position);
overlay.setText(s);
overlay.setVisibility(View.VISIBLE);
handler.removeCallbacks(overlayThread);
// 延迟让overlay为不可见
handler.postDelayed(overlayThread, 700);
}
}
}
/**
* 设置overlay不可见
*/
private class OverlayThread implements Runnable {
@Override
public void run() {
overlay.setVisibility(View.GONE);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_TOUCH_SCROLL
|| scrollState == SCROLL_STATE_FLING) {
isScroll = true;
} else {
isScroll = false;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!isScroll) {
return;
}
if (mReady) {
String key = getAlpha(totalCityList.get(firstVisibleItem).getKey());
overlay.setText(key);
overlay.setVisibility(View.VISIBLE);
handler.removeCallbacks(overlayThread);
// 延迟让overlay为不可见
handler.postDelayed(overlayThread, 700);
}
}
其中最主要关键的地方就是以上这些了
最后我就放上demo的下载地址了,Demo点我
就是这些了,祝大家第一天工作愉快~
-------------------------------
2019-10-09更新
CSDN当初上传设置的最低1分,现在居然涨到50了,,,过分。现在传到了百度云, 提取码:l7ip
欢迎自取,好用的话,不放点个关注。谢谢~~