1. package com.example.healthembed.util;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import java.util.Timer;
  6. import java.util.TimerTask;
  7.  
  8. import android.content.Context;
  9. import android.graphics.Canvas;
  10. import android.graphics.Paint;
  11. import android.graphics.Paint.Align;
  12. import android.graphics.Paint.FontMetricsInt;
  13. import android.graphics.Paint.Style;
  14. import android.os.Handler;
  15. import android.os.Message;
  16. import android.util.AttributeSet;
  17. import android.util.Log;
  18. import android.view.MotionEvent;
  19. import android.view.View;
  20.  
  21. /**
  22. * 滚动选择器
  23. *
  24. * @author chenjing
  25. *
  26. */
  27. public class PickerView extends View
  28. {
  29.  
  30. public static final String TAG = "PickerView";
  31. /**
  32. * text之间间距和minTextSize之比
  33. */
  34. public static final float MARGIN_ALPHA = 2.8f;
  35. /**
  36. * 自动回滚到中间的速度
  37. */
  38. public static final float SPEED = 2;
  39.  
  40. private List<String> mDataList;
  41. /**
  42. * 选中的位置,这个位置是mDataList的中心位置,一直不变
  43. */
  44. private int mCurrentSelected;
  45. private Paint mPaint;
  46.  
  47. private float mMaxTextSize = 80;
  48. private float mMinTextSize = 40;
  49.  
  50. private float mMaxTextAlpha = 255;
  51. private float mMinTextAlpha = 120;
  52.  
  53. private int mColorText = 0x333333;
  54.  
  55. private int mViewHeight;
  56. private int mViewWidth;
  57.  
  58. private float mLastDownY;
  59. /**
  60. * 滑动的距离
  61. */
  62. private float mMoveLen = 0;
  63. private boolean isInit = false;
  64. private onSelectListener mSelectListener;
  65. private Timer timer;
  66. private MyTimerTask mTask;
  67.  
  68. Handler updateHandler = new Handler()
  69. {
  70.  
  71. @Override
  72. public void handleMessage(Message msg)
  73. {
  74. if (Math.abs(mMoveLen) < SPEED)
  75. {
  76. mMoveLen = 0;
  77. if (mTask != null)
  78. {
  79. mTask.cancel();
  80. mTask = null;
  81. performSelect();
  82. }
  83. } else
  84. {
  85. // 这里mMoveLen / Math.abs(mMoveLen)是为了保有mMoveLen的正负号,以实现上滚或下滚
  86. mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED;
  87. }
  88.  
  89. invalidate();//重绘
  90. }
  91.  
  92. };
  93.  
  94. public PickerView(Context context)
  95. {
  96. super(context);
  97. init();
  98. }
  99.  
  100. public PickerView(Context context, AttributeSet attrs)
  101. {
  102. super(context, attrs);
  103. init();
  104. }
  105.  
  106. public void setOnSelectListener(onSelectListener listener)
  107. {
  108. mSelectListener = listener;
  109. }
  110.  
  111. private void performSelect()
  112. {
  113. if (mSelectListener != null)
  114. mSelectListener.onSelect(mDataList.get(mCurrentSelected));
  115. //选中的位置,这个位置是mDataList的中心位置,一直不变
  116. }
  117.  
  118. public void setData(List<String> datas)
  119. {
  120. mDataList = datas;
  121. mCurrentSelected = datas.size() / 2;
  122. invalidate();
  123. }
  124.  
  125. public void setSelected(int selected)
  126. {
  127. mCurrentSelected = selected;
  128. }
  129.  
  130. private void moveHeadToTail()
  131. {
  132. String head = mDataList.get(0);
  133. mDataList.remove(0);
  134. mDataList.add(head);
  135. }
  136.  
  137. private void moveTailToHead()
  138. {
  139. String tail = mDataList.get(mDataList.size() - 1);
  140. mDataList.remove(mDataList.size() - 1);
  141. mDataList.add(0, tail);
  142. //Inserts the specified element at the specified position in this list (optional operation)
  143. }
  144.  
  145. @Override
  146. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  147. {
  148. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  149. mViewHeight = getMeasuredHeight();
  150. Log.e(TAG,String.valueOf(mViewHeight));
  151. mViewWidth = getMeasuredWidth();
  152. Log.e(TAG,String.valueOf(mViewWidth));
  153. // 按照View的高度计算字体大小
  154. mMaxTextSize = mViewWidth / 9.0f;
  155. mMinTextSize = mMaxTextSize / 2f;
  156. isInit = true;
  157. invalidate();//调用invalidate()方法,请求重新draw()
  158. }
  159.  
  160. private void init()
  161. {
  162. timer = new Timer();
  163. mDataList = new ArrayList<String>();
  164. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
  165. mPaint.setStyle(Style.FILL);
  166. mPaint.setTextAlign(Align.CENTER);//设置绘制文字的对齐方向
  167. mPaint.setColor(mColorText);
  168. }
  169. public void initDataHigh() {
  170.  
  171. for(int j = 0; j<35; j++) {
  172. moveTailToHead();
  173. }
  174.  
  175. }
  176.  
  177. public void initDataLow() {
  178.  
  179. for(int j = 0; j<50; j++) {
  180. moveTailToHead();
  181. }
  182.  
  183. }
  184.  
  185. @Override
  186. protected void onDraw(Canvas canvas)
  187. {
  188. super.onDraw(canvas);
  189. // 根据index绘制view
  190. if (isInit)
  191. drawData(canvas);
  192. }
  193.  
  194. private void drawData(Canvas canvas)
  195. {
  196. // 先绘制选中的text再往上往下绘制其余的text
  197. float scale = parabola(mViewHeight / 4.0f, mMoveLen);
  198. float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
  199. mPaint.setTextSize(size);
  200. mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
  201. // text居中绘制,注意baseline的计算才能达到居中,y值是text中心坐标
  202. /* float x = (float) (mViewWidth / 2.0);
  203. float y = (float) (mViewHeight / 2.0 + mMoveLen);
  204. FontMetricsInt fmi = mPaint.getFontMetricsInt();
  205. float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));
  206. */
  207.  
  208. float x = (float) (mViewWidth / 2.0+ mMoveLen);
  209. float y = (float) (mViewHeight / 2.0 );
  210. FontMetricsInt fmi = mPaint.getFontMetricsInt();
  211. float baseline = (float) (x - (fmi.bottom+fmi.descent)/2);
  212. canvas.drawText(mDataList.get(mCurrentSelected), baseline, y, mPaint);
  213. // 绘制上方data
  214. for (int i = 1; (mCurrentSelected - i) >= 0; i++)
  215. {
  216. drawOtherText(canvas, i, -1);
  217. Log.e(TAG,String.valueOf(mCurrentSelected));
  218. }
  219. // 绘制下方data
  220. for (int i = 1; (mCurrentSelected + i) < mDataList.size(); i++)
  221. {
  222. drawOtherText(canvas, i, 1);
  223. }
  224.  
  225. }
  226.  
  227. /**
  228. * @param canvas
  229. * @param position
  230. * 距离mCurrentSelected的差值
  231. * @param type
  232. * 1表示向下绘制,-1表示向上绘制
  233. */
  234. private void drawOtherText(Canvas canvas, int position, int type)
  235. {
  236. float d = (float) (MARGIN_ALPHA * mMinTextSize * position + type
  237. * mMoveLen);
  238. float scale = parabola(mViewHeight / 4.0f, d);
  239. float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
  240. mPaint.setTextSize(size);
  241. mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
  242. /*float y = (float) (mViewHeight / 2.0 + type * d);*/
  243. float x = (float) (mViewWidth/ 2.0 + type * d);
  244. FontMetricsInt fmi = mPaint.getFontMetricsInt();
  245. float baseline = (float) (x - (fmi.bottom+fmi.descent));
  246. canvas.drawText(mDataList.get(mCurrentSelected + type * position),
  247. baseline,(float) (mViewHeight / 2.0), mPaint);
  248. }
  249.  
  250. /**
  251. * 抛物线
  252. *
  253. * @param zero
  254. * 零点坐标
  255. * @param x
  256. * 偏移量
  257. * @return scale
  258. */
  259. private float parabola(float zero, float x)
  260. {
  261. float f = (float) (1 - Math.pow(x / zero, 2));//x的y次方
  262. return f < 0 ? 0 : f;
  263. }
  264.  
  265. @Override
  266. public boolean onTouchEvent(MotionEvent event)
  267. {
  268. switch (event.getActionMasked())
  269. {
  270. case MotionEvent.ACTION_DOWN://表示用户开始触摸
  271. doDown(event);
  272. break;
  273. case MotionEvent.ACTION_MOVE:
  274. doMove(event);
  275. break;
  276. case MotionEvent.ACTION_UP://表示用户抬起了手指
  277. doUp(event);
  278. break;
  279. }
  280. return true;
  281. }
  282.  
  283. private void doDown(MotionEvent event)
  284. {
  285. if (mTask != null)
  286. {
  287. mTask.cancel();
  288. mTask = null;
  289. }
  290. mLastDownY = event.getX();
  291. }
  292.  
  293. private void doMove(MotionEvent event)
  294. {
  295.  
  296. mMoveLen += (event.getX() - mLastDownY);
  297.  
  298. //mMoveLen为正为向下滑
  299. if (mMoveLen > MARGIN_ALPHA * mMinTextSize / 2)
  300. {
  301. // 往下滑超过离开距离
  302. moveTailToHead();
  303. mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize;
  304. } else if (mMoveLen < -MARGIN_ALPHA * mMinTextSize / 2)
  305. {
  306. // 往上滑超过离开距离
  307. moveHeadToTail();
  308. mMoveLen = mMoveLen + MARGIN_ALPHA * mMinTextSize;
  309. }
  310.  
  311. mLastDownY = event.getX();
  312. invalidate();
  313. }
  314.  
  315. /*
  316. task - task to be scheduled.
  317. firstTime - First time at which task is to be executed.
  318. period - time in milliseconds between successive task executions.
  319. */
  320. private void doUp(MotionEvent event)
  321. {
  322. // 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置
  323. if (Math.abs(mMoveLen) < 0.0001)
  324. {
  325. mMoveLen = 0;
  326. return;
  327. }
  328. if (mTask != null)
  329. {
  330. mTask.cancel();
  331. mTask = null;
  332. }
  333. mTask = new MyTimerTask(updateHandler);
  334. timer.schedule(mTask, 0, 1000);
  335. //第三个参数,第一次调用之后,从第二次开始每隔多长的时间调用一次 run() 方法
  336. }
  337.  
  338. class MyTimerTask extends TimerTask
  339. {
  340. Handler handler;
  341.  
  342. public MyTimerTask(Handler handler)
  343. {
  344. this.handler = handler;
  345. }
  346.  
  347. @Override
  348. public void run()
  349. {
  350. handler.sendMessage(handler.obtainMessage());
  351. }
  352.  
  353. }
  354.  
  355. public interface onSelectListener
  356. {
  357. void onSelect(String text);
  358. }
  359. }
  1. /**
  2. * 获得屏幕的高度
  3. */
  4. WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  5. DisplayMetrics outMetrics = new DisplayMetrics();
  6. wm.getDefaultDisplay().getMetrics(outMetrics);
  7. mScreenHeight = outMetrics.heightPixels;
  8. Log.i("test",String.valueOf(mScreenHeight)); //2s 720

难点1:

字体随距离的渐变。可以看到,text随离中心位置的距离变化而变化,这里变化的是透明度alpha和字体大小TexSize,这两个值我都设置了Max和Min值,通过其与中心点的距离计算scale。

难点2:

循环滚动。为了解决循环滚动的问题我把存放text的List从中间往上下摊开,通过不断地moveHeadToTail和moveTailToHead使选中的text始终是list的中间position的值。

效果图:

自定义控件pickView的更多相关文章

  1. android自定义控件一站式入门

    自定义控件 Android系统提供了一系列UI相关的类来帮助我们构造app的界面,以及完成交互的处理. 一般的,所有可以在窗口中被展示的UI对象类型,最终都是继承自View的类,这包括展示最终内容的非 ...

  2. ASP.NET MVC学习之母版页和自定义控件的使用

    一.母板页_Layout.cshtml类似于传统WebForm中的.master文件,起到页面整体框架重用的目地1.母板页代码预览 <!DOCTYPE html> <html> ...

  3. C# 自定义控件VS用户控件

    1 自定义控件与用户控件区别 WinForm中, 用户控件(User Control):继承自 UserControl,主要用于开发 Container 控件,Container控件可以添加其他Con ...

  4. 自定义控件之 圆形 / 圆角 ImageView

    一.问题在哪里? 问题来源于app开发中一个很常见的场景——用户头像要展示成圆的:       二.怎么搞? 机智的我,第一想法就是,切一张中间圆形透明.四周与底色相同.尺寸与头像相同的蒙板图片,盖在 ...

  5. 如何开发FineReport的自定义控件?

    FineReport作为插件化开发的报表软件,有些特殊需求的功能需要自己开发,开发的插件包帆软官方有提提供,可以去帆软论坛上找,本文将主要介绍如何开发一个自定义控件,这里讲讲方法论. 第一步:实例化一 ...

  6. WPF自定义控件第二 - 转盘按钮控件

    继之前那个控件,又做了一个原理差不多的控件.这个控件主要模仿百度贴吧WP版帖子浏览界面左下角那个弹出的按钮盘.希望对大家有帮助. 这个控件和之前的也差不多,为了不让大家白看,文章最后发干货. 由于这个 ...

  7. 【Win 10应用开发】AdaptiveTrigger在自定义控件中是可以触发的

    前些天,看到有网友给我留言,说AdaptiveTrigger在自定义控件(模板化控件)中不能触发.因为当时我正在写其他的代码,就没有去做实验来验证,于是我就给这位网友提了使用GotoVisualSta ...

  8. WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式、水印、Label标签、功能扩展

    一.前言.预览 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要是对文本 ...

  9. Android自定义控件之自定义ViewGroup实现标签云

    前言: 前面几篇讲了自定义控件绘制原理Android自定义控件之基本原理(一),自定义属性Android自定义控件之自定义属性(二),自定义组合控件Android自定义控件之自定义组合控件(三),常言 ...

随机推荐

  1. poj 3070 Fibonacci (矩阵快速幂乘/模板)

    题意:给你一个n,输出Fibonacci (n)%10000的结果 思路:裸矩阵快速幂乘,直接套模板 代码: #include <cstdio> #include <cstring& ...

  2. vim中文帮助文档安装

    vim自带的帮助手册是英文的, 对平时编程的人来说没有多大阅读困难,在何况还有"星级译王"呢, 但是我猜和我一样连英语四级都愁的大有人,可偏偏就有一帮好心人人将其翻译成了中文, 可 ...

  3. html+css底部自动固定底部

    前端在切图过程中,肯定遇见过这种情况. 页面结构由三个部分组成,头部.内容.底部. 当一个页面的内容没撑满屏幕时,底部是跟着内容而并列存在的. 这个时候如果是大屏的话,底部下面会有多余的空白区域,而网 ...

  4. APP自识别安卓苹果

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. JS - A*寻路

    算法核心 A*估值算法 寻路估值算法有非常多:常用的有广度优先算法,深度优先算法,哈夫曼树等等,游戏中用的比较多的如:A*估值 算法描述 对起点与终点进行横纵坐标的运算 代码实现 start: 起点坐 ...

  6. Python实现二叉树的四种遍历

    对于一个没学过数据结构这门课程的编程菜鸟来说,自己能理解数据结构中的相关概念,但是自己动手通过Python,C++来实现它们却总感觉有些吃力.递归,指针,类这些知识点感觉自己应用的不够灵活,这是自己以 ...

  7. java类集框架(ArrayList,LinkedList,Vector区别)

    主要分两个接口:collection和Map 主要分三类:集合(set).列表(List).映射(Map)1.集合:没有重复对象,没有特定排序方式2.列表:对象按索引位置排序,可以有重复对象3.映射: ...

  8. 探讨.NET Core数据加密和解密问题

    前言 一直困扰着我关于数据加密这一块,24号晚上用了接近3个小时去完成一项任务,本以为立马能解决,但是为了保证数据的安全性,我们开始去对数据进行加密,然后接下来3个小时专门去研究加密这一块,然而用着用 ...

  9. OpenCV 玩九宫格数独(二):knn 数字识别

    欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 作者:刘潇龙 前言 首先需要说明,这里所说的数字识别不是手写数字识别! 但凡对机器学习有所了解的人,相信看到数 ...

  10. css3实现checkbox变按钮

    css3实现checkbox变按钮 .search_checkbox { margin: 0; padding: 0; margin-left: 15px; display: inline-block ...