編輯:關於Android編程
最近一直關注一些比較有名的app,像美團、58、趕集網、淘寶等等。主要目的就是學習下目前一些常用的技術,模擬一下它們的比較炫的界面來鞏固下知識,我發現美團、58同城、趕集網它們的切換城市界面類似,也挺酷炫,另外一個原因由於前面幾篇博客寫的自定義控件的一些知識相對來說比較難,也有好多看官反應很難讀懂,那麼好,今天呢,就和大家一起分享一下這個界面的寫法。這個界面的實現並不難牽扯到的知識點還挺多的而且還挺酷炫,所以能夠讀這篇博客你絕對賺到了,哈哈。。。
如有謬誤歡迎批評指正,如有疑問歡迎留言。
通過本篇博客你將學到以下知識點
①BaseAdapter的使用包括BaseAdapter中的getViewTypeCount和getItemViewType方法的使用
②百度地圖定位的使用
③自定義控件的相關的知識
④數據庫相關的操作
⑤pinyin4j的用法
我們廢話不多說先看看效果圖這也是今天我們要達到的效果,由於csdn只允許上傳圖片大小不超過2M的圖片,所以這裡我錄制兩張圖片如下

它的主要功能有:①展示定位的城市②展示最近訪問的城市③展示熱門的城市④展示需要展示的城市⑤用EditText進行篩選城市⑥當滑動右邊的字母時左邊的ListView會跳到相應的位置等。
在這裡要提醒大家注意一點不要在模擬器上運行,最好在真機上運行,模擬器上的運行界面效果不好,看到上面兩張圖是不是感覺還不錯,通過這篇博客的學習相信你也可以,咱們廢話不多說進入主題
首先來分析下整個界面如下圖

從整體上來說包括三大部分第一部分就是最上方的一個EditText,第二部分就是最右邊的自定義View,第三部分是EditText下方的ListView,這裡EditText的主要作用就是篩選城市,接著我們一點一點的去實現上面的效果。
1、右側自定義View的實現
實現這樣一個效果就是滑動最右邊的自定義View然後界面中間的TextView去展示所滑到的字母,這裡就要去自定義一個View了,首先來分析下思路,我是這樣想的:
①需要用canvas的drawText方法將:“定位”、最近、熱門“、全部、A-Z這些數據畫出來,怎麼去按照上述圖片的樣子去繪畫這些數據呢?首先需要獲得每個字符的高度,怎麼獲得?用View的高度除以字符的個數就可以得到每個字符的高度,然後繪制時通過控制Y坐標不斷的增加從而使數據沿著豎直方向去繪制,在自定義的View中它的實現代碼如下
for (int i = 0; i < letter.length; i++) {
String text = letter[i];
float xPosition = width / 2 - mPaint.measureText(text) / 2;
float yPosition = singleHeight * i + singleHeight;
//通過不斷的改變yPosition將數組中的數據一個一個繪制到自定義的View中
canvas.drawText(text, xPosition, yPosition, mPaint);
}
第4行就是讓所繪制的文字在X方向上顯示在View的中間,而float yPosition = singleHeight * i + singleHeight;就是來改變每個文字的Y坐標使其沿著豎直方向去繪制文字
②在滑動時怎樣通知Activity當前滑動到哪兒了?
這裡是通過一個監聽的方式,在Activity中注冊了自定義View的監聽,然後在View滑動的時候將數據回調給Activity
我們先看看代碼然後運行下看看是不是這樣
MyLetterView的代碼如下
package com.example.citylistpractice;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
public class MyLetterView extends View {
private Paint mPaint;
private boolean isShowBg = false;// 用於區分是否顯示view的背景
private OnSlidingListener mOnSlidingListener;// 滑動此View的監聽器
private int choose = -1;// 用於標記當前所選中的位置
private TextView mTvDialog;//用於接受從activity中傳過來的,中間用於展示字母的textView
//需要展示的數據
private String[] letter = { 定位, 最近, 熱門, 全部, A, B, C, D,
E, F, G, H,J, K, L, M, N,P, Q,
R, S, T,W, X, Y, Z };
public MyLetterView(Context context) {
super(context);
}
public MyLetterView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public MyLetterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(26);
mPaint.setColor(Color.parseColor(#8c8c8c));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//當此View被按下時所顯示的背景顏色
if (isShowBg) {
canvas.drawColor(Color.parseColor(#40000000));
}
//計算每個字符所占的高度
float singleHeight = getHeight() / letter.length;
int width = getWidth();
for (int i = 0; i < letter.length; i++) {
String text = letter[i];
float xPosition = width / 2 - mPaint.measureText(text) / 2;
float yPosition = singleHeight * i + singleHeight;
//通過不斷的改變yPosition將數組中的數據一個一個繪制到自定義的View中
canvas.drawText(text, xPosition, yPosition, mPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int position = (int) (event.getY() / getHeight() * letter.length);
int oldChoose = choose;
switch (action) {
case MotionEvent.ACTION_DOWN:
isShowBg = true;
if (oldChoose != position && mOnSlidingListener != null) {
if (position > 0 && position < letter.length) {
//將滑動到的字母傳遞到activity中
mOnSlidingListener.sliding(letter[position]);
choose=position;
if(mTvDialog!=null){
mTvDialog.setVisibility(View.VISIBLE);
mTvDialog.setText(letter[position]);
}
}
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
isShowBg = true;
if (oldChoose != position && mOnSlidingListener != null) {
if (position >=0 && position < letter.length) {
mOnSlidingListener.sliding(letter[position]);
choose=position;
if(mTvDialog!=null){
mTvDialog.setVisibility(View.VISIBLE);
mTvDialog.setText(letter[position]);
}
}
invalidate();
}
break;
case MotionEvent.ACTION_UP:
isShowBg = false;
choose=-1;
if(mTvDialog!=null){
mTvDialog.setVisibility(View.GONE);
}
invalidate();
break;
}
return true;
}
//MyLetterView的一個滑動的監聽
public void setOnSlidingListener(OnSlidingListener mOnSlidingListener) {
this.mOnSlidingListener = mOnSlidingListener;
}
public interface OnSlidingListener {
public void sliding(String str);
}
public void setTextView(TextView tvDialog) {
mTvDialog=tvDialog;
}
}
可以發現重寫了onTouchEvent方法,然後通過監聽down,move,up事件來執行相關的操作,當down時首先會改變整個view的背景色,然後將當前滑到的字母通過回調的方式即調用mOnSlidingListener.sliding(letter[position])(這裡的mOnSlidingListener就是在activity中的setOnSlidingListener所注冊的監聽器)傳遞到Activity中。
布局文件
MainActivity中的代碼
package com.example.citylistpractice;
import com.example.citylistpractice.MyLetterView.OnSlidingListener;
import android.os.Bundle;
import android.widget.TextView;
import android.app.Activity;
public class MainActivity extends Activity {
private MyLetterView myLetterView;
private TextView tvDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myLetterView=(MyLetterView) findViewById(R.id.my_letterview);
tvDialog=(TextView) findViewById(R.id.tv_dialog);
//將中間展示字母的TextView傳遞到myLetterView中並在其中控制它的顯示與隱藏
myLetterView.setTextView(tvDialog);
//注冊MyLetterView中監聽(跟setOnClickListener這種系統默認寫好的監聽一樣只不過這裡是我們自己寫的)
myLetterView.setOnSlidingListener(new OnSlidingListener() {
@Override
public void sliding(String str) {
tvDialog.setText(str);
}
});
}
}
在MAinActivity中可以看到myLetterView注冊了在MyLetterView中的監聽,通過回調的方式將MyLetterView中滑動到的文字傳遞給MainAcitivity中,並通過tvDialog顯示在屏幕中間,它的效果圖如下

可以看到效果還不錯,(再次提醒注意這裡最好不要用模擬器去運行,因為模擬器上運行的效果與和上面的效果差距很大,用真機效果好)這樣自定義的這個View的功能就實現了。
2、ListView數據的展示
ListView數據的展示是這個界面的重點,對於ListView數據的展示我們都知道它是依靠BaseAdapter的,這裡也是通過給ListView設置一個適配器從而實現文章剛開始展現的效果,只不過這裡的Adapter用了平時再給ListView設置適配器時不常用的兩個方法,一個是getViewTypeCount,另外一個是getItemViewType,仔細觀察上面的分析圖可以發現這個列表共有5種類型的Item①當前定位城市②最近訪問城市③熱門城市④全部城市(僅僅顯示”全部城市“這四個字)⑤也就是這個列表的主角就是顯示從數據庫中查出的所有的城市。下面來一一分析這個5種Item的實現方法,
第一種item即當前定位城市,這個item就是用百度定位來定位用戶當前所在城市,這個布局沒什麼可說的。
第二種布局即最近訪問城市,認真看文章剛開始的那個分析圖會發現這個item包含一個GridView用來展示最近訪問的城市,這裡需要注意美團它的最近訪問城市是展示三個,我們這裡也是,這裡的最近訪問城市是通過操作數據庫來實現的,這裡有兩種實現方法①當數據庫中已經有三條數據時,當用戶訪問第四個城市的時候,此時需要將第四個城市插入到最近訪問城市的最前面,而將數據庫中原來排在第三位的城市刪除掉,這樣就保證了數據庫中始終有三個最近訪問的城市。②每次都將新訪問的城市插入到數據庫,在查詢時只查前三條,並按時間先後順序排序。這裡我們采用的是第2個方案,它的實現代碼如下
插入城市
public void InsertCity(String name) {
SQLiteDatabase db = cityOpenHelper.getReadableDatabase();
Cursor cursor = db.rawQuery(select * from recentcity where name = '
+ name + ', null);
if (cursor.getCount() > 0) { //
db.delete(recentcity, name = ?, new String[] { name });
}
db.execSQL(insert into recentcity(name, date) values(' + name + ',
+ System.currentTimeMillis() + ));
db.close();
}
查詢城市
SQLiteDatabase recentVisitDb = cityOpenHelper.getWritableDatabase();
Cursor cursor = recentVisitDb.rawQuery(select * from recentcity order by date desc limit 0, 3, null);
while (cursor.moveToNext()) {
String recentVisitCityName=cursor.getString(cursor.getColumnIndex(name));
recentCityList.add(recentVisitCityName);
}
cursor.close();
recentVisitDb.close();
如果你對數據庫不熟可以參考此博客SQLiteDatabase數據庫操作詳解
第三種布局即熱門城市這個item和第二種類似,也是包含一個GridView這裡的GridView的數據是從服務器中返回過來的,這裡需要注意的是這裡的GridView和第二種布局中的GridView都是自定義的GridView,因為這裡的GridView是以Item的形式展現在ListView中的,所以當數據較多時GridView的數據展示不完,這裡進行自定義的目的在於,GridView的數據有多少我們讓它自適應數據的個數不需要滑動而將數據展示完。它的定義也非常簡單代碼如下
package com.example.citylist.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
public class MyGridView extends GridView {
public MyGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, measureSpec);
}
}
如果大家對自定義View不熟可以參考此博客(Android開發之自定義控件(一)---onMeasure詳解)
第四種布局很簡單就是一個TextView展示“全部城市”這四個字
第五種布局也就是主角的主角,就是按照字母的順序去展示從數據庫中查出來的城市,這裡的數據是在assets下的一個db文件通過調用SQLiteDatabase.openOrCreateDatabase(dbf, null)在android中使用SQLiteDatabase的靜態方法openOrCreateDatabase(String path,SQLiteDatabae.CursorFactory factory)(參數1 :數據庫創建的路徑,參數2 :一般設置為null就可以了)打開或者創建一個數據庫。它會自動去檢測是否存在這個數據庫,如果存在則打開,不存在則創建一個數據庫;創建成功則返回一個SQLiteDatabase對象,否則拋出異常FileNotFoundException,例如創建一個meituan_cities.db的數據庫
,SQLiteDatabae db=SQLiteDatabase.openOrCreateDatabase(/data/data/com.lingdududu.db/databases/meituan_cities.db,null); 在這個項目中這裡的“/data/data/com.lingdududu.db/databases/meituan_cities.db”就是項目的assets目錄下的meituan_cities.db的路徑。
如果你仔細看了這個界面你會發現這裡的城市被分成了22組,哪22組?從A-Z 26個字母去掉i,o,u,v。這一點可以從我們剛才自定義的View的字母中看到,如果我沒說之前你發現了,我只能說你太牛逼了,你可以去警察局破案了,哈哈。每一組的第一個Item是展示這組數據的首字母的。這是怎麼做到的呢?這就需要依靠pinyin4j這個jar包了,如果你不會用可以去查查資料,這裡我們的代碼裡也有詳細的注釋,由於篇幅原因我就不再說了,如果有需要的話我會專門寫一篇博客來闡述它的用法。這裡說一下它的實現思想,它是通過當前條目的城市的拼音的首字母和它的前一個條目的城市的拼音的首字母進行比較,如果不相同說明這是下一組的數據,當前條目應該展示首字母,否則的話就將展示字母的TextView隱藏起來。
好了將這5種類型的Item都分析完後我們來看看,它的部分代碼
將字母按A-Z排序的comparator
/**
* a-z排序
*/
@SuppressWarnings(rawtypes)
Comparator comparator = new Comparator() {
@Override
public int compare(City lhs, City rhs) {
String a = lhs.getPinyin().substring(0, 1);
String b = rhs.getPinyin().substring(0, 1);
int flag = a.compareTo(b);
if (flag == 0) {
return a.compareTo(b);
} else {
return flag;
}
}
};
在MainActivity中將查詢出來的數據按照我們自己定義的規則進行排序的代碼如下
Collections.sort(cityList, comparator);
創建數據庫的代碼
public void createDataBase() throws IOException {
boolean dbExist = checkDataBase();
if (dbExist) {
// 數據庫已存在,do nothing.
} else {
// 創建數據庫
try {
File dir = new File(DB_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File dbf = new File(DB_PATH + DB_NAME);
if (dbf.exists()) {
dbf.delete();
}
SQLiteDatabase.openOrCreateDatabase(dbf, null);
// 復制asseets中的db文件到DB_PATH下
copyDataBase();
} catch (IOException e) {
throw new Error(數據庫創建失敗);
}
}
}
創建好數據庫後將assets下的數據復制到創建好的數據庫下copyDataBase方法的代碼如下
private void copyDataBase() throws IOException {
// Open your local db as the input stream
InputStream myInput = mContext.getAssets().open(ASSETS_NAME);
// Path to the just created empty db
String outFileName = DB_PATH + DB_NAME;
// Open the empty db as the output stream
OutputStream myOutput = new FileOutputStream(outFileName);
// transfer bytes from the inputfile to the outputfile
byte[] buffer = new byte[1024];
int length;
while ((length = myInput.read(buffer)) > 0) {
myOutput.write(buffer, 0, length);
}
// Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
}
ListView的Adapter的代碼
package com.example.citylist.adapter;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.example.citylist.R;
import com.example.citylist.bean.City;
import com.example.citylist.view.MyGridView;
public class CityListAdapter extends BaseAdapter {
private Context mContext;
private List mAllCityList;
private List mHotCityList;
private List mRecentCityList;
public HashMap alphaIndexer;// 存放存在的漢語拼音首字母和與之對應的列表位置
private String[] sections;// 存放存在的漢語拼音首字母
private LocationClient myLocationClient;
private String currentCity;//當前城市
private MyLocationListener myLocationListener;
private boolean isNeedRefresh;//當前定位的城市是否需要刷新
private TextView tvCurrentLocateCity;
private ProgressBar pbLocate;
private TextView tvLocate;
private final int VIEW_TYPE = 5;//view的類型個數
public CityListAdapter(Context context, List allCityList,
List hotCityList, List recentCityList) {
this.mContext = context;
this.mAllCityList = allCityList;
this.mHotCityList = hotCityList;
this.mRecentCityList=recentCityList;
alphaIndexer = new HashMap();
sections = new String[allCityList.size()];
//這裡的主要目的是將listview中要顯示字母的條目保存下來,方便在滑動時獲得位置,alphaIndexer在Acitivity有調用
for (int i = 0; i < mAllCityList.size(); i++) {
// 當前漢語拼音首字母
String currentStr = getAlpha(mAllCityList.get(i).getPinyin());
// 上一個漢語拼音首字母,如果不存在為
String previewStr = (i - 1) >= 0 ? getAlpha(mAllCityList.get(i - 1).getPinyin()) : ;
if (!previewStr.equals(currentStr)) {
String name = getAlpha(mAllCityList.get(i).getPinyin());
alphaIndexer.put(name, i);
sections[i] = name;
}
}
isNeedRefresh=true;
initLocation();
}
@Override
public int getViewTypeCount() {
return VIEW_TYPE;
}
@Override
public int getItemViewType(int position) {
return position < 4 ? position : 4;
}
@Override
public int getCount() {
return mAllCityList.size();
}
@Override
public Object getItem(int position) {
return mAllCityList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
int viewType = getItemViewType(position);
if (viewType == 0) {//view類型為0,也就是:當前定位城市的布局
convertView = View.inflate(mContext, R.layout.item_location_city,
null);
tvLocate=(TextView) convertView.findViewById(R.id.tv_locate);
tvCurrentLocateCity=(TextView) convertView.findViewById(R.id.tv_current_locate_city);
pbLocate = (ProgressBar) convertView.findViewById(R.id.pb_loacte);
if(!isNeedRefresh){
tvLocate.setText(當前定位城市);
tvCurrentLocateCity.setVisibility(View.VISIBLE);
tvCurrentLocateCity.setText(currentCity);
pbLocate.setVisibility(View.GONE);
}else{
myLocationClient.start();
}
tvCurrentLocateCity.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
pbLocate.setVisibility(View.VISIBLE);
tvLocate.setText(正在定位);
tvCurrentLocateCity.setVisibility(View.GONE);
myLocationClient.start();
}
});
} else if (viewType == 1) {//最近訪問城市
convertView = View.inflate(mContext,R.layout.item_recent_visit_city, null);
TextView tvRecentVisitCity=(TextView) convertView.findViewById(R.id.tv_recent_visit_city);
tvRecentVisitCity.setText(最近訪問城市);
MyGridView gvRecentVisitCity = (MyGridView) convertView.findViewById(R.id.gv_recent_visit_city);
gvRecentVisitCity.setAdapter(new RecentVisitCityAdapter(mContext,mRecentCityList));
} else if (viewType == 2) {//熱門城市
convertView = View.inflate(mContext,R.layout.item_recent_visit_city, null);
TextView tvRecentVisitCity=(TextView) convertView.findViewById(R.id.tv_recent_visit_city);
tvRecentVisitCity.setText(熱門城市);
MyGridView gvRecentVisitCity = (MyGridView) convertView.findViewById(R.id.gv_recent_visit_city);
gvRecentVisitCity.setAdapter(new HotCityAdapter(mContext,mHotCityList));
} else if (viewType == 3) {//全部城市,僅展示“全部城市這四個字”
convertView = View.inflate(mContext,R.layout.item_all_city_textview, null);
} else {//數據庫中所有的城市的名字展示
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = View.inflate(mContext, R.layout.item_city_list,null);
viewHolder.tvAlpha = (TextView) convertView.findViewById(R.id.tv_alpha);
viewHolder.tvCityName = (TextView) convertView.findViewById(R.id.tv_city_name);
viewHolder.llMain=(LinearLayout) convertView.findViewById(R.id.ll_main);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
if (position >= 1) {
viewHolder.tvCityName.setText(mAllCityList.get(position).getName());
viewHolder.llMain.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,mAllCityList.get(position).getName(),0).show();
}
});
String currentStr = getAlpha(mAllCityList.get(position).getPinyin());
String previewStr = (position - 1) >= 0 ? getAlpha(mAllCityList
.get(position - 1).getPinyin()) : ;
//如果當前的條目的城市名字的拼音的首字母和其前一條條目的城市的名字的拼音的首字母不相同,則將布局中的展示字母的TextView展示出來
if (!previewStr.equals(currentStr)) {
viewHolder.tvAlpha.setVisibility(View.VISIBLE);
viewHolder.tvAlpha.setText(currentStr);
} else {
viewHolder.tvAlpha.setVisibility(View.GONE);
}
}
}
return convertView;
}
// 獲得漢語拼音首字母
private String getAlpha(String str) {
if (str == null) {
return #;
}
if (str.trim().length() == 0) {
return #;
}
char c = str.trim().substring(0, 1).charAt(0);
// 正則表達式,判斷首字母是否是英文字母
Pattern pattern = Pattern.compile(^[A-Za-z]+$);
if (pattern.matcher(c + ).matches()) {
return (c + ).toUpperCase();
} else if (str.equals(0)) {
return 定位;
} else if (str.equals(1)) {
return 最近;
} else if (str.equals(2)) {
return 熱門;
} else if (str.equals(3)) {
return 全部;
} else {
return #;
}
}
class ViewHolder {
TextView tvAlpha;
TextView tvCityName;
LinearLayout llMain;
}
public void initLocation() {
myLocationClient = new LocationClient(mContext);
myLocationListener=new MyLocationListener();
myLocationClient.registerLocationListener(myLocationListener);
// 設置定位參數
LocationClientOption option = new LocationClientOption();
option.setCoorType(bd09ll); // 設置坐標類型
option.setScanSpan(10000); // 10分鐘掃描1次
// 需要地址信息,設置為其他任何值(string類型,且不能為null)時,都表示無地址信息。
option.setAddrType(all);
// 設置是否返回POI的電話和地址等詳細信息。默認值為false,即不返回POI的電話和地址信息。
option.setPoiExtraInfo(true);
// 設置產品線名稱。強烈建議您使用自定義的產品線名稱,方便我們以後為您提供更高效准確的定位服務。
option.setProdName(通過GPS定位我當前的位置);
// 禁用啟用緩存定位數據
option.disableCache(true);
// 設置最多可返回的POI個數,默認值為3。由於POI查詢比較耗費流量,設置最多返回的POI個數,以便節省流量。
option.setPoiNumber(3);
// 設置定位方式的優先級。
// 當gps可用,而且獲取了定位結果時,不再發起網絡請求,直接返回給用戶坐標。這個選項適合希望得到准確坐標位置的用戶。如果gps不可用,再發起網絡請求,進行定位。
option.setPriority(LocationClientOption.GpsFirst);
myLocationClient.setLocOption(option);
myLocationClient.start();
}
public class MyLocationListener implements BDLocationListener{
@Override
public void onReceiveLocation(BDLocation arg0) {
isNeedRefresh=false;
if(arg0.getCity()==null){
//定位失敗
tvLocate.setText(未定位到城市,請選擇);
tvCurrentLocateCity.setVisibility(View.VISIBLE);
tvCurrentLocateCity.setText(重新選擇);
pbLocate.setVisibility(View.GONE);
return;
}else{
//定位成功
currentCity=arg0.getCity().substring(0,arg0.getCity().length()-1);
tvLocate.setText(當前定位城市);
tvCurrentLocateCity.setVisibility(View.VISIBLE);
tvCurrentLocateCity.setText(currentCity);
myLocationClient.stop();
pbLocate.setVisibility(View.GONE);
}
}
@Override
public void onReceivePoi(BDLocation arg0) {
}
}
}
裡面用到了百度的定位,裡面的注釋都很清楚就不多說了,這裡說一下這樣一個功能的實現,這個功能是當滑動右邊的自定義的View時,ListView根據當前滑動的字母進行變動,這是怎麼實現的?它的實現的思想是這樣的,將上面說的22組數據中,每一組的第一個條目的城市的首字母(也就是在ListView中顯示字母的那個條目)以key,value的形式放到Map中,這裡的key就是當前展示的字母,而value是當前條目在整個列表集合的位置,注意這裡的集合是ListView展示的所有數據的集合,包括我們所說的5種布局的全部數據的集合。而當滑動右邊的自定義的View時,假如說滑動到了“S”,這時在MainActivity中有個回調會將當前滑動的字母回調到MainActivity中,在MainAcivity中收到當前滑動到的“S”後,就會從Map中根據這個字母來查詢它在ListView中所對應的位置,然後通過ListView.setSelection(position)這個方法使界面顯示與當前字母所對應的那個組。它的實現代碼如下
//自定義myLetterView的一個監聽
myLetterView.setOnSlidingListener(new OnSlidingListener() {
@Override
public void sliding(String s) {
isScroll=false;
if(cityListAdapter.alphaIndexer.get(s)!=null){
//根據MyLetterView滑動到的數據獲得ListView應該展示的位置
int position = cityListAdapter.alphaIndexer.get(s);
//將listView展示到相應的位置
lvCity.setSelection(position);
}
}
});
3、EditText實現篩選城市的功能
這個功能的實現其實很簡單,在MainAcitivity中其實是有兩個ListView的一個就是用來展示所有的數據用的,另外一個就是用來展示搜索結果用的,這裡實現篩選的方式很簡單就是給EditText添加一個addTextChangedListener,這個監聽器的代碼如下
etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(s.toString()==null||.equals(s.toString())){
myLetterView.setVisibility(View.VISIBLE);
lvCity.setVisibility(View.VISIBLE);
lvResult.setVisibility(View.GONE);
tvNoResult.setVisibility(View.GONE);
}else{
searchCityList.clear();
myLetterView.setVisibility(View.GONE);
lvCity.setVisibility(View.GONE);
getResultCityList(s.toString());
if (searchCityList.size() <= 0) {
lvResult.setVisibility(View.GONE);
tvNoResult.setVisibility(View.VISIBLE);
} else {
lvResult.setVisibility(View.VISIBLE);
tvNoResult.setVisibility(View.GONE);
}
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
});
當輸入內容時會把展示數據的ListView進行隱藏,而把展示搜索結果的ListView顯示出來,根據EditText輸入的內容,從數據庫中篩選出來符合條件的數據進行展示,它的篩選的SQL語句如下
Cursor cursor = db.rawQuery(select * from city where name like % + keyword+ % or pinyin like % + keyword + %, null);然後將篩選出來的數據通過Adapter展示出來,這樣就完成這個功能。
到這裡關於仿58,美團,趕集網的切換城市的界面就算寫完了,裡面由於細節特別多,我就撿主要的功能,分析了它的實現思想。
如果你有什麼疑問,或者發現文章中的錯誤,歡迎批評指正,謝謝。如果你覺著這篇文章對你有幫助,就贊一個,頂一下呗,您的支持是我前進的動力。。
Android(Animation): 一直轉個不停的齒輪
利用RotateAnimation實現齒輪的無限勻速轉動: 效果圖: (不會做動態圖,齒輪其實一直在轉動) 基本代碼如下: 1.
Android JNI/NDK開發之基本姿勢(二)
接著上篇文章Android JNI/NDK開發之基本姿勢<一>,今天我們講講怎麼調用native方法,以及native調用java層方法,上篇文章我們已經完成
StrictMode介紹
作為Android開發,日常的開發工作中或多或少要接觸到性能問題,比如我的Android程序運行緩慢卡頓,並且常常出現ANR對話框等等問題。既然有性能問題,就需要進行性能
Android Studio制作.so庫實踐
前言因為工作需要可能要用到JNI開發,本篇文章就分享一下我在這方面的實踐,以前我們使用Eclipse做NDK開發,非常麻煩,需要配cygwin的編譯環境,後面NDK功能完