Android自定义滑轮城市选择器
效果图:
简书地址:http://www.jianshu.com/p/b4c1a99dada8
源码下载地址:http://download.csdn.net/detail/qq_34908107/9881697
关于自定义的程序如下图:
主要的代码我就不贴上了,只贴一下LoopView代码:
package demo.spdbank.hcq.com.wheelview.wheel;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Created by qinzishuai on 2015/8/18.
*/
public class LoopView extends View {
private float scaleX = 1.05F;
public enum ACTION {
// 点击,滑翔(滑到尽头),拖拽事件
CLICK, FLING, DAGGLE
}
Context context;
Handler handler;
private GestureDetector gestureDetector;
OnItemSelectedListener onItemSelectedListener;
// Timer mTimer;
ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> mFuture;
Paint paintOuterText;
Paint paintCenterText;
Paint paintIndicator;
List<String> items;
int textSize;
int maxTextWidth;
int maxTextHeight;
int colorGray;
int colorBlack;
int colorLightGray;
// 条目间距倍数
float lineSpacingMultiplier;
boolean isLoop;
// 第一条线Y坐标值
int firstLineY;
int secondLineY;
int totalScrollY;
int initPosition;
private int selectedItem;
int preCurrentIndex;
int change;
// 显示几个条目
int itemsVisible;
int measuredHeight;
int measuredWidth;
int paddingLeft = 0;
int paddingRight = 0;
// 半圆周长
int halfCircumference;
// 半径
int radius;
private int mOffset = 0;
private float previousY;
long startTime = 0;
private Rect tempRect = new Rect();
public LoopView(Context context) {
super(context);
initLoopView(context);
}
public LoopView(Context context, AttributeSet attributeset) {
super(context, attributeset);
initLoopView(context);
}
public LoopView(Context context, AttributeSet attributeset, int defStyleAttr) {
super(context, attributeset, defStyleAttr);
initLoopView(context);
}
private void initLoopView(Context context) {
this.context = context;
handler = new MessageHandler(this);
gestureDetector = new GestureDetector(context, new LoopViewGestureListener(this));
gestureDetector.setIsLongpressEnabled(false);
lineSpacingMultiplier = 2.0F;
isLoop = true;
itemsVisible = 9;
textSize = 0;
colorGray = 0xffafafaf;
colorBlack = 0xff313131;
colorLightGray = 0xffc5c5c5;
totalScrollY = 0;
initPosition = -1;
initPaints();
setTextSize(16F);
}
private void initPaints() {
paintOuterText = new Paint();
paintOuterText.setColor(colorGray);
paintOuterText.setAntiAlias(true);
paintOuterText.setTypeface(Typeface.MONOSPACE);
paintOuterText.setTextSize(textSize);
paintCenterText = new Paint();
paintCenterText.setColor(colorBlack);
paintCenterText.setAntiAlias(true);
paintCenterText.setTextScaleX(scaleX);
paintCenterText.setTypeface(Typeface.MONOSPACE);
paintCenterText.setTextSize(textSize);
paintIndicator = new Paint();
paintIndicator.setColor(colorLightGray);
paintIndicator.setAntiAlias(true);
if (android.os.Build.VERSION.SDK_INT >= 11) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
}
private void remeasure() {
if (items == null) {
return;
}
measureTextWidthHeight();
halfCircumference = (int) (maxTextHeight * lineSpacingMultiplier * (itemsVisible - 1));
measuredHeight = (int) ((halfCircumference * 2) / Math.PI);
radius = (int) (halfCircumference / Math.PI);
measuredWidth = maxTextWidth + paddingLeft + paddingRight;
firstLineY = (int) ((measuredHeight - lineSpacingMultiplier * maxTextHeight) / 2.0F);
secondLineY = (int) ((measuredHeight + lineSpacingMultiplier * maxTextHeight) / 2.0F);
if (initPosition == -1) {
if (isLoop) {
initPosition = (items.size() + 1) / 2;
} else {
initPosition = 0;
}
}
preCurrentIndex = initPosition;
}
private void measureTextWidthHeight() {
for (int i = 0; i < items.size(); i++) {
String s1 = items.get(i);
paintCenterText.getTextBounds(s1, 0, s1.length(), tempRect);
int textWidth = tempRect.width();
if (textWidth > maxTextWidth) {
maxTextWidth = (int) (textWidth * scaleX);
}
paintCenterText.getTextBounds("\u661F\u671F", 0, 2, tempRect); // 星期
int textHeight = tempRect.height();
if (textHeight > maxTextHeight) {
maxTextHeight = textHeight;
}
}
}
void smoothScroll(ACTION action) {
cancelFuture();
if (action == ACTION.FLING || action == ACTION.DAGGLE) {
float itemHeight = lineSpacingMultiplier * maxTextHeight;
mOffset = (int) ((totalScrollY % itemHeight + itemHeight) % itemHeight);
if ((float) mOffset > itemHeight / 2.0F) {
mOffset = (int) (itemHeight - (float) mOffset);
} else {
mOffset = -mOffset;
}
}
mFuture = mExecutor.scheduleWithFixedDelay(new SmoothScrollTimerTask(this, mOffset), 0, 10, TimeUnit.MILLISECONDS);
}
// void smoothScroll() {
// int offset = (int) (totalScrollY % (lineSpacingMultiplier * maxTextHeight));
// cancelFuture();
// mFuture = mExecutor.scheduleWithFixedDelay(new SmoothScrollTimerTask(this, offset), 0, 10, TimeUnit.MILLISECONDS);
// }
protected final void scrollBy(float velocityY) {
cancelFuture();
// 修改这个值可以改变滑行速度
int velocityFling = 10;
mFuture = mExecutor.scheduleWithFixedDelay(new InertiaTimerTask(this, velocityY), 0, velocityFling, TimeUnit.MILLISECONDS);
}
public void cancelFuture() {
if (mFuture != null && !mFuture.isCancelled()) {
mFuture.cancel(true);
mFuture = null;
}
}
public final void setNotLoop() {
isLoop = false;
}
public final void setTextSize(float size) {
if (size > 0.0F) {
textSize = (int) (context.getResources().getDisplayMetrics().density * size);
paintOuterText.setTextSize(textSize);
paintCenterText.setTextSize(textSize);
}
}
public final void setInitPosition(int initPosition) {
if (initPosition < 0) {
this.initPosition = 0;
} else {
if (items != null && items.size() > initPosition) {
this.initPosition = initPosition;
}
}
}
public final void setListener(OnItemSelectedListener OnItemSelectedListener) {
onItemSelectedListener = OnItemSelectedListener;
}
public final void setItems(List<String> items) {
this.items = items;
remeasure();
invalidate();
}
@Override
public int getPaddingLeft() {
return paddingLeft;
}
@Override
public int getPaddingRight() {
return paddingRight;
}
// 设置左右内边距
public void setViewPadding(int left, int top, int right, int bottom) {
paddingLeft = left;
paddingRight = right;
}
public final int getSelectedItem() {
return selectedItem;
}
//
// protected final void scrollBy(float velocityY) {
// Timer timer = new Timer();
// mTimer = timer;
// timer.schedule(new InertiaTimerTask(this, velocityY, timer), 0L, 20L);
// }
protected final void onItemSelected() {
if (onItemSelectedListener != null) {
postDelayed(new OnItemSelectedRunnable(this), 200L);
}
}
/**
* 设置中间文字的scaleX的值,如果为1.0,则没有错位效果,
* link https://github.com/weidongjian/androidWheelView/issues/10
*
* @param scaleX
*/
public void setScaleX(float scaleX) {
this.scaleX = scaleX;
}
@Override
protected void onDraw(Canvas canvas) {
if (items == null) {
return;
}
String as[] = new String[itemsVisible];
change = (int) (totalScrollY / (lineSpacingMultiplier * maxTextHeight));
preCurrentIndex = initPosition + change % items.size();
if (!isLoop) {
if (preCurrentIndex < 0) {
preCurrentIndex = 0;
}
if (preCurrentIndex > items.size() - 1) {
preCurrentIndex = items.size() - 1;
}
} else {
if (preCurrentIndex < 0) {
preCurrentIndex = items.size() + preCurrentIndex;
}
if (preCurrentIndex > items.size() - 1) {
preCurrentIndex = preCurrentIndex - items.size();
}
}
int j2 = (int) (totalScrollY % (lineSpacingMultiplier * maxTextHeight));
// 设置as数组中每个元素的值
int k1 = 0;
while (k1 < itemsVisible) {
int l1 = preCurrentIndex - (itemsVisible / 2 - k1);
if (isLoop) {
while (l1 < 0) {
l1 = l1 + items.size();
}
while (l1 > items.size() - 1) {
l1 = l1 - items.size();
}
as[k1] = items.get(l1);
} else if (l1 < 0) {
as[k1] = "";
} else if (l1 > items.size() - 1) {
as[k1] = "";
} else {
as[k1] = items.get(l1);
}
k1++;
}
canvas.drawLine(0.0F, firstLineY, measuredWidth, firstLineY, paintIndicator);
canvas.drawLine(0.0F, secondLineY, measuredWidth, secondLineY, paintIndicator);
int j1 = 0;
while (j1 < itemsVisible) {
canvas.save();
// L(弧长)=α(弧度)* r(半径) (弧度制)
// 求弧度--> (L * π ) / (π * r) (弧长X派/半圆周长)
float itemHeight = maxTextHeight * lineSpacingMultiplier;
double radian = ((itemHeight * j1 - j2) * Math.PI) / halfCircumference;
// 弧度转换成角度(把半圆以Y轴为轴心向右转90度,使其处于第一象限及第四象限
float angle = (float) (90D - (radian / Math.PI) * 180D);
if (angle >= 90F || angle <= -90F) {
canvas.restore();
} else {
int translateY = (int) (radius - Math.cos(radian) * radius - (Math.sin(radian) * maxTextHeight) / 2D);
canvas.translate(0.0F, translateY);
canvas.scale(1.0F, (float) Math.sin(radian));
if (translateY <= firstLineY && maxTextHeight + translateY >= firstLineY) {
// 条目经过第一条线
canvas.save();
canvas.clipRect(0, 0, measuredWidth, firstLineY - translateY);
canvas.drawText(as[j1], getTextX(as[j1], paintOuterText, tempRect), maxTextHeight, paintOuterText);
canvas.restore();
canvas.save();
canvas.clipRect(0, firstLineY - translateY, measuredWidth, (int) (itemHeight));
canvas.drawText(as[j1], getTextX(as[j1], paintCenterText, tempRect), maxTextHeight, paintCenterText);
canvas.restore();
} else if (translateY <= secondLineY && maxTextHeight + translateY >= secondLineY) {
// 条目经过第二条线
canvas.save();
canvas.clipRect(0, 0, measuredWidth, secondLineY - translateY);
canvas.drawText(as[j1], getTextX(as[j1], paintCenterText, tempRect), maxTextHeight, paintCenterText);
canvas.restore();
canvas.save();
canvas.clipRect(0, secondLineY - translateY, measuredWidth, (int) (itemHeight));
canvas.drawText(as[j1], getTextX(as[j1], paintOuterText, tempRect), maxTextHeight, paintOuterText);
canvas.restore();
} else if (translateY >= firstLineY && maxTextHeight + translateY <= secondLineY) {
// 中间条目
canvas.clipRect(0, 0, measuredWidth, (int) (itemHeight));
canvas.drawText(as[j1], getTextX(as[j1], paintCenterText, tempRect), maxTextHeight, paintCenterText);
selectedItem = items.indexOf(as[j1]);
} else {
// 其他条目
canvas.clipRect(0, 0, measuredWidth, (int) (itemHeight));
canvas.drawText(as[j1], getTextX(as[j1], paintOuterText, tempRect), maxTextHeight, paintOuterText);
}
canvas.restore();
}
j1++;
}
}
// 绘制文字起始位置
private int getTextX(String a, Paint paint, Rect rect) {
paint.getTextBounds(a, 0, a.length(), rect);
// 获取到的是实际文字宽度
int textWidth = rect.width();
// 转换成绘制文字宽度
textWidth *= scaleX;
return (measuredWidth - textWidth) / 2;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
remeasure();
setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean eventConsumed = gestureDetector.onTouchEvent(event);
float itemHeight = lineSpacingMultiplier * maxTextHeight;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTime = System.currentTimeMillis();
cancelFuture();
previousY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float dy = previousY - event.getRawY();
previousY = event.getRawY();
totalScrollY = (int) (totalScrollY + dy);
// 边界处理。
if (!isLoop) {
float top = -initPosition * itemHeight;
float bottom = (items.size() - 1 - initPosition) * itemHeight;
if (totalScrollY < top) {
totalScrollY = (int) top;
} else if (totalScrollY > bottom) {
totalScrollY = (int) bottom;
}
}
break;
case MotionEvent.ACTION_UP:
default:
if (!eventConsumed) {
float y = event.getY();
double l = Math.acos((radius - y) / radius) * radius;
int circlePosition = (int) ((l + itemHeight / 2) / itemHeight);
float extraOffset = (totalScrollY % itemHeight + itemHeight) % itemHeight;
mOffset = (int) ((circlePosition - itemsVisible / 2) * itemHeight - extraOffset);
if ((System.currentTimeMillis() - startTime) > 120) {
// 处理拖拽事件
smoothScroll(ACTION.DAGGLE);
} else {
// 处理条目点击事件
smoothScroll(ACTION.CLICK);
}
}
break;
}
invalidate();
return true;
}
}