好用的城市选择(三级联动)

时间:2021-01-04 05:36:04

最近公司项目中用到了关于收货地址的功能,所以就根据实际情况剖析一下我做的三级联动,也算是总结和回顾吧。

说起三级联动肯定都不陌生,因为现在很多项目中都会有应用,比如 淘宝、京东、美团、饿了么等等,这些都有,我项目中做的其实就是和淘宝的差不多当然我们都叫做仿淘宝项目。废话不多说了下面就直接上代码,根据代码来分析具体的用法,我们主要贴出来一些关键代码。 源码地址: http://download.csdn.net/detail/army_jun/9765371

一、首先地址页面

package myaddressselector.myaddressselector;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import myaddressselector.myaddressselector.callback.SelectAddressInterface;
import myaddressselector.myaddressselector.view.AddressDialog;
import myaddressselector.myaddressselector.view.AddressSelectorDialog;
import myaddressselector.myaddressselector.view.SettingItemView;


public class AddAddressActivity extends AppCompatActivity implements SelectAddressInterface {

EditText mConsignee;
SettingItemView mSivAddress;
EditText mDetailsAddress;
EditText mPostcode;
EditText mPhoneNum;
TextView mAddressSave;

private AddressSelectorDialog addressSelectorDialog;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_address);
initView();
}

private void initView() {
mConsignee = (EditText) findViewById(R.id.consignee);
mSivAddress = (SettingItemView) findViewById(R.id.siv_address);
mDetailsAddress = (EditText) findViewById(R.id.details_address);
mPostcode = (EditText) findViewById(R.id.postcode);
mPhoneNum = (EditText) findViewById(R.id.phoneNum);
mAddressSave = (TextView) findViewById(R.id.address_save);
addAddressAreaData();
mAddressSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(AddAddressActivity.this, "已经保存了地址", Toast.LENGTH_SHORT).show();
}
});
}


private void addAddressAreaData() {
mSivAddress.setItemTip("请选择 ");
mSivAddress.setItemText("所在地区");
mSivAddress.setOnItemClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
setCitySelectorDialog(); //三级联动
}
});
}

private void setCitySelectorDialog() {
if (addressSelectorDialog == null) {
addressSelectorDialog = new AddressSelectorDialog(this);
}
addressSelectorDialog.show();
addressSelectorDialog.setOnAddressChangedListener(new AddressDialog.OnAddressChangedListener() {
@Override
public void onCanceled() {
addressSelectorDialog.dismiss();
}

@Override
public void onConfirmed(String currentProvinceName, String currentCityName, String currentDistrictName, String currentZipCode) {
mSivAddress.setItemTip(currentProvinceName + currentCityName + currentDistrictName);
mPostcode.setText(currentZipCode);
addressSelectorDialog.dismiss();
}
});
}

public static void startAction(Context context) {
Intent i = new Intent(context, AddAddressActivity.class);
context.startActivity(i);
}

@Override
public void setAreaString(String area) {

}

@Override
public void setAreaString(String currentProvinceName, String currentCityName, String currentDistrictName, String currentZipCode) {
mSivAddress.setItemTip(currentProvinceName + currentCityName + currentDistrictName);
mPostcode.setText(currentZipCode);
}

@Override
public void setAreaString(String currentProvinceName, String currentCityName) {

}
}

可以看到这个activity实现了一个接口,这个接口就是通过回调可以拿到的选择后的城市数据,使用接口目的就是能够实时的监控并得到城市改变的数据。接口代码 SelectAddressInterface

public interface SelectAddressInterface {

/**
* 用于地址选择器完成选择后更新最新的地址
* @param area
*/

void setAreaString(String area);

void setAreaString(String currentProvinceName, String currentCityName, String currentDistrictName, String currentZipCode);

void setAreaString(String currentProvinceName, String currentCityName);
}

从上面activity可以看到我把地址三级联动选择封装在了一个dialog里面也就是AddressSelectorDialog 这就相当于一个工具了无论放在那个项目都可以用,只需要new一下就ok了dialog的代码如下

package myaddressselector.myaddressselector.view;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.TextView;

import java.util.ArrayList;

import myaddressselector.myaddressselector.R;


/**
* Created by Army on 2017/2/24
*/

public class AddressSelectorDialog extends AddressDialog implements View.OnClickListener {
private WheelView mViewProvince;
private WheelView mViewCity;
private WheelView mViewDistrict;
private Context mContext;
private TextView tvCancel;
private TextView tvConfirm;
protected boolean isDataLoaded = false;

public AddressSelectorDialog(Context context, int themeResId) {
super(context, themeResId);
this.mContext = context;
}

public AddressSelectorDialog(Context context) {
super(context);
this.mContext = context;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_address);
setUpViews();
setUpListener();
}

private void setUpViews() {
mViewProvince = (WheelView) findViewById(R.id.id_province);
mViewCity = (WheelView) findViewById(R.id.id_city);
mViewDistrict = (WheelView) findViewById(R.id.id_district);
tvCancel = (TextView) findViewById(R.id.btn_cancel);
tvConfirm = (TextView) findViewById(R.id.btn_confirm);
initProvinceSelectView();
}

private void setUpListener() {
// 取消和确定
tvCancel.setOnClickListener(this);
tvConfirm.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_cancel:
if (null != listener) {
listener.onCanceled();
}
break;
case R.id.btn_confirm:
if (null != listener) {
listener.onConfirmed(mCurrentProvinceName, mCurrentCityName, mCurrentDistrictName, mCurrentZipCode);
}
break;
default:
break;
}
}

private void initProvinceSelectView() {
mViewProvince.setOnSelectListener(new WheelView.OnSelectListener() {
@Override
public void endSelect(int id, String text) {
String provinceText = mProvinceDatas.get(id);
if (!mCurrentProvinceName.equals(provinceText)) {
mCurrentProvinceName = provinceText;
ArrayList<String> mCityData = mCitisDatasMap.get(mCurrentProvinceName);
mViewCity.resetData(mCityData);
mViewCity.setDefault(0);
mCurrentCityName = mCityData.get(0);

ArrayList<String> mDistrictData = mDistrictDatasMap.get(mCurrentCityName);
mViewDistrict.resetData(mDistrictData);
mViewDistrict.setDefault(0);
mCurrentDistrictName = mDistrictData.get(0);
}
mCurrentZipCode = mZipcodeDatasMap.get(mCurrentDistrictName);
}

@Override
public void selecting(int id, String text) {
}
});

mViewCity.setOnSelectListener(new WheelView.OnSelectListener() {
@Override
public void endSelect(int id, String text) {
ArrayList<String> mCityData = mCitisDatasMap.get(mCurrentProvinceName);
String cityText = mCityData.get(id);
if (!mCurrentCityName.equals(cityText)) {
mCurrentCityName = cityText;
ArrayList<String> mCountyData = mDistrictDatasMap.get(mCurrentCityName);
mViewDistrict.resetData(mCountyData);
mViewDistrict.setDefault(0);
mCurrentDistrictName = mCountyData.get(0);
}
mCurrentZipCode = mZipcodeDatasMap.get(mCurrentDistrictName);
}

@Override
public void selecting(int id, String text) {

}
});

mViewDistrict.setOnSelectListener(new WheelView.OnSelectListener() {
@Override
public void endSelect(int id, String text) {
ArrayList<String> mDistrictData = mDistrictDatasMap.get(mCurrentCityName);
String districtText = mDistrictData.get(id);
if (!mCurrentDistrictName.equals(districtText)) {
mCurrentDistrictName = districtText;
}
mCurrentZipCode = mZipcodeDatasMap.get(mCurrentDistrictName);
}

@Override
public void selecting(int id, String text) {

}
});
getData();


}

private void getData() {
// 启动线程读取数据
new Thread() {
@Override
public void run() {
// 读取数据
isDataLoaded = initProvinceDatas();

// 通知界面线程
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
setDefaultData();
}
});
}
}.start();
}

private void setDefaultData() {
mViewProvince.setData(mProvinceDatas);
mViewProvince.setDefault(0);
mCurrentProvinceName = mProvinceDatas.get(0);

ArrayList<String> mCityData = mCitisDatasMap.get(mCurrentProvinceName);
mViewCity.setData(mCityData);
mViewCity.setDefault(0);
mCurrentCityName = mCityData.get(0);

ArrayList<String> mDistrictData = mDistrictDatasMap.get(mCurrentCityName);
mViewDistrict.setData(mDistrictData);
mViewDistrict.setDefault(0);
mCurrentDistrictName = mDistrictData.get(0);
mCurrentZipCode = mZipcodeDatasMap.get(0);
}
}

二、数据来源

城市的数据来源其实就是解析的xml文件 关于XML的解析,一共有SAX、PULL、DOM三种解析方式。

此次项目中使用的是SAX解析方式,因为它占用内存少,并且速度快,代码如下

package myaddressselector.myaddressselector.service;


import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.ArrayList;
import java.util.List;

import myaddressselector.myaddressselector.bean.CityModel;
import myaddressselector.myaddressselector.bean.DistrictModel;
import myaddressselector.myaddressselector.bean.ProvinceModel;

public class XmlParserHandler extends DefaultHandler {

/**
* 城市数据
*/

private List<ProvinceModel> provinceList = new ArrayList<ProvinceModel>();

public XmlParserHandler() {

}

public List<ProvinceModel> getDataList() {
return provinceList;
}

@Override
public void startDocument() throws SAXException {
}

ProvinceModel provinceModel = new ProvinceModel();
CityModel cityModel = new CityModel();
DistrictModel districtModel = new DistrictModel();

@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if (qName.equals("province")) {
provinceModel = new ProvinceModel();
provinceModel.setName(attributes.getValue(0));
provinceModel.setCityList(new ArrayList<CityModel>());
} else if (qName.equals("city")) {
cityModel = new CityModel();
cityModel.setName(attributes.getValue(0));
cityModel.setDistrictList(new ArrayList<DistrictModel>());
} else if (qName.equals("district")) {
districtModel = new DistrictModel();
districtModel.setName(attributes.getValue(0));
districtModel.setZipcode(attributes.getValue(1));
}
}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if (qName.equals("district")) {
cityModel.getDistrictList().add(districtModel);
} else if (qName.equals("city")) {
provinceModel.getCityList().add(cityModel);
} else if (qName.equals("province")) {
provinceList.add(provinceModel);
}
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
}

}

三、造*

和前面的封装一样,这次*造好了以后就可以直接用了,自定义了一个滚动组件WheelView

package myaddressselector.myaddressselector.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader.TileMode;
import android.os.Handler;
import android.os.Message;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;

import myaddressselector.myaddressselector.R;


/**
* WheelView滚轮
*
*/

public class WheelView extends View {
/**
* 控件宽度
*/

private float controlWidth;
/**
* 控件高度
*/

private float controlHeight;
/**
* 是否滑动中
*/

private boolean isScrolling = false;
/**
* 选择的内容
*/

private ArrayList<ItemObject> itemList = new ArrayList<ItemObject>();
/**
* 设置数据
*/

private ArrayList<String> dataList = new ArrayList<String>();
/**
* 按下的坐标
*/

private int downY;
/**
* 按下的时间
*/

private long downTime = 0;
/**
* 短促移动
*/

private long goonTime = 200;
/**
* 短促移动距离
*/

private int goonDistence = 100;
/**
* 画线画笔
*/

private Paint linePaint;
/**
* 线的默认颜色
*/

private int lineColor = 0xff000000;
/**
* 线的默认宽度
*/

private float lineWidth = 2f;
/**
* 默认字体
*/

private float normalFont = 14.0f;
/**
* 选中的时候字体
*/

private float selectedFont = 22.0f;
/**
* 单元格高度
*/

private int unitHeight = 50;
/**
* 显示多少个内容
*/

private int itemNumber = 7;
/**
* 默认字体颜色
*/

private int normalColor = 0xff000000;
/**
* 选中时候的字体颜色
*/

private int selectedColor = 0xffff0000;
/**
* 蒙板高度
*/

private float maskHight = 48.0f;
/**
* 选择监听
*/

private OnSelectListener onSelectListener;
/**
* 是否可用
*/

private boolean isEnable = true;
/**
* 刷新界面
*/

private static final int REFRESH_VIEW = 0x001;
/**
* 移动距离
*/

private static final int MOVE_NUMBER = 5;
/**
* 是否允许选空
*/

private boolean noEmpty = true;

/**
* 正在修改数据,避免ConcurrentModificationException异常
*/

private boolean isClearing = false;

public WheelView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
initData();
}

public WheelView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
initData();
}

public WheelView(Context context) {
super(context);
initData();
}

@Override
public boolean onTouchEvent(MotionEvent event) {

if (!isEnable)
return true;
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isScrolling = true;
downY = (int) event.getY();
downTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
actionMove(y - downY);
onSelectListener();
break;
case MotionEvent.ACTION_UP:
int move = Math.abs(y - downY);
// 判断段时间移动的距离
if (System.currentTimeMillis() - downTime < goonTime
&& move > goonDistence) {
goonMove(y - downY);
} else {
actionUp(y - downY);
}
noEmpty();
isScrolling = false;
break;
default:
break;
}
return true;
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

drawLine(canvas);
drawList(canvas);
drawMask(canvas);
}

private synchronized void drawList(Canvas canvas) {
if (isClearing)
return;
try {
for (ItemObject itemObject : itemList) {
itemObject.drawSelf(canvas, getMeasuredWidth());
}
} catch (Exception e) {
}

}

@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
controlWidth = getWidth();
if (controlWidth != 0) {
setMeasuredDimension(getWidth(), itemNumber * unitHeight);
controlWidth = getWidth();
}

}

/**
* 继续移动一定距离
*/

private synchronized void goonMove(final int move) {
new Thread(new Runnable() {

@Override
public void run() {
int distence = 0;
while (distence < unitHeight * MOVE_NUMBER) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
actionThreadMove(move > 0 ? distence : distence * (-1));
distence += 10;

}
actionUp(move > 0 ? distence - 10 : distence * (-1) + 10);
noEmpty();
}
}).start();
}

/**
* 不能为空,必须有选项
*/

private void noEmpty() {
if (!noEmpty)
return;
for (ItemObject item : itemList) {
if (item.isSelected())
return;
}
int move = (int) itemList.get(0).moveToSelected();
if (move < 0) {
defaultMove(move);
} else {
defaultMove((int) itemList.get(itemList.size() - 1)
.moveToSelected());
}
for (ItemObject item : itemList) {
if (item.isSelected()) {
if (onSelectListener != null)
onSelectListener.endSelect(item.id, item.itemText);
break;
}
}
}

/**
* 初始化数据
*/

private void initData() {
isClearing = true;
itemList.clear();
for (int i = 0; i < dataList.size(); i++) {
ItemObject itmItemObject = new ItemObject();
itmItemObject.id = i;
itmItemObject.itemText = dataList.get(i);
itmItemObject.x = 0;
itmItemObject.y = i * unitHeight;
itemList.add(itmItemObject);
}
isClearing = false;
}

/**
* 移动的时候
*
* @param move
*/

private void actionMove(int move) {
for (ItemObject item : itemList) {
item.move(move);
}
invalidate();
}

/**
* 移动,线程中调用
*
* @param move
*/

private void actionThreadMove(int move) {
for (ItemObject item : itemList) {
item.move(move);
}
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
}

/**
* 松开的时候
*
* @param move
*/

private void actionUp(int move) {
int newMove = 0;
if (move > 0) {
for (int i = 0; i < itemList.size(); i++) {
if (itemList.get(i).isSelected()) {
newMove = (int) itemList.get(i).moveToSelected();
if (onSelectListener != null)
onSelectListener.endSelect(itemList.get(i).id,
itemList.get(i).itemText);
break;
}
}
} else {
for (int i = itemList.size() - 1; i >= 0; i--) {
if (itemList.get(i).isSelected()) {
newMove = (int) itemList.get(i).moveToSelected();
if (onSelectListener != null)
onSelectListener.endSelect(itemList.get(i).id,
itemList.get(i).itemText);
break;
}
}
}
for (ItemObject item : itemList) {
item.newY(move + 0);
}
slowMove(newMove);
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);

}

/**
* 缓慢移动
*
* @param move
*/

private synchronized void slowMove(final int move) {
new Thread(new Runnable() {

@Override
public void run() {
// 判断*
int m = move > 0 ? move : move * (-1);
int i = move > 0 ? 1 : (-1);
// 移动速度
int speed = 1;
while (true) {
m = m - speed;
if (m <= 0) {
for (ItemObject item : itemList) {
item.newY(m * i);
}
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
for (ItemObject item : itemList) {
item.newY(speed * i);
}
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (ItemObject item : itemList) {
if (item.isSelected()) {
if (onSelectListener != null)
onSelectListener.endSelect(item.id, item.itemText);
break;
}
}

}
}).start();
}

/**
* 移动到默认位置
*
* @param move
*/

private void defaultMove(int move) {
for (ItemObject item : itemList) {
item.newY(move);
}
Message rMessage = new Message();
rMessage.what = REFRESH_VIEW;
handler.sendMessage(rMessage);
}

/**
* 滑动监听
*/

private void onSelectListener() {
if (onSelectListener == null)
return;
for (ItemObject item : itemList) {
if (item.isSelected()) {
onSelectListener.selecting(item.id, item.itemText);
}
}
}

/**
* 绘制线条
*
* @param canvas
*/

private void drawLine(Canvas canvas) {

if (linePaint == null) {
linePaint = new Paint();
linePaint.setColor(lineColor);
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(lineWidth);
}

canvas.drawLine(0, controlHeight / 2 - unitHeight / 2 + 2,
controlWidth, controlHeight / 2 - unitHeight / 2 + 2, linePaint);
canvas.drawLine(0, controlHeight / 2 + unitHeight / 2 - 2,
controlWidth, controlHeight / 2 + unitHeight / 2 - 2, linePaint);
}

/**
* 绘制遮盖板
*
* @param canvas
*/

private void drawMask(Canvas canvas) {
LinearGradient lg = new LinearGradient(0, 0, 0, maskHight, 0x00f2f2f2,
0x00f2f2f2, TileMode.MIRROR);
Paint paint = new Paint();
paint.setShader(lg);
canvas.drawRect(0, 0, controlWidth, maskHight, paint);

LinearGradient lg2 = new LinearGradient(0, controlHeight - maskHight,
0, controlHeight, 0x00f2f2f2, 0x00f2f2f2, TileMode.MIRROR);
Paint paint2 = new Paint();
paint2.setShader(lg2);
canvas.drawRect(0, controlHeight - maskHight, controlWidth,
controlHeight, paint2);
}

/**
* 初始化,获取设置的属性
*
* @param context
* @param attrs
*/

private void init(Context context, AttributeSet attrs) {

TypedArray attribute = context.obtainStyledAttributes(attrs,
R.styleable.WheelView);
unitHeight = (int) attribute.getDimension(
R.styleable.WheelView_unitHight, 32);
normalFont = attribute.getDimension(
R.styleable.WheelView_normalTextSize, 14.0f);
selectedFont = attribute.getDimension(
R.styleable.WheelView_selectedTextSize, 22.0f);
itemNumber = attribute.getInt(R.styleable.WheelView_itemNumber, 7);
normalColor = attribute.getColor(
R.styleable.WheelView_normalTextColor, 0xff000000);
selectedColor = attribute.getColor(
R.styleable.WheelView_selectedTextColor, 0xffff0000);
lineColor = attribute.getColor(R.styleable.WheelView_lineColor,
0xff000000);
lineWidth = attribute.getDimension(R.styleable.WheelView_lineHeight, 2f);
maskHight = attribute.getDimension(R.styleable.WheelView_maskHight,
48.0f);
noEmpty = attribute.getBoolean(R.styleable.WheelView_noEmpty, true);
isEnable = attribute
.getBoolean(R.styleable.WheelView_isEnable, true);
attribute.recycle();

controlHeight = itemNumber * unitHeight;

}

/**
* 设置数据 (第一次)
*
* @param data
*/

public void setData(ArrayList<String> data) {
this.dataList = data;
initData();
}

/**
* 重置数据
*
* @param data
*/

public void resetData(ArrayList<String> data) {
setData(data);
//invalidate();
postInvalidate();
}

/**
* 获取返回项 id
*
* @return
*/

public int getSelected() {
for (ItemObject item : itemList) {
if (item.isSelected())
return item.id;
}
return -1;
}

/**
* 获取返回的内容
*
* @return
*/

public String getSelectedText() {
for (ItemObject item : itemList) {
if (item.isSelected())
return item.itemText;
}
return "";
}

/**
* 是否正在滑动
*
* @return
*/

public boolean isScrolling() {
return isScrolling;
}

/**
* 是否可用
*
* @return
*/

public boolean isEnable() {
return isEnable;
}

/**
* 设置是否可用
*
* @param isEnable
*/

public void setEnable(boolean isEnable) {
this.isEnable = isEnable;
}

/**
* 设置默认选项
*
* @param index
*/

public void setDefault(int index) {
if (index > itemList.size() - 1)
return;
float move = itemList.get(index).moveToSelected();
defaultMove((int) move);
}

/**
* 获取列表大小
*
* @return
*/

public int getListSize() {
if (itemList == null)
return 0;
return itemList.size();
}

/**
* 获取某项的内容
*
* @param index
* @return
*/

public String getItemText(int index) {
if (itemList == null)
return "";
return itemList.get(index).itemText;
}

/**
* 监听
*
* @param onSelectListener
*/

public void setOnSelectListener(OnSelectListener onSelectListener) {
this.onSelectListener = onSelectListener;
}

@SuppressLint("HandlerLeak")
Handler handler = new Handler() {

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case REFRESH_VIEW:
invalidate();
break;
default:
break;
}
}

};

/**
* 单条内容
*
* @author JiangPing
*/

private class ItemObject {
/**
* id
*/

public int id = 0;
/**
* 内容
*/

public String itemText = "";
/**
* x坐标
*/

public int x = 0;
/**
* y坐标
*/

public int y = 0;
/**
* 移动距离
*/

public int move = 0;
/**
* 字体画笔
*/

private TextPaint textPaint;
/**
* 字体范围矩形
*/

private Rect textRect;

public ItemObject() {
super();
}

/**
* 绘制自身
*
* @param canvas 画板
* @param containerWidth 容器宽度
*/

public void drawSelf(Canvas canvas, int containerWidth) {

if (textPaint == null) {
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
}

if (textRect == null)
textRect = new Rect();

// 判断是否被选择
if (isSelected()) {
textPaint.setColor(selectedColor);
// 获取距离标准位置的距离
float moveToSelect = moveToSelected();
moveToSelect = moveToSelect > 0 ? moveToSelect : moveToSelect
* (-1);
// 计算当前字体大小
float textSize = (float) normalFont
+ ((float) (selectedFont - normalFont) * (1.0f - (float) moveToSelect
/ (float) unitHeight));
textPaint.setTextSize(textSize);
} else {
textPaint.setColor(normalColor);
textPaint.setTextSize(normalFont);
}

// 返回包围整个字符串的最小的一个Rect区域
itemText = (String) TextUtils.ellipsize(itemText, textPaint, containerWidth,
TextUtils.TruncateAt.END);
textPaint.getTextBounds(itemText, 0, itemText.length(), textRect);
// 判断是否可视
if (!isInView())
return;

// 绘制内容
canvas.drawText(itemText, x + controlWidth / 2 - textRect.width()
/ 2, y + move + unitHeight / 2 + textRect.height() / 2,
textPaint);

}

/**
* 是否在可视界面内
*
* @return
*/

public boolean isInView() {
if (y + move > controlHeight
|| (y + move + unitHeight / 2 + textRect.height() / 2) < 0)
return false;
return true;
}

/**
* 移动距离
*
* @param _move
*/

public void move(int _move) {
this.move = _move;
}

/**
* 设置新的坐标
*
* @param _move
*/

public void newY(int _move) {
this.move = 0;
this.y = y + _move;
}

/**
* 判断是否在选择区域内
*
* @return
*/

public boolean isSelected() {
if ((y + move) >= controlHeight / 2 - unitHeight / 2 + 2
&& (y + move) <= controlHeight / 2 + unitHeight / 2 - 2)
return true;
if ((y + move + unitHeight) >= controlHeight / 2 - unitHeight / 2
+ 2
&& (y + move + unitHeight) <= controlHeight / 2
+ unitHeight / 2 - 2)
return true;
if ((y + move) <= controlHeight / 2 - unitHeight / 2 + 2
&& (y + move + unitHeight) >= controlHeight / 2
+ unitHeight / 2 - 2)
return true;
return false;
}

/**
* 获取移动到标准位置需要的距离
*/

public float moveToSelected() {
return (controlHeight / 2 - unitHeight / 2) - (y + move);
}
}

/**
* 选择监听
*
* @author JiangPing
*/

public interface OnSelectListener {
/**
* 结束选择
*
* @param id
* @param text
*/

public void endSelect(int id, String text);

/**
* 选中的内容
*
* @param id
* @param text
*/

public void selecting(int id, String text);

}
}

这个自定义组件最大的好处是可以直接在布局文件中改变选择和未选中的城市的字体大小和字体颜色非常方便这些自定义的属性都在attr文件中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/sc_white"
android:orientation="vertical">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="15dp"
android:text="取消"
android:textColor="@color/text_sub"
android:textSize="@dimen/text_size_small"
/>

<TextView
android:id="@+id/btn_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:padding="15dp"
android:text="确定"
android:textColor="@color/text_sub"
android:textSize="@dimen/text_size_small"
/>


</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">


<myaddressselector.myaddressselector.view.WheelView
android:id="@+id/id_province"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:itemNumber="5"
app:lineColor="#cccccc"
app:lineHeight="1dp"
app:maskHight="30dp"
app:noEmpty="true"
app:normalTextColor="#777"
app:normalTextSize="16sp"
app:selectedTextColor="#ff000000"
app:selectedTextSize="22sp"
app:unitHight="40dp"/>


<myaddressselector.myaddressselector.view.WheelView
android:id="@+id/id_city"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:itemNumber="5"
app:lineColor="#cccccc"
app:lineHeight="1dp"
app:maskHight="30dp"
app:noEmpty="true"
app:normalTextColor="#777"
app:normalTextSize="16sp"
app:selectedTextColor="#ff000000"
app:selectedTextSize="22sp"
app:unitHight="40dp"/>


<myaddressselector.myaddressselector.view.WheelView
android:id="@+id/id_district"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:itemNumber="5"
app:lineColor="#cccccc"
app:lineHeight="1dp"
app:maskHight="30dp"
app:noEmpty="true"
app:normalTextColor="#777"
app:normalTextSize="16sp"
app:selectedTextColor="#ff000000"
app:selectedTextSize="22sp"
app:unitHight="40dp"/>

</LinearLayout>

</LinearLayout>

四、最后界面实现

好用的城市选择(三级联动))
git地址:https://github.com/Army1990/AddressSelectorDemo
源码地址:http://download.csdn.net/detail/army_jun/9765371