Android仿美团切换城市

      

        转载请注明出处:http://blog.csdn.net/dmk877/article/details/49757731


     最近一直关注一些比较有名的app,像美团、58、赶集网、淘宝等等。主要目的就是学习下目前一些常用的技术,模拟一下它们的比较炫的界面来巩固下知识,我发现美团、58同城、赶集网它们的切换城市界面类似,也挺酷炫,另外一个原因由于前面几篇博客写的自定义控件的一些知识相对来说比较难,也有好多看官反应很难读懂,那么好,今天呢,就和大家一起分享一下这个界面的写法。这个界面的实现并不难牵扯到的知识点还挺多的而且还挺酷炫,所以能够读这篇博客你绝对赚到了,哈哈。。。

如有谬误欢迎批评指正,如有疑问欢迎留言。

通过本篇博客你将学到以下知识点

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

②百度地图定位的使用

③自定义控件的相关的知识

④数据库相关的操作

⑤pinyin4j的用法

我们废话不多说先看看效果图这也是今天我们要达到的效果,由于csdn只允许上传图片大小不超过2M的图片,所以这里我录制两张图片如下

        

它的主要功能有:①展示定位的城市②展示最近访问的城市③展示热门的城市④展示需要展示的城市⑤用EditText进行筛选城市⑥当滑动右边的字母时左边的ListView会跳到相应的位置等。

在这里要提醒大家注意一点不要在模拟器上运行,最好在真机上运行,模拟器上的运行界面效果不好,看到上面两张图是不是感觉还不错,通过这篇博客的学习相信你也可以,咱们废话不多说进入主题

首先来分析下整个界面如下图

Android仿美团切换城市_第1张图片

从整体上来说包括三大部分第一部分就是最上方的一个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显示在屏幕中间,它的效果图如下

可以看到效果还不错,(再次提醒注意这里最好不要用模拟器去运行,因为模拟器上运行的效果与和上面的效果差距很大,用真机效果好)这样自定义的这个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,美团,赶集网的切换城市的界面就算写完了,里面由于细节特别多,我就捡主要的功能,分析了它的实现思想。

如果你有什么疑问,或者发现文章中的错误,欢迎批评指正,谢谢。如果你觉着这篇文章对你有帮助,就赞一个,顶一下呗,您的支持是我前进的动力。。


源码地址

 转载请注明出处:http://blog.csdn.net/dmk877/article/details/49757731





















你可能感兴趣的:(android,BaseAdapter,切换城市,仿58同城切换城市,仿美团切换城市)