Android自定义控件_垂直滚动器PickerView
来源:互联网 发布:linux tcp连接日志 编辑:程序博客网 时间:2024/05/16 15:45
转载请注明出处:http://blog.csdn.net/ljmingcom304/article/details/50393098
本文出自:【梁敬明的博客】
1.效果展示
前段时间公司的产品设计了一款三级联动的垂直滚动器PickerView,当时觉得挺简单的,实际开发中还是遇到了点小障碍,于是上网上搜了段代码用,代码是别人写的,直接Copy过来始终也是别人的东西,只有学到手才真正的属于自己,于是稍微研究了下,并通过自己的方式实现。下面先上张效果图,实现了其中一组数据的滚动选择,其他两组联动数据的实现原理是一样的,就是麻烦了点。
2.实现过程
初始化画笔和存放数据的集合。
private void init() { mDataList = new ArrayList<String>(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Style.FILL); mPaint.setAntiAlias(true); mPaint.setTextAlign(Align.CENTER); mPaint.setColor(Color.BLACK);}在效果图中看以看到,当字体位于View中心时尺寸最大,位于两侧时尺寸最小。因此,需要设置字体的最大尺寸、最小尺寸以及字体间的距离。
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mViewHeight = getMeasuredHeight(); mViewWidth = getMeasuredWidth(); // 按照View的高度计算字体大小 mMaxTextSize = mViewHeight / 6.0f; mMinTextSize = mMaxTextSize / 1.5f; mDistance = MARGIN_ALPHA * mViewHeight;}初始化默认绘制,首先将当前选中的元素绘制到View的中心位置,索引在当前元素前的绘制到当前元素的上方,索引在当前元素后的绘制到当前元素的下方。
/** * @param position:当前元素前后第几个位置,若为当前元素position为0 * @param direction:绘制的元素相对于当前元素的方向,上方为-1,下方为1,当前元素默认为下方 */private void drawText(Canvas canvas, int position, int direction) { // 元素距离控件中心的相对距离 float offset = (float) (mDistance * position + direction * mMoveLen); // 缩放倍数:位于控件中心时,缩放倍数是1;控件中心的前一个数据和后一个数据的缩放倍数是0 float f = (float) (1 - Math.pow(offset / mDistance, 2));// 按抛物线缩放 // 当数据与控件中心的距离,大于数据间的初始距离时,不再进行缩放 float scale = f < 0 ? 0 : f; // 字体尺寸 float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize; // 字体大小与透明度 mPaint.setTextSize(size); mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha)); float x = (float) (mViewWidth / 2.0); float y = getTextBaseLine((float) (mViewHeight / 2.0 + direction * offset)); canvas.drawText(mDataList.get(mCurrentSelected + direction * position), x, y, mPaint);}处理View的触摸滑动事件,包括触摸按下事件、触摸滑动事件和触摸离开事件。通过效果图可以知道,在字体没有滑动到初始化位置时手指离开,字体会自动滑动到初始化的位置,这里通过定时器的方式实现字体的自动滑动。 当手指按下时,首先要初始化定时任务,将上次的定时任务清除,并记录下手指按下的位置。
private void doDown(MotionEvent event) { if(mHandler!=null){ mHandler.removeCallbacks(mRunnable); } mLastDownY = event.getY();}当手指进行滑动时,获取每次滑动的距离,当滑动的距离超过字体间相对距离的一半时,调整字体在集合中的位置。向上滑动时,第一个元素移动到最后位置,其余元素整体前移,将每个元素的滑动距离整体向下移动一个相对位置。向上滑动时,最后一个元素移动到集合的首位,其余元素整体后移,将每个元素的滑动距离整体向上移动一个相对位置。
private void doMove(MotionEvent event) { mMoveLen += (event.getY() - mLastDownY); // 当向下滑动时,mMoveLen为正;当向上滑动时,mMoveLen为负 if (mMoveLen > mDistance / 2) { // 往下滑超过离开距离 String tail = mDataList.get(mDataList.size() - 1); mDataList.remove(mDataList.size() - 1); mDataList.add(0, tail); // 重新设置数据的位置,将其整体上移 mMoveLen -= mDistance; } else if (mMoveLen < -mDistance / 2) { // 往上滑超过离开距离 String head = mDataList.get(0); mDataList.remove(0); mDataList.add(head); // 重新设置数据的位置,将其整体下移 mMoveLen += mDistance; } mLastDownY = event.getY(); invalidate();}
当手指离开时,开启定时任务,保证字体的初始化相对View的位置不发生改变。
private void doUp(MotionEvent event) { // 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置 if (Math.abs(mMoveLen) < 0.0001) { mMoveLen = 0; return; } if(mHandler!=null){ mHandler.removeCallbacks(mRunnable); } mHandler.postDelayed(mRunnable, 10);}
开启定时任务,判断当前字体已经滑动的距离,每隔指定时间自动滑动指定的距离,直到字体滑动到初始化相对View的位置。
private Runnable mRunnable = new Runnable() { @Override public void run() { if (Math.abs(mMoveLen) < SPEED) { mMoveLen = 0; if (mHandler != null) { mHandler.removeCallbacks(this); // 事件监听 if (mSelectListener != null) mSelectListener.onSelect(mDataList .get(mCurrentSelected)); } } else{ // 这里mMoveLen / Math.abs(mMoveLen)是为了保有mMoveLen的正负号,以实现上滚或下滚 // 用于将数据回滚到起始位置或者终点位置 mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED; mHandler.postDelayed(this, 10); } invalidate(); }};
3.示例代码
下面上完整代码,首先是布局文件。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/darker_gray" > <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#ffffff" > <com.example.pickerview.PickerView android:id="@+id/minute_pv" android:layout_width="160dp" android:layout_height="320dp" /> </RelativeLayout></RelativeLayout>
然后是Activity。
public class MainActivity extends Activity { PickerView pv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pv = (PickerView) findViewById(R.id.minute_pv); List<String> data = new ArrayList<String>(); for (int i = 0; i < 10; i++) { data.add("0" + i); } pv.setData(data); pv.setOnSelectListener(new onSelectListener() { @Override public void onSelect(String text) { Toast.makeText(MainActivity.this, "选择" + text + " 分", Toast.LENGTH_SHORT).show(); } }); }}
最后是实现代码。
public class PickerView extends View {/** * text之间间距和minTextSize之比,手工进行配置 */public static final float MARGIN_ALPHA = 0.35f;/** * 自动回滚到中间的速度 */public static final float SPEED = 2;private List<String> mDataList;/** * 选中的位置,这个位置是mDataList的中心位置,一直不变 */private int mCurrentSelected;private Paint mPaint;private float mMaxTextSize;private float mMinTextSize;private float mMaxTextAlpha = 255;private float mMinTextAlpha = 120;// 要绘制的数据位于当前数据的上方或下方private int mUp = -1;private int mDown = 1;/** 相邻数据间的距离 */private float mDistance;private int mViewHeight;private int mViewWidth;private float mLastDownY;/** 滑动的距离 */private float mMoveLen = 0;private Handler mHandler = new Handler();private onSelectListener mSelectListener;public PickerView(Context context) { this(context, null);}public PickerView(Context context, AttributeSet attrs) { this(context, attrs, 0);}public PickerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init();}interface onSelectListener { void onSelect(String text);}public void setOnSelectListener(onSelectListener listener) { mSelectListener = listener;}/** 添加数据 */public void setData(List<String> datas) { mDataList = datas; mCurrentSelected = datas.size() / 2; invalidate();}public void setSelected(int selected) { mCurrentSelected = selected;}private void init() { mDataList = new ArrayList<String>(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Style.FILL); mPaint.setAntiAlias(true); mPaint.setTextAlign(Align.CENTER); mPaint.setColor(Color.BLACK);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mViewHeight = getMeasuredHeight(); mViewWidth = getMeasuredWidth(); // 按照View的高度计算字体大小 mMaxTextSize = mViewHeight / 6.0f; mMinTextSize = mMaxTextSize / 1.5f; mDistance = MARGIN_ALPHA * mViewHeight;}@Overrideprotected void onDraw(Canvas canvas) { // 绘制当前元素 drawText(canvas, 0, mDown); // 绘制上方元素 for (int i = 1; (mCurrentSelected - i) >= 0; i++) { drawText(canvas, i, mUp); } // 绘制下方元素 for (int i = 1; (mCurrentSelected + i) < mDataList.size(); i++) { drawText(canvas, i, mDown); }}/** * @param position:当前元素前后第几个位置,若为当前元素position为0 * @param direction:绘制的元素相对于当前元素的方向,上方为-1,下方为1,当前元素默认为下方 */private void drawText(Canvas canvas, int position, int direction) { // 元素距离控件中心的相对距离 float offset = (float) (mDistance * position + direction * mMoveLen); // 缩放倍数:位于控件中心时,缩放倍数是1;控件中心的前一个数据和后一个数据的缩放倍数是0 float f = (float) (1 - Math.pow(offset / mDistance, 2));// 按抛物线缩放 // 当数据与控件中心的距离,大于数据间的初始距离时,不再进行缩放 float scale = f < 0 ? 0 : f; // 字体尺寸 float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize; // 字体大小与透明度 mPaint.setTextSize(size); mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha)); float x = (float) (mViewWidth / 2.0); float y = getTextBaseLine((float) (mViewHeight / 2.0 + direction * offset)); canvas.drawText(mDataList.get(mCurrentSelected + direction * position), x, y, mPaint);}// 调整字体的位置,使其垂直居中进行绘制private float getTextBaseLine(float p) { FontMetricsInt fmi = mPaint.getFontMetricsInt(); float baseline = (float) (p - (fmi.bottom / 2.0 + fmi.top / 2.0)); return baseline;}@Overridepublic boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: doDown(event); break; case MotionEvent.ACTION_MOVE: doMove(event); break; case MotionEvent.ACTION_UP: doUp(event); break; } return true;}private void doDown(MotionEvent event) { if(mHandler!=null){ mHandler.removeCallbacks(mRunnable); } mLastDownY = event.getY();}private void doMove(MotionEvent event) { mMoveLen += (event.getY() - mLastDownY); // 当向下滑动时,mMoveLen为正;当向上滑动时,mMoveLen为负 if (mMoveLen > mDistance / 2) { // 往下滑超过离开距离 String tail = mDataList.get(mDataList.size() - 1); mDataList.remove(mDataList.size() - 1); mDataList.add(0, tail); // 重新设置数据的位置,将其整体上移 mMoveLen -= mDistance; } else if (mMoveLen < -mDistance / 2) { // 往上滑超过离开距离 String head = mDataList.get(0); mDataList.remove(0); mDataList.add(head); // 重新设置数据的位置,将其整体下移 mMoveLen += mDistance; } mLastDownY = event.getY(); invalidate();}private void doUp(MotionEvent event) { // 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置 if (Math.abs(mMoveLen) < 0.0001) { mMoveLen = 0; return; } if(mHandler!=null){ mHandler.removeCallbacks(mRunnable); } mHandler.postDelayed(mRunnable, 10);}private Runnable mRunnable = new Runnable() { @Override public void run() { if (Math.abs(mMoveLen) < SPEED) { mMoveLen = 0; if (mHandler != null) { mHandler.removeCallbacks(this); // 事件监听 if (mSelectListener != null) mSelectListener.onSelect(mDataList .get(mCurrentSelected)); } } else{ // 这里mMoveLen / Math.abs(mMoveLen)是为了保有mMoveLen的正负号,以实现上滚或下滚 // 用于将数据回滚到起始位置或者终点位置 mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED; mHandler.postDelayed(this, 10); } invalidate(); }};
}
1 0
- Android自定义控件_垂直滚动器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战—滚动选择器PickerView
- Android自定义控件实战——滚动选择器PickerView
- Android自定义控件实战—滚动选择器PickerView(滚动变化字体)
- Android 自定义View,实现滚动选择器PickerView
- android 自定义View 滚动选择器PickerView
- Android自定义垂直滚动自动选择日期控件
- 自定义控件-PickerView
- android自定义View-垂直滚动的TextView
- android自定义View-垂直滚动的TextView
- 华为oj:图片管理
- 腾讯分析系统架构解析
- Android —— Studio导入Eclipse项目方法以及出现的问题
- Java构造函数 为什么需要无参构造函数
- CodeForces 570B,C
- Android自定义控件_垂直滚动器PickerView
- 主队列中添加的同步操作永远不会被执行,会死锁原因
- Android gradient设置布局的背景渐变效果
- 探真无阻塞加载javascript脚本技术,我们会发现很多意想不到的秘密
- spark aggregateByKey函数使用问题
- Android SDK Manager 更新代理配置和代理清单
- 802.11协议帧格式、Wi-Fi连接交互过程、无线破解入门研究
- Fiddler转发请求与修改响应
- Android属性动画完全解析(上),初识属性动画的基本用法