天天看點

Android仿美團切換城市

        轉載請注明出處:http://blog.csdn.net/dmk877/article/details/49757731

     最近一直關注一些比較有名的app,像美團、58、趕集網、淘寶等等。主要目的就是學習下目前一些常用的技術,模拟一下它們的比較炫的界面來鞏固下知識,我發現美團、58同城、趕集網它們的切換城市界面類似,也挺酷炫,另外一個原因由于前面幾篇部落格寫的自定義控件的一些知識相對來說比較難,也有好多看官反應很難讀懂,那麼好,今天呢,就和大家一起分享一下這個界面的寫法。這個界面的實作并不難牽扯到的知識點還挺多的而且還挺酷炫,是以能夠讀這篇部落格你絕對賺到了,哈哈。。。

如有謬誤歡迎批評指正,如有疑問歡迎留言。

通過本篇部落格你将學到以下知識點

①BaseAdapter的使用包括BaseAdapter中的getViewTypeCount和getItemViewType方法的使用

②百度地圖定位的使用

③自定義控件的相關的知識

④資料庫相關的操作

⑤pinyin4j的用法

我們廢話不多說先看看效果圖這也是今天我們要達到的效果,由于csdn隻允許上傳圖檔大小不超過2M的圖檔,是以這裡我錄制兩張圖檔如下

Android仿美團切換城市
Android仿美團切換城市

它的主要功能有:①展示定位的城市②展示最近通路的城市③展示熱門的城市④展示需要展示的城市⑤用EditText進行篩選城市⑥當滑動右邊的字母時左邊的ListView會跳到相應的位置等。

在這裡要提醒大家注意一點不要在模拟器上運作,最好在真機上運作,模拟器上的運作界面效果不好,看到上面兩張圖是不是感覺還不錯,通過這篇部落格的學習相信你也可以,咱們廢話不多說進入主題

首先來分析下整個界面如下圖

Android仿美團切換城市

從整體上來說包括三大部分第一部分就是最上方的一個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中。

布局檔案

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f0f0f0" >

        <TextView
            android:id="@+id/tv_dialog"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_centerInParent="true"
            android:background="@android:color/darker_gray"
            android:gravity="center"
            android:textColor="#ffffffff"
            android:textSize="30dp"
            android:visibility="gone" />

        <com.example.citylistpractice.MyLetterView
            android:id="@+id/my_letterview"
            android:layout_width="25dp"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:layout_marginRight="2dp"
            android:layout_marginTop="7dp" />

</RelativeLayout>
           

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顯示在螢幕中間,它的效果圖如下

Android仿美團切換城市

可以看到效果還不錯,(再次提醒注意這裡最好不要用模拟器去運作,因為模拟器上運作的效果與和上面的效果差距很大,用真機效果好)這樣自定義的這個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<City>() {
		@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<City> mAllCityList;
	private List<City> mHotCityList;
	private List<String> mRecentCityList;
	public HashMap<String, Integer> 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<City> allCityList,
			List<City> hotCityList, List<String> recentCityList) {
		this.mContext = context;
		this.mAllCityList = allCityList;
		this.mHotCityList = hotCityList;
		this.mRecentCityList=recentCityList;
		
		alphaIndexer = new HashMap<String, Integer>();
		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,美團,趕集網的切換城市的界面就算寫完了,裡面由于細節特别多,我就撿主要的功能,分析了它的實作思想。

如果你有什麼疑問,或者發現文章中的錯誤,歡迎批評指正,謝謝。如果你覺着這篇文章對你有幫助,就贊一個,頂一下呗,您的支援是我前進的動力。。

源碼位址

或者加群,群裡有源碼。群号:467325240

 轉載請注明出處:http://blog.csdn.net/dmk877/article/details/49757731