最近在做一个时间设置功能,之前都是简单的用一个EditText来实现,并设置输入范围或者输入错误的警告信息,这样的方法虽然简单,但用户使用起来,显得繁琐,而且还动不动的蹦出来俩提示,一点都不友好。
因此这次换个新的设计吧——卡尺选择。

主要介绍一下这个View的主要几个绘制点:
1. 坐标轴:需要绘制X轴(横向卡尺)或Y轴(纵向卡尺)drawLine(Canvas canvas, Paint paint);
2. 刻度:需要绘制坐标轴上的刻度。drawScale(Canvas canvas, Paint paint);绘制刻度时,需要的参数有坐标轴宽度以及刻度之间的间隔;
3. 当前刻度指针:drawScalePointer(Canvas canvas, Paint paint); 需要绘制在当前卡尺显示区域的中点。由于卡尺的这个指针每次卡尺滚动都需要重新绘制,所以每次都需要计算当前显示区域的中点位置,而这个中间位置是相对于卡尺起点位置的坐标;
4. 滑动卡尺支持scroll以及fling方式,且注意滑动的边界处理。

其他的就不多说了,直接上代码吧,代码中都有注释:
首先是卡尺绘制的基类,

package rkhy.com.ecg.view.scale;import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.Scroller;import rkhy.com.ecg.R;/*** **************************************************** @ 日        期:2018/1/5 15:33* @ 作        者:shangming* 卡尺基类* ***************************************************/
public abstract class BaseScaleView extends View {protected int mScaleMin; // 刻度最小值protected int mScaleMax; // 刻度最大值protected int mScaleHeight; // 刻度高度protected int mScaleNumHeight; // 整数刻度高度protected int mScaleSpace; // 刻度间隔protected int mScaleCount; //相对刻度起点滑动的刻度protected int mViewWidth; //宽度protected int mViewHeight; //高度protected int mScrollPreX;protected int mInitMiddleScalePointer; // 初始中间刻度指针protected int mScrollViewWidth; // 滚动的View宽度protected int mScaleMiddle; // 屏幕中间的刻度偏移量protected Scroller mScroller;protected OnScaleScrollListener mOnScaleScrollListener;protected VelocityTracker mVelocityTracker;public BaseScaleView(Context context) {super(context);init(context, null);}public BaseScaleView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init(context, attrs);}public BaseScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context, attrs);}private void init(Context context, AttributeSet attrs) {TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.scale_attrs);mScaleMin = ta.getInteger(R.styleable.scale_attrs_scale_min, 10);mScaleMax = ta.getInteger(R.styleable.scale_attrs_scale_max, 100);mScaleHeight = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_height, 20);mScaleNumHeight = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_number_height, 30);mScaleSpace = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_space, 15);ta.recycle();mScroller = new Scroller(context);initView();}@Overrideprotected void onDraw(Canvas canvas) {Paint paint = new Paint();paint.setColor(getResources().getColor(R.color.headBgColor));paint.setAntiAlias(true);// 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰paint.setDither(true);paint.setStyle(Paint.Style.STROKE);// 文字居中paint.setTextAlign(Paint.Align.CENTER);drawLine(canvas, paint);drawScale(canvas, paint);drawScalePointer(canvas, paint);super.onDraw(canvas);}public void setCurrent(int scale) {if (scale >= mScaleMin && scale <= mScaleMax) {smoothTo(scale);this.postInvalidate();}}public void smoothScrollBy(int dx, int dy) {mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);}@Overridepublic void computeScroll() {super.computeScroll();// 判断Scroller是否执行完毕if (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), mScroller.getCurrY());// 通过重绘来不断调用computeScrollinvalidate();}}public void setOnScaleScrollListener(OnScaleScrollListener onScaleScrollListener) {this.mOnScaleScrollListener = onScaleScrollListener;}/*** 初始化参数*/protected abstract void initView();/*** 绘制刻度线** @param canvas* @param paint*/protected abstract void drawLine(Canvas canvas, Paint paint);/*** 绘制刻度** @param canvas* @param paint*/protected abstract void drawScale(Canvas canvas, Paint paint);/*** 绘制刻度指针** @param canvas* @param paint*/protected abstract void drawScalePointer(Canvas canvas, Paint paint);/*** 滑动到指定刻度** @param scale*/public abstract void smoothTo(int scale);public interface OnScaleScrollListener {void onScaleScroll(int scale);}
}

横向卡尺的实现:

package rkhy.com.ecg.view.scale;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewGroup;import rkhy.com.ecg.R;/*** **************************************************** @ 日        期:2018/1/5 16:58* @ 作        者:shangming* 横向卡尺实现* ***************************************************/
public class HorizontalScaleView extends BaseScaleView {public HorizontalScaleView(Context context) {super(context);}public HorizontalScaleView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public HorizontalScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void initView() {mViewWidth = (mScaleMax - mScaleMin) * mScaleSpace;mViewHeight = mScaleHeight * 8 - 4;ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(mViewWidth, mViewHeight);this.setLayoutParams(layoutParams);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int height = MeasureSpec.makeMeasureSpec(mViewHeight, MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, height);mScrollViewWidth = getMeasuredWidth();mScaleMiddle = mScrollViewWidth / mScaleSpace / 2;mInitMiddleScalePointer = mScaleMiddle + mScaleMin;}@Overrideprotected void drawLine(Canvas canvas, Paint paint) {canvas.drawLine(0, mViewHeight, mViewWidth, mViewHeight, paint);}@Overrideprotected void drawScale(Canvas canvas, Paint paint) {paint.setTextSize(mViewHeight / 4); // 刻度数字大小paint.setStrokeWidth(2.0f);for (int i = 0, num = mScaleMin; i <= mScaleMax - mScaleMin; i++) {int x = i * mScaleSpace;if (0 == (i % 10)) { // 10的整数倍canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleNumHeight, paint);canvas.drawText(num + "", x, mViewHeight - mScaleNumHeight - mScaleHeight - 2, paint); // 绘制整数数字num += 10;} else if (0 == (i % 5)) { // 5的整数倍int height = mViewHeight - (mScaleHeight + (mScaleNumHeight - mScaleHeight) / 2);canvas.drawLine(x, mViewHeight, x, height, paint);} else { // 非整数canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleHeight, paint);}}}@Overrideprotected void drawScalePointer(Canvas canvas, Paint paint) {paint.setColor(getResources().getColor(R.color.red));paint.setStrokeWidth(5.0f);//根据滑动的距离,计算指针的位置【指针始终位于刻度中间】int currentX = mScroller.getCurrX();//滑动的刻度mScaleCount = mInitMiddleScalePointer + (int) Math.rint((double) currentX / (double) mScaleSpace);// 刻度越界处理if (mScaleCount >= mScaleMax) {mScaleCount = mScaleMax;} else if (mScaleCount <= mScaleMin) {mScaleCount = mScaleMin;}if (mOnScaleScrollListener != null) { //回调方法mOnScaleScrollListener.onScaleScroll(mScaleCount);}int x = mScaleSpace * (mScaleMiddle + mScaleCount - mInitMiddleScalePointer);// 滑动的距离越界处理if (x >= mViewWidth) {x = mViewWidth;} else if (x <= 0) {x = 0;}canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleNumHeight - mScaleHeight, paint);}@Overridepublic void smoothTo(int scale) {if (scale < mScaleMin || scale > mScaleMax) {return;}int dx = (scale - mScaleCount) * mScaleSpace;smoothScrollBy(dx, 0);}@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();if (mVelocityTracker == null) {//检查速度测量器,如果为null,获得一个mVelocityTracker = VelocityTracker.obtain();}int index = event.getActionIndex();int pointerId = event.getPointerId(index);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mVelocityTracker.addMovement(event);if (mScroller != null && !mScroller.isFinished()) {mScroller.abortAnimation();}mScrollPreX = x;break;case MotionEvent.ACTION_MOVE:mVelocityTracker.addMovement(event);int dx = mScrollPreX - x;if (dx < 0) {if (mScaleCount <= mScaleMin) { // 滑到右边界return super.onTouchEvent(event);}} else {if (mScaleCount >= mScaleMax) { // 滑到左边界return super.onTouchEvent(event);}}smoothScrollBy(dx, 0);mScrollPreX = x;postInvalidate();break;case MotionEvent.ACTION_UP://设置速度单位:/50msmVelocityTracker.computeCurrentVelocity(50);// 初始速度:*px/50msint initialVelocity = (int) mVelocityTracker.getXVelocity(pointerId);if (Math.abs(initialVelocity) > 150) {// 由于坐标轴正方向问题,要加负号。doFling(-initialVelocity);} else {if (mScaleCount < mScaleMin) mScaleCount = mScaleMin;if (mScaleCount > mScaleMax) mScaleCount = mScaleMax;int finalX = (mScaleCount - mInitMiddleScalePointer) * mScaleSpace;mScroller.setFinalX(finalX); //纠正指针位置invalidate();}break;}return true;}private void doFling(int speed) {if (mScroller == null) {return;}mScroller.fling(this.getScrollX(), 0, speed, 0,-(mScrollViewWidth / 2), mViewWidth - (mScrollViewWidth / 2),0, 0);}
}

最终效果:

android自定义View之尺子相关推荐

  1. Android自定义View —— TypedArray

    在上一篇中Android 自定义View Canvas -- Bitmap写到了TypedArray 这个属性 下面也简单的说一下TypedArray的使用 TypedArray 的作用: 用于从该结 ...

  2. Android 自定义View —— Canvas

    上一篇在android 自定义view Paint 里面 说了几种常见的Point 属性 绘制图形的时候下面总有一个canvas ,Canvas 是是画布 上面可以绘制点,线,正方形,圆,等等,需要和 ...

  3. android自定义view获取控件,android 自定义控件View在Activity中使用findByViewId得到结果为null...

    转载:http://blog.csdn.net/xiabing082/article/details/48781489 1.  大家常常自定义view,,然后在xml 中添加该view 组件..如果在 ...

  4. Android自定义View:ViewGroup(三)

    自定义ViewGroup本质是什么? 自定义ViewGroup本质上就干一件事--layout. layout 我们知道ViewGroup是一个组合View,它与普通的基本View(只要不是ViewG ...

  5. android 自定义图形,Android自定义View之图形图像(模仿360的刷新球自定

    概述: 360安全卫士的那个刷新球(姑且叫它刷新球,因为真的不知道叫什么好,不是dota里的刷新球!!),里面像住了水一样,生动可爱,看似简单,写起来不太简单,本例程只是实现了它的部分功能而已,说实话 ...

  6. android代码实现手机加速功能,Android自定义View实现内存清理加速球效果

    Android自定义View实现内存清理加速球效果 发布时间:2020-09-21 22:21:57 来源:脚本之家 阅读:105 作者:程序员的自我反思 前言 用过猎豹清理大师或者相类似的安全软件, ...

  7. android中仿qq最新版抽屉,Android 自定义View实现抽屉效果

    Android 自定义View实现抽屉效果 说明 这个自定义View,没有处理好多点触摸问题 View跟着手指移动,没有采用传统的scrollBy方法,而是通过不停地重新布局子View的方式,来使得子 ...

  8. Android 自定义 圆环,Android自定义view实现圆环效果实例代码

    先上效果图,如果大家感觉不错,请参考实现代码. 重要的是如何实现自定义的view效果 (1)创建类,继承view,重写onDraw和onMesure方法 public class CirclePerc ...

  9. android自定义抽奖,Android自定义view制作抽奖转盘

    本文实例为大家分享了Android自定义view制作抽奖转盘的具体代码,供大家参考,具体内容如下 效果图 TurntableActivity package com.bawei.myapplicati ...

最新文章

  1. Science封面:三元锂电池安全性差、寿命短问题已解决,来自美国能源部华人团队最新研究...
  2. ASP.NET Core 框架源码地址
  3. webpack 引入模块import 后面加入{}和不加大括号有什么区别
  4. python 多分类情感_文本情感分类(一):传统模型
  5. OS / Linux / pthread_join() 和 pthread_detach() 函数区别
  6. Javascript实现的2048
  7. 受限玻尔兹曼机RBM实现及能量值思考——matlab实现
  8. Win11如何调整任务栏大小 Win11调整任务栏大小的方法
  9. 【TensorFlow】TensorFlow函数精讲之tf.nn.max_pool()和tf.nn.avg_pool()
  10. windows server2012 domain user权限配置
  11. SWAT模型学习小技巧(前言)
  12. ex10_10MyInteger类
  13. 动态数组是怎么创建的?BQe
  14. 淘客基地教你如何推广淘客小程序
  15. mysql 汉字笔画排序规则_SQL Server与MySQL中排序规则与字符集相关知识的一点总结...
  16. 关于《成电讲坛》活动领票环节的调查报告
  17. 【C语言】计算圆周长以及面积
  18. EOSIO流服务Dfuse
  19. HDU 6069 题解
  20. 什么是邮箱域名,企业邮箱域名有什么好处?

热门文章

  1. html 图片自动大小,css图片自适应_用css让图片自动适应大小
  2. 聚焦 AI + 大数据展现数据驱动 引领行业创新升级
  3. Java开发工程师面试题
  4. Win7 右下角出现 Test Mode
  5. 【目标检测】(15) YOLOV4 损失函数,附TensorFlow完整代码
  6. 任务管理器”内存列的含义
  7. 【项目经验】——ASP.NET页面间传值
  8. 计算机数控装置软件模块组成,第六章计算机数控装置
  9. MOOC网深度学习应用开发4——猫狗大战
  10. 数学篇(四)希腊字母的正确读法(转)