Android自定义WheelView
Posted z8z87878
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义WheelView相关的知识,希望对你有一定的参考价值。
没人可以预见未来,珍惜现在
看项目新原型,好像要用到时间选择控件,我看八成要自定义是吧,然后就去网上找了一点,说用这个WheelView做比较好,还能做什么省市县的。去github看了会,额,感觉可能为了功能强还是什么,感觉代码好多,四五百行,六七百行之类的。反正有时间,还是自己写一个吧。有问题自己也好改先看看效果
这里的WheelView继承的是view,我的思路是,数据和线自己画,然后再监听触摸事件,根据滑动距离改变数据和单行数据的高度,这里弄了个类来记录一行的条目
private static class ItemString
private String str; //存储要显示的数据
private int index; //存储显示的索引
private float height; //存储显示的高度位置
public ItemString()
public ItemString(String str, int index,float height)
this.str = str;
this.index = index;
this.height = height;
public String getStr()
return str;
public void setStr(String str)
this.str = str;
public int getIndex()
return index;
public void setIndex(int index)
this.index = index;
public float getHeight()
return height;
public void setHeight(float height)
this.height = height;
先来看看不能滑动时什么样子
/**
* Created by liaoyalong on 2016-12-28.
*/
public class WheelView extends View
private int textSize = dp2px(15); //普通字体大小
private int selectTextSize = dp2px(24); //选中字体的大小
private Paint normalPaint; //普通字体画笔
private Paint selectPaint; //选中字体画笔
private Paint linePaint; //线画笔
private int height = 0; //控件的高度
private int width = 0; //控件宽度
private int textHeight = 0; //普通字体高度
private int selectTextHeight = 0; //选中字体高度
private List<String> mStringList; //存储传进来的数据
private List<ItemString> itemList = new ArrayList<>(); //存储显示的条目,默认显示3行,既默认存储5条。第一行和最后一行不可见
private List<Float> itemHeight = new ArrayList<>(); //存储每个条目所在控件的高度
private float selectHeight; //存储选中条目所在控件的高度
private int totalText = 0; //总共有多少条数据
private int currentIndex = Integer.MAX_VALUE; //当前选中数据的索引值
private int displayCount = 3; //默认显示3行
private float lineHeight = 0; //行高
private int mCenterIndex; //中间,既选中行的索引
public WheelView(Context context)
this(context,null);
public WheelView(Context context, AttributeSet attrs)
this(context, attrs,0);
public WheelView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
normalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
normalPaint.setTextAlign(Paint.Align.CENTER);
normalPaint.setTextSize(textSize);
normalPaint.setColor(Color.GRAY);
normalPaint.setTypeface(Typeface.SERIF);
normalPaint.setTextSkewX(-0.3f); //字体倾斜度调整
selectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
selectPaint.setTextAlign(Paint.Align.CENTER);
selectPaint.setTextSize(selectTextSize);
selectPaint.setColor(Color.RED);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(Color.BLUE);
//所有需要自己修改的字体效果都可以自己加
/*----------------------------如有需要,自行定义attr增加属性可控性--------------------------------*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) //当测量完成后,获得我们想要的不变的控件宽高
super.onSizeChanged(w, h, oldw, oldh);
height = getHeight();
width = getWidth();
lineHeight = height/3.0f;
Rect bound = new Rect();
normalPaint.getTextBounds("123",0,1,bound);
textHeight = bound.height();
selectPaint.getTextBounds("123",0,1,bound);
selectTextHeight = bound.height();
/**保证传入的数据大于等于2,注意要在WheelView的onSizeChanged执行完后调用,因为这里用到了测量后的宽高,所以可mWheelView.getViewTreeObserver().addOnGlobalLayoutListener
* @param list
*/
public void setListText(List<String> list)
mStringList = list;
totalText = list.size(); //总共有多少条数据
currentIndex = currentIndex/2 - currentIndex/2 % (totalText); //当前中间的那条,这样为了循环移动
mCenterIndex = (displayCount+2)/2; //中间的是第几行
itemList.clear();
itemHeight.clear();
for (int i = 0; i < displayCount + 2; i++) //存储每条条目的初始高度,和条目
float hei = (lineHeight * (i-1) + lineHeight/2.0f + textHeight/2.0f);
itemHeight.add(hei);
int index = currentIndex + i -mCenterIndex;
String text = list.get(index % totalText); //取%防止越界,为了循环移动
itemList.add( new ItemString(text, index, hei) );
selectHeight = height/2.0f + selectTextHeight/2.0f; //选中条目所在控件的高度位置
Float f = itemHeight.get(mCenterIndex);
f = selectHeight;
itemList.get(mCenterIndex).setHeight(selectHeight);
postInvalidate(); //重新绘制
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas); //画view的背景色
for (int i = 1; i < displayCount; i++) //画线
canvas.drawLine(0,lineHeight * i,width,lineHeight * i,linePaint);
for (int i = 0; i < itemList.size(); i++)
ItemString itemString = itemList.get(i);
if (i != mCenterIndex) //如果不是选中行
canvas.drawText(itemString.getStr(),width/2.0f,itemString.getHeight(),normalPaint);
else
canvas.drawText(itemString.getStr(),width/2.0f,itemString.getHeight(),selectPaint);//选中行换画笔
不能滑动,静态的花点时间,耐心点就可以画出来了,看看怎么滑动判断的吧
private float downY; //按下时的位置
private float delY; //移动距离
@Override
public boolean onTouchEvent(MotionEvent event)
switch (event.getAction())
case MotionEvent.ACTION_DOWN:
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
delY = event.getY() - downY;
if (nextLine(delY))
exchangeLine(true);
else if (preLine(delY))
exchangeLine(false);
else
for (int i = 0; i < itemList.size(); i++) //不换行时根据原来的高度加上滑动距离改变位置
itemList.get(i).setHeight(itemHeight.get(i)+delY);
break;
case MotionEvent.ACTION_UP:
downY = 0;
for (int i = 0; i < itemList.size(); i++)
itemList.get(i).setHeight(itemHeight.get(i));
break;
//设置滑动透明度变化,需要自行调整
int alphy = (int) (255 - 330 * Math.abs(itemList.get(0).getHeight() - itemHeight.get(0)) / (lineHeight/2 + selectHeight/2));
normalPaint.setAlpha(alphy);
selectPaint.setAlpha(alphy);
postInvalidate();
return super.onTouchEvent(event);
/**交换行
* @param next
*/
private void exchangeLine(boolean next)
if(next)
itemList.remove(0); //移除第一行
int nextIndex = (itemList.get(itemList.size()-1).getIndex()+1);
itemList.add(new ItemString(mStringList.get(nextIndex % totalText),nextIndex,0)); //添加新数据到最后一行(最后一行和第一行不滑动都看不到)
for (int i = 0; i < itemList.size(); i++)
itemList.get(i).setHeight(itemHeight.get(i)); //重新设置高度
else
itemList.remove(itemList.size()-1); //移除最后一行
int preIndex = (itemList.get(0).getIndex()-1);
itemList.add(0,new ItemString(mStringList.get(preIndex % totalText),preIndex,0)); //添加新数据到第一行
for (int i = 0; i < itemList.size(); i++)
itemList.get(i).setHeight(itemHeight.get(i)); //重新设置高度
/**是否上一行
* @param delY
* @return
*/
private boolean preLine(float delY)
float move = lineHeight/2 + selectTextHeight/2;
if (delY > move)
downY += move;
currentIndex -= 1;
return true;
return false;
/**是否下一行
* @param delY
* @return
*/
private boolean nextLine(float delY)
float move = lineHeight/2 + selectTextHeight/2;
if (delY < 0 && delY + move < 0)
downY -= move;
currentIndex += 1;
return true;
return false;
public int getSelectIndex()
return currentIndex % totalText;
好吧,我承认,下班了,我想走了,自己看注释吧,我觉得我注释挺详细的。等下贴完整代码在评论区
package com.example.wheelview.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* Created by liaoyalong on 2016-12-28.
*/
public class WheelView extends View
private int textSize = dp2px(15); //普通字体大小
private int selectTextSize = dp2px(24); //选中字体的大小
private Paint normalPaint; //普通字体画笔
private Paint selectPaint; //选中字体画笔
private Paint linePaint; //线画笔
private int height = 0; //控件的高度
private int width = 0; //控件宽度
private int textHeight = 0; //普通字体高度
private int selectTextHeight = 0; //选中字体高度
private List<String> mStringList; //存储传进来的数据
private List<ItemString> itemList = new ArrayList<>(); //存储显示的条目,默认显示3行,既默认存储5条。第一行和最后一行不可见
private List<Float> itemHeight = new ArrayList<>(); //存储每个条目所在控件的高度
private float selectHeight; //存储选中条目所在控件的高度
private int totalText = 0; //总共有多少条数据
private int currentIndex = Integer.MAX_VALUE; //当前选中数据的索引值
private int displayCount = 3; //默认显示3行
private float lineHeight = 0; //行高
private int mCenterIndex; //中间,既选中行的索引
public WheelView(Context context)
this(context,null);
public WheelView(Context context, AttributeSet attrs)
this(context, attrs,0);
public WheelView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
normalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
normalPaint.setTextAlign(Paint.Align.CENTER);
normalPaint.setTextSize(textSize);
normalPaint.setColor(Color.GRAY);
normalPaint.setTypeface(Typeface.SERIF);
normalPaint.setTextSkewX(-0.3f); //字体倾斜度调整
selectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
selectPaint.setTextAlign(Paint.Align.CENTER);
selectPaint.setTextSize(selectTextSize);
selectPaint.setColor(Color.RED);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(Color.BLUE);
//所有需要自己修改的字体效果都可以自己加
/*----------------------------如有需要,自行定义attr增加属性可控性--------------------------------*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) //当测量完成后,获得我们想要的不变的控件宽高
super.onSizeChanged(w, h, oldw, oldh);
height = getHeight();
width = getWidth();
lineHeight = height/3.0f;
Rect bound = new Rect();
normalPaint.getTextBounds("123",0,1,bound);
textHeight = bound.height();
selectPaint.getTextBounds("123",0,1,bound);
selectTextHeight = bound.height();
/**保证传入的数据大于等于2,注意要在WheelView的onSizeChanged执行完后调用,因为这里用到了测量后的宽高,所以可mWheelView.getViewTreeObserver().addOnGlobalLayoutListener
* @param list
*/
public void setListText(List<String> list)
mStringList = list;
totalText = list.size(); //总共有多少条数据
currentIndex = currentIndex/2 - currentIndex/2 % (totalText); //当前中间的那条,这样为了循环移动
mCenterIndex = (displayCount+2)/2; //中间的是第几行
itemList.clear();
itemHeight.clear();
for (int i = 0; i < displayCount + 2; i++) //存储每条条目的初始高度,和条目
float hei = (lineHeight * (i-1) + lineHeight/2.0f + textHeight/2.0f);
itemHeight.add(hei);
int index = currentIndex + i -mCenterIndex;
String text = list.get(index % totalText); //取%防止越界,为了循环移动
itemList.add( new ItemString(text, index, hei) );
selectHeight = height/2.0f + selectTextHeight/2.0f; //选中条目所在控件的高度位置
Float f = itemHeight.get(mCenterIndex);
f = selectHeight;
itemList.get(mCenterIndex).setHeight(selectHeight);
postInvalidate(); //重新绘制
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas); //画view的背景色
for (int i = 1; i < displayCount; i++) //画线
canvas.drawLine(0,lineHeight * i,width,lineHeight * i,linePaint);
for (int i = 0; i < itemList.size(); i++)
ItemString itemString = itemList.get(i);
if (i != mCenterIndex) //如果不是选中行
canvas.drawText(itemString.getStr(),width/2.0f,itemString.getHeight(),normalPaint);
else
canvas.drawText(itemString.getStr(),width/2.0f,itemString.getHeight(),selectPaint);//选中行换画笔
private float downY; //按下时的位置
private float delY; //移动距离
@Override
public boolean onTouchEvent(MotionEvent event)
switch (event.getAction())
case MotionEvent.ACTION_DOWN:
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
delY = event.getY() - downY;
if (nextLine(delY))
exchangeLine(true);
else if (preLine(delY))
exchangeLine(false);
else
for (int i = 0; i < itemList.size(); i++) //不换行时根据原来的高度加上滑动距离改变位置
itemList.get(i).setHeight(itemHeight.get(i)+delY);
break;
case MotionEvent.ACTION_UP:
downY = 0;
for (int i = 0; i < itemList.size(); i++)
itemList.get(i).setHeight(itemHeight.get(i));
break;
//设置滑动透明度变化,需要自行调整
int alphy = (int) (255 - 330 * Math.abs(itemList.get(0).getHeight() - itemHeight.get(0)) / (lineHeight/2 + selectHeight/2));
normalPaint.setAlpha(alphy);
selectPaint.setAlpha(alphy);
postInvalidate();
return super.onTouchEvent(event);
/**交换行
* @param next
*/
private void exchangeLine(boolean next)
if(next)
itemList.remove(0); //移除第一行
int nextIndex = (itemList.get(itemList.size()-1).getIndex()+1);
itemList.add(new ItemString(mStringList.get(nextIndex % totalText),nextIndex,0)); //添加新数据到最后一行(最后一行和第一行不滑动都看不到)
for (int i = 0; i < itemList.size(); i++)
itemList.get(i).setHeight(itemHeight.get(i)); //重新设置高度
else
itemList.remove(itemList.size()-1); //移除最后一行
int preIndex = (itemList.get(0).getIndex()-1);
itemList.add(0,new ItemString(mStringList.get(preIndex % totalText),preIndex,0)); //添加新数据到第一行
for (int i = 0; i < itemList.size(); i++)
itemList.get(i).setHeight(itemHeight.get(i)); //重新设置高度
/**是否上一行
* @param delY
* @return
*/
private boolean preLine(float delY)
float move = lineHeight/2 + selectTextHeight/2;
if (delY > move)
downY += move;
currentIndex -= 1;
return true;
return false;
/**是否下一行
* @param delY
* @return
*/
private boolean nextLine(float delY)
float move = lineHeight/2 + selectTextHeight/2;
if (delY < 0 && delY + move < 0)
downY -= move;
currentIndex += 1;
return true;
return false;
public int getSelectIndex()
return currentIndex % totalText;
private static class ItemString
private String str; //存储要显示的数据
private int index; //存储显示的索引
private float height; //存储显示的高度位置
public ItemString()
public ItemString(String str, int index,float height)
this.str = str;
this.index = index;
this.height = height;
public String getStr()
return str;
public void setStr(String str)
this.str = str;
public int getIndex()
return index;
public void setIndex(int index)
this.index = index;
public float getHeight()
return height;
public void setHeight(float height)
this.height = height;
private int dp2px(int dp)
return (int) (getResources().getDisplayMetrics().density * dp);
activity
public class MainActivity extends AppCompatActivity
private WheelView mWheelView;
String[] text = "0","1","2","3","4","5","6","7","8","9","10";
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWheelView = (WheelView) findViewById(R.id.wheel);
mWheelView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
@Override
public void onGlobalLayout()
mWheelView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
mWheelView.setListText(Arrays.asList(text));
);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
int index = mWheelView.getSelectIndex();
Toast.makeText(MainActivity.this,"当前索引为"+index+",对应的值为"+text[index],Toast.LENGTH_SHORT).show();
);
以上是关于Android自定义WheelView的主要内容,如果未能解决你的问题,请参考以下文章