转载请标明出处:https://blog.csdn.net/m0_38074457/article/details/85305237,本文出自【陈少华的博客】

一、效果图

二、控件结构

三、代码实现

1、attrs.xml中添加自定义控件的属性

<declare-styleable name="RulerView"><attr name="titleName" format="reference|string" /><attr name="unitName" format="reference|string" /><attr name="minValue" format="reference|integer" /><attr name="maxValue" format="reference|integer" /><attr name="currentValue" format="reference|integer" /></declare-styleable>

2、创建自定义控件RulerView继承View(请结合具体代码来看)

1)构造方法中通过initView获取初始化值。

2)onMeasure中来确定控件自身和控件内所要绘制内容的的尺寸。

3)onDraw中进行控件的绘制。

4)GestureDetectorCompat的监听方法onScroll和onFling中根据手指滑动的距离来计算当前刻度的位置。


import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.RegionIterator;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;/*** Created by HARRY on 2018/12/26.*/public class RulerView extends View {//控件宽private int mMeasuredWidth = 0;//控件高private int mMeasuredHeight = 0;//标尺宽private int mRulerWid;//标尺高private int mRulerHei;//标尺的左侧离控件的左侧的距离private int mLeft;//标尺的右侧离控件左侧的距离private int mRight;//标尺顶部离控件顶部距离private int mTop;//标尺底部离控件顶部的距离private int mBottom;//标题private String mTitleName;//画笔private Paint mPaint = new Paint();//目前设置刻度值默认显示6个单位值,打个比方初始显示可见的值是20-80,那么mLineOffset的值就是,标尺的宽除以6private int mLineOffset;//标尺刻度的长度private int mLineLong;//标尺指示器的长度private int mIndicatorLong;//最大刻度值private int mMaxValue;//最小刻度值private int mMinValue;//现在的刻度值private float mCurrentValue;//平均单位值下占有的px值private float mAverage;//标尺中间坐标x值private int mRulerMiddleX;//标尺刻度值y坐标private int mValueY;//实时手指移动时候的x坐标private float mCurrentDownX;//点击标尺时候记录下的点击y坐标private float mStartClickY;//点击标尺时候记录下的点击x坐标private float mStartClickX;//标尺单位名private String mUnitName;//滑动监听private SizeViewValueChangeListener listener;private GestureDetectorCompat mDetector;private ValueAnimator animator;public RulerView(Context context) {this(context, null);}public RulerView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public RulerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView(context, attrs);}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public RulerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);initView(context, attrs);}private void initView(Context context, AttributeSet attrs) {mDetector = new GestureDetectorCompat(context, new RulerGestureListener());TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RulerView);if (array != null) {String titleName = array.getString(R.styleable.RulerView_titleName);String unitName = array.getString(R.styleable.RulerView_unitName);int minValue = array.getInt(R.styleable.RulerView_minValue, 30);int maxValue = array.getInt(R.styleable.RulerView_maxValue, 120);int currentValue = array.getInt(R.styleable.RulerView_currentValue, 76);if (TextUtils.isEmpty(titleName)) {mTitleName = "体重";} else {mTitleName = titleName;}if (TextUtils.isEmpty(unitName)) {mUnitName = "kg";} else {mUnitName = unitName;}mMaxValue = maxValue;mMinValue = minValue;//默认让标尺距离左侧为100mLeft = 100;//默认让标尺距离顶部距离为100mTop = 100;setCurrentValue(currentValue);array.recycle();}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int modeHei = MeasureSpec.getMode(heightMeasureSpec);mMeasuredWidth = getMeasuredWidth();if (modeHei == MeasureSpec.AT_MOST || modeHei == MeasureSpec.UNSPECIFIED) {//不确定控件高度情况下设置一个默认值int heiDefault = DensityUtil.dip2px(getContext(), 140);setMeasuredDimension(mMeasuredWidth, heiDefault);} else if (modeHei == MeasureSpec.EXACTLY) {//确切值不做任何操作}mMeasuredHeight = getMeasuredHeight();//设置标尺宽度mRulerWid = mMeasuredWidth - 2 * mLeft;mRight = mLeft + mRulerWid;//默认让标尺可见的范围内可以显示6个单位的刻度mLineOffset = mRulerWid / 6;mRulerHei = mMeasuredHeight - 2 * mTop;mBottom = mRulerHei + mTop;//使刻度线的长度为标尺高度的1/3mLineLong = mRulerHei / 3;//标尺的指示器的长度为标尺高度的1/2mIndicatorLong = mRulerHei / 2;//单位刻度下占有的屏幕宽度mAverage = mLineOffset / 10;//标尺中间的位置的x坐标mRulerMiddleX = mMeasuredWidth / 2;//标尺刻度值的y坐标位置mValueY = mTop + mLineLong + 100;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);initDraw(canvas);//画标尺drawRuler(canvas);//        //把标尺以外所有区域染白
//        drawOutSideRuler(canvas);//目前没有更好的办法,先把标尺的左右两侧都设置白色drawLeftRight(canvas);//画标题drawTitle(canvas);//画实时刻度值drawRulerValue(canvas, (int) mCurrentValue);}//    private void drawOutSideRuler(Canvas canvas) {
//        mPaint.setColor(Color.parseColor("#FFFFFF"));
//        mPaint.setStyle(Paint.Style.FILL);
//
//        Rect rulerRect = new Rect();
//        rulerRect.set(mLeft, mTop, mRight, mBottom);
//
//        Rect allRect = new Rect();
//        allRect.set(0, 0, mMeasuredWidth, mRulerHei);
//
//        Region regionRuler = new Region(rulerRect);
//        Region regionAll = new Region(allRect);
//        regionAll.op(regionRuler, Region.Op.DIFFERENCE);
//
//        drawRegion(canvas, regionAll, mPaint);
//    }private void drawRegion(Canvas canvas, Region rgn, Paint paint) {RegionIterator iter = new RegionIterator(rgn);Rect r = new Rect();while (iter.next(r)) {canvas.drawRect(r, paint);}}private void drawTitle(Canvas canvas) {setTitlePaint();//画标题,使标题底部正好在标尺顶部,baseLineY的实现原理这里不讲了,大家百度下Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();int baseLineY = mTop - fm.bottom;canvas.drawText(mTitleName, mMeasuredWidth / 2, baseLineY - 10, mPaint);}private void initDraw(Canvas canvas) {mPaint.setColor(Color.parseColor("#FFFFFF"));mPaint.setStyle(Paint.Style.FILL);RectF rect = new RectF(0, 0, mMeasuredWidth, mMeasuredHeight);canvas.drawRect(rect, mPaint);}private void drawLeftRight(Canvas canvas) {mPaint.setColor(Color.parseColor("#FFFFFF"));mPaint.setStyle(Paint.Style.FILL);RectF rectLeft = new RectF(0, 0, mLeft - 2, mMeasuredHeight);RectF rectRight = new RectF(mRight + 2, 0, mMeasuredWidth, mMeasuredHeight);canvas.drawRect(rectLeft, mPaint);canvas.drawRect(rectRight, mPaint);}private void setTitlePaint() {mPaint.setColor(Color.BLACK);mPaint.setTextSize(40);mPaint.setStyle(Paint.Style.FILL);mPaint.setTextAlign(Paint.Align.CENTER);}private void drawRuler(Canvas canvas) {RectF rulerRect = new RectF();rulerRect.set(mLeft, mTop, mRight, mBottom);//画标尺的背景setRulerBackgroundPaint();int cornor = DensityUtil.dip2px(getContext(), 6);canvas.drawRoundRect(rulerRect, cornor, cornor, mPaint);//画标尺的刻度线和刻度drawRulerLine(canvas, (int) mCurrentValue);//画标尺的指示器线setIndicatorLinePaint();canvas.drawLine(mMeasuredWidth / 2, mTop, mMeasuredWidth / 2, mTop + mIndicatorLong, mPaint);//画标尺的背景边框setRulerStrokePaint();canvas.drawRoundRect(rulerRect, cornor, cornor, mPaint);}private void drawRulerValue(Canvas canvas, int indicatorValue) {setIndicatorValuePaint();//计算出baseLine位置,使文字正好在标尺的下面,具体原理这里就不讲了,大家百度Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();int baseLineY = mBottom - fm.top;canvas.drawText(indicatorValue + " " + mUnitName, mRulerMiddleX, baseLineY, mPaint);}private void drawRulerLine(Canvas canvas, int indicatorValue) {setTitlePaint();//目前标尺值十位数字值int remainder = indicatorValue % 10;setRulerLinePaint();//画右侧长线int startXRl = (int) (mRulerMiddleX + mAverage * (10 - remainder));int endXRl = startXRl;int startYRl = mTop;int endYRl = mTop + mLineLong;int valueRl = (indicatorValue / 10 + 1) * 10;while (startXRl <= mRight && valueRl <= mMaxValue) {canvas.drawLine(startXRl, startYRl, endXRl, endYRl, mPaint);canvas.drawText("" + valueRl, startXRl, mValueY, mPaint);startXRl = startXRl + mLineOffset;endXRl = startXRl;valueRl = valueRl + 10;}//画右侧短线int startYRs = mTop;int endYRs = mTop + mLineLong / 2;int valueRs = 0;int startXRs = 0;if (remainder > 5) {valueRs = (indicatorValue / 10 + 1) * 10 + 5;startXRs = (int) (mRulerMiddleX + mAverage * (10 - remainder + 5));} else {valueRs = (indicatorValue / 10) * 10 + 5;startXRs = (int) (mRulerMiddleX + mAverage * (5 - remainder));}int endXRs = startXRs;while (startXRs <= mRight && valueRs <= mMaxValue) {canvas.drawLine(startXRs, startYRs, endXRs, endYRs, mPaint);startXRs = startXRs + mLineOffset;endXRs = startXRs;valueRs = valueRs + 10;}//画左侧长线int startXLl = (int) (mRulerMiddleX - mAverage * remainder);int endXLl = startXLl;int startYLl = mTop;int endYLl = mTop + mLineLong;int valueLl = (indicatorValue / 10) * 10;while (startXLl >= mLeft && valueLl >= mMinValue) {canvas.drawLine(startXLl, startYLl, endXLl, endYLl, mPaint);canvas.drawText("" + valueLl, startXLl, mValueY, mPaint);startXLl = startXLl - mLineOffset;endXLl = startXLl;valueLl = valueLl - 10;}//画左侧短线int startYLs = mTop;int endYLs = mTop + mLineLong / 2;int startXLs = 0;int valueLs = 0;if (remainder > 5) {valueLs = (indicatorValue / 10) * 10 + 5;startXLs = (int) (mRulerMiddleX - mAverage * (remainder - 5));} else {valueLs = (indicatorValue / 10 - 1) * 10 + 5;startXLs = (int) (mRulerMiddleX - mAverage * (remainder + 5));}int endXLs = startXLs;while (startXLs >= mLeft && valueLs >= mMinValue) {canvas.drawLine(startXLs, startYLs, endXLs, endYLs, mPaint);startXLs = startXLs - mLineOffset;endXLs = startXLs;valueLs = valueLs - 10;}}@Overridepublic boolean onTouchEvent(MotionEvent event) {return mDetector.onTouchEvent(event);}private class RulerGestureListener extends GestureDetector.SimpleOnGestureListener {@Overridepublic boolean onDown(MotionEvent e) {if (animator != null) {animator.cancel();}mCurrentDownX = e.getX();//记录用户点击屏幕时候的坐标点mStartClickX = mCurrentDownX;mStartClickY = e.getY();return true;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {System.out.println("onScroll");if (mStartClickX >= mLeft && mStartClickX <= mRight &&mStartClickY >= mTop && mStartClickY < mBottom) {float value = mCurrentValue + distanceX / mAverage;setCurrentValue(value);invalidate();if (listener != null) {listener.onValueChange((int) mCurrentValue);}}return true;}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {System.out.println("onFling");if (mStartClickX >= mLeft && mStartClickX <= mRight &&mStartClickY >= mTop && mStartClickY < mBottom) {animator = ValueAnimator.ofFloat(velocityX, 0);animator.setDuration(1000);
//                animator.setInterpolator(new LinearInterpolator());animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float animatedValue = (float) animation.getAnimatedValue();float value = mCurrentValue - (animatedValue * (float) 0.005) / mAverage;setCurrentValue(value);invalidate();if (listener != null) {listener.onValueChange((int) mCurrentValue);}}});animator.start();}return true;}}public void setCurrentValue(float value) {mCurrentValue = value;if (mCurrentValue < mMinValue) {mCurrentValue = mMinValue;} else if (mCurrentValue > mMaxValue) {mCurrentValue = mMaxValue;}}private void setIndicatorValuePaint() {mPaint.setColor(Color.RED);mPaint.setStyle(Paint.Style.FILL);mPaint.setTextSize(60);//使文字居中显示mPaint.setTextAlign(Paint.Align.CENTER);}private void setIndicatorLinePaint() {mPaint.setColor(Color.parseColor("#FF0000"));mPaint.setStrokeWidth(3);}private void setRulerLinePaint() {
//        mPaint.setColor(Color.parseColor("#E2E2E2"));mPaint.setColor(Color.parseColor("#000000"));mPaint.setStrokeWidth(3);}private void setRulerBackgroundPaint() {mPaint.setStyle(Paint.Style.FILL);mPaint.setColor(Color.parseColor("#F8F8F8"));}private void setRulerStrokePaint() {mPaint.setStyle(Paint.Style.STROKE);
//        mPaint.setColor(Color.parseColor("#E2E2E2"));mPaint.setColor(Color.parseColor("#000000"));mPaint.setStrokeWidth(3);}public void setOnValueChangeListener(SizeViewValueChangeListener listener) {this.listener = listener;}}
public interface SizeViewValueChangeListener {void onValueChange(int value);
}

三、项目中如何引用

步骤1.将JitPack存储库添加到构建文件中

项目的根build.gradle中添加以下代码:

allprojects {repositories {...maven { url 'https://jitpack.io' }}
}

步骤2.build.gradle添加依赖项

dependencies {implementation 'com.github.hnsycsxhzcsh:RulerView:v1.3'
}

步骤3. 布局中引用控件

<com.rulerlibrary.RulerViewandroid:id="@+id/sizeview_kg"android:layout_width="match_parent"android:layout_height="wrap_content"app:currentValue="76"app:maxValue="150"app:minValue="45"app:titleName="体重"app:unitName="kg" />

步骤4. activity中添加监听

mSizeViewKg = findViewById(R.id.sizeview_kg);//设置初始化值
mSizeViewKg.setCurrentValue(89);mSizeViewKg.setOnValueChangeListener(new SizeViewValueChangeListener() {@Overridepublic void onValueChange(int value) {//获取现在的值}});

备注:

可以在github上下载我的项目:https://github.com/hnsycsxhzcsh/RulerView,如果我的博客对你有帮助的话,欢迎博客点赞支持,并在github右上角star支持!

《Android自定义控件》RulerView,仿唯品会身高、体重等标尺,尺码控件,滑动可修改刻度值相关推荐

  1. Android自定义控件之3D上下翻页效果的倒计时控件

    这是一个自定义的倒计时控件,具有3D上下翻页翻转效果.最近项目中需要做一个倒计时控件,需要和iOS端的效果保持一样.大致效果是这样的,如下图所示: 由于暂时还不会怎么样制作gif动态图,所以想看具体效 ...

  2. android汽车之家顶部滑动菜单,Android自定义控件之仿汽车之家下拉刷新

    关于下拉刷新的实现原理我在上篇文章Android自定义控件之仿美团下拉刷新中已经详细介绍过了,这篇文章主要介绍表盘的动画实现原理 汽车之家的下拉刷新分为三个状态: 第一个状态为下拉刷新状态(pull ...

  3. Android自定义控件之仿汽车之家下拉刷新

    感谢 阿拉灯神灯 的技术分享 .版权声明:原文来自http://blog.csdn.net/nugongahou110 关于下拉刷新的实现原理我在上篇文章Android自定义控件之仿美团下拉刷新中已经 ...

  4. android高仿小米日历,高仿钉钉和小米的日历控件

    i# 简介 这是一个高仿钉钉和小米的日历控件,支持快速滑动,界面缓存.想要定制化UI,使用起来非常简单,就像使用ListView一样 一些特点: 可以自定义日历控件UI 支持快速滑动 支持农历和阳历 ...

  5. android高仿钉钉和小米的日历控件

    原文地址:http://www.jianshu.com/p/622fdded4dc9 简介 这是一个高仿钉钉和小米的日历控件,支持快速滑动,界面缓存.想要定制化UI,使用起来非常简单,就像使用List ...

  6. android 桌面图标的点击放大效果,Android仿英语流利说取词放大控件的实现方法(附demo源码下载)...

    本文实例讲述了Android仿英语流利说取词放大控件的实现方法.分享给大家供大家参考,具体如下: 1 取词放大控件 英语流利说是一款非常帮的口语学习app,在app的修炼页面长按屏幕,会弹出一个放大镜 ...

  7. android 酷狗demo_Android仿酷狗音乐自定义侧滑菜单控件简单实现

    随着Android的不断成熟,许多绚丽的效果也在不断的被大家开发出来,其中侧滑的效果用到的项目很多,用的好的更是给吸引了很多用户.国内像QQ和酷狗App的侧滑就很给力,所以查了一些资料,并结合View ...

  8. Android仿酷狗音乐自定义侧滑菜单控件简单实现

    随着Android的不断成熟,许多绚丽的效果也在不断的被大家开发出来,其中侧滑的效果用到的项目很多,用的好的更是给吸引了很多用户.国内像QQ和酷狗App的侧滑就很给力,所以查了一些资料,并结合View ...

  9. 高仿钉钉和小米的日历控件

    CalendarExaple 项目地址:codbking/CalendarExaple 简介:高仿钉钉和小米的日历控件 更多:作者   提 Bug   示例 APK    标签: CalendarVi ...

最新文章

  1. contentProvider中有关query方法的使用
  2. SAP MM 工序委外流程初探
  3. 简书非官方大数据(一)
  4. SharePonit 2010 更改另存为列表模板的语言类型
  5. 网站SEO优化介绍搜索引擎给网站排名的过程
  6. 【专题介绍】跨越 X 突破,音视频聚力新机遇
  7. ecs 云服务器 管理控制台_【弹性计算】教您快速学会云服务器ECS 创建命令!
  8. vue.js v-on
  9. bootstrap table border粗细_Web前端开发(18)——Bootstrap响应式布局
  10. Android 开发工具集合 - (Android Dev Tools)
  11. java synchronized互斥锁使用
  12. PHPnow中ZendDebugger与ZendOptimizer 共存
  13. SaaSpace:25款最佳免费视频编辑软件工具
  14. 一个将汉字转换成拼音的npm包
  15. 关于QTTabBar的使用
  16. 5.ViewPage2使用及坑点解决
  17. win2003企业版安装
  18. 分散式云存储,元宇宙数据存储基建
  19. 如何编制试算平衡表_试算平衡表的编制步骤是怎样的?
  20. 那些年我记下的一些编程错误

热门文章

  1. 记账APP:小哈记账2——注册页面的制作
  2. 深度学习基础之图像分类
  3. 从mysql2ch到synch,一次重构与升级
  4. redis为什么选择了跳跃表而不是红黑树
  5. 怎么把ogg音乐格式转换成mp3
  6. 零知识证明安全实现经验
  7. OpenWrt设置修改IP地址
  8. EAP-TTLS预研报告
  9. php提取字符串连接,如何从PHP中的字符串中提取URL?
  10. oracle字符串截取substr和字符串查找instr