Android进阶路之自定义view实操第一篇

前言
Android自定义view在实际开发中运用十分广泛,也有许多牛人写出很多牛掰的自定义view,无奈只能望其项背啊!
今天,虽然标题是自定义view实操,其实就是把别人写好的轮子拿过来改造改造,所以,“实操”是有水分滴。
在这里插入图片描述
效果图
在开始吐墨水之前,先给大家看一下效果图:
在这里插入图片描述

实现步骤

  • 先定义一个叫MyClockView的类,继承自View
public class MyClockView extends View{}
  • 然后定义需要用到的属性

1、全局画布

private Canvas mCanvas;

2、时分秒的Paint

private Paint mTextPaint;

3、文本颜色、字体大小设置

/* 亮色,用于秒针、渐变终止色 */
private int mLightColor;
/* 暗色,刻度线、渐变起始色 */
private int mDarkColor;
/* 背景色 */
private int mBackgroundColor;
/* 时分秒文本字体大小 */
private float mTextSize;
/* am pm 文本字体大小 */
private float mSize;
/* 时钟半径,不包括padding值 */
private float mRadius;
/* 刻度线长度 */
private float mScaleLength;
/* 梯度扫描渐变 */
private SweepGradient mSweepGradient;
/* 渐变矩阵,作用在SweepGradient */
private Matrix mGradientMatrix;

4、时分秒显示

/* 时 */
private String h;
/* 分 */
private String m;
/* 秒 */
private String s;
/* 区分是上午还是下午 */
private String am_pm;

5、三角形秒针属性

/* 秒针角度 */
private float mSecondDegree;
/* 秒针画笔 */
private Paint mSecondHandPaint;
/* 秒针路径 */
private Path mSecondHandPath = new Path();

6、圆弧刻度

/* 刻度圆弧画笔 */
private Paint mScaleArcPaint;
/* 刻度圆弧的外接矩形 */
private RectF mScaleArcRectF = new RectF();
/* 刻度线画笔 */
private Paint mScaleLinePaint;
  • 重写构造
    这里只用到两个构造,在其中一个构造方法中处理一些初始化操作。
public MyClockView(Context context) {
    super(context);
}

public MyClockView(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0, 0);
    //背景色
    mBackgroundColor = ta.getColor(R.styleable.ClockView_clock_backgroundColor, Color.parseColor("#237EAD"));
    mLightColor = ta.getColor(R.styleable.ClockView_clock_lightColor, Color.parseColor("#ffffff"));
    mDarkColor = ta.getColor(R.styleable.ClockView_clock_darkColor, Color.parseColor("#80ffffff"));
    mTextSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 48));
    mSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 28));
    ta.recycle();
	//时分秒绘制
    mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mTextPaint.setStyle(Paint.Style.FILL);
    mTextPaint.setColor(mLightColor);
    //居中绘制文字
    mTextPaint.setTextAlign(Paint.Align.CENTER);
    mTextPaint.setTextSize(mTextSize);

    mAmpmPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mAmpmPaint.setStyle(Paint.Style.FILL);
    mAmpmPaint.setColor(mLightColor);
    mAmpmPaint.setTextAlign(Paint.Align.CENTER);
    mAmpmPaint.setTextSize(mSize);
	//圆弧刻度
    mScaleLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mScaleLinePaint.setStyle(Paint.Style.STROKE);
    mScaleLinePaint.setColor(mBackgroundColor);

    mScaleArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mScaleArcPaint.setStyle(Paint.Style.STROKE);

    mGradientMatrix = new Matrix();
	//三角形秒针
    mSecondHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mSecondHandPaint.setStyle(Paint.Style.FILL);
    mSecondHandPaint.setColor(mLightColor);
}
  • 重写onMeasure方法,测量控件大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(measureDimension(widthMeasureSpec), measureDimension(heightMeasureSpec));
}

private int measureDimension(int measureSpec) {
    int defaultSize = 800;
    int model = MeasureSpec.getMode(measureSpec);
    int size = MeasureSpec.getSize(measureSpec);
    switch (model) {
        case MeasureSpec.EXACTLY:
            return size;
        case MeasureSpec.AT_MOST:
            return Math.min(size, defaultSize);
        case MeasureSpec.UNSPECIFIED:
            return defaultSize;
        default:
            return defaultSize;
    }
}
  • 绘制时分秒
/**
* 绘制中间的文字
 */
private void drawCenterTxt() {
    mTextPaint.getTextBounds(h + ":" + m + ":" + s, 0, 0, mTextRect);
//        mCanvas.drawText(am_pm, getWidth() / 2, getHeight() / 2 - mDefaultPadding, mAmpmPaint);
    mCanvas.drawText(h + ":" + m + ":" + s, getWidth() / 2, getHeight() / 2 + mDefaultPadding, mTextPaint);// 绘制时间
}

实时获取系统时间

/**
* 获取当前时间
*/
private void getCurrentTime() {
   Calendar calendar = Calendar.getInstance();
   float milliSecond = calendar.get(Calendar.MILLISECOND);
   float second = calendar.get(Calendar.SECOND) + milliSecond / 1000;// 精确到小数点后 保证圆滑
   s = String.valueOf(calendar.get(Calendar.SECOND) + calendar.get(Calendar.MILLISECOND) / 1000);
   if (Integer.parseInt(s) < 10) {
       s = "0" + s;
   }
   m = String.valueOf(calendar.get(Calendar.MINUTE) + Integer.parseInt(s) / 60);
   if (Integer.parseInt(m) < 10)
       m = "0" + m;
   h = String.valueOf(calendar.get(Calendar.HOUR_OF_DAY) + Integer.parseInt(m) / 60);
   if (Integer.parseInt(h) < 10)
       h = "0" + h;
   if (calendar.get(Calendar.AM_PM) == Calendar.AM) {
       am_pm = "上午";
   } else {
       am_pm = "下午";
   }
   mSecondDegree = second / 60 * 360;
}

然后在onDraw方法中调用

@Override
protected void onDraw(Canvas canvas) {
    mCanvas = canvas;
    getCurrentTime();
    drawCenterTxt();
}
  • 绘制圆弧刻度
    在绘制圆弧刻度时,需要保证绘制的是圆形,有几个前提:
    1、RectF切割的是正方形
    2、切割出正方形后,就需要知道正方形的四个边距离控件边界的距离
    3、因此,就需要计算上下左右的padding
private float mDefaultPadding;
private float mPaddingLeft;
private float mPaddingTop;
private float mPaddingRight;
private float mPaddingBottom;

这里需要重写onSizeChanged方法来计算这5个变量

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(),
            h - getPaddingTop() - getPaddingBottom()) / 2;
    mDefaultPadding = 0.12f * mRadius;
    mPaddingLeft = mDefaultPadding + w / 2 - mRadius + getPaddingLeft();// 左边距
    mPaddingRight = mDefaultPadding + w / 2 - mRadius + getPaddingRight();// 右边距
    mPaddingTop = mDefaultPadding + h / 2 - mRadius + getPaddingTop();// 上边距
    mPaddingBottom = mDefaultPadding + h / 2 - mRadius + getPaddingBottom();// 下边距
}

对于圆形半径mRadius的计算公式如下:

mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(),
            h - getPaddingTop() - getPaddingBottom()) / 2;

解释:原本半径的取值是取控件宽和高中值最小的那个的一半作为半径值,也就是加入宽240,高480,那么半径的值就是120,但是,如果控件设置了padding,而半径的取值始终是宽高中小的那个一半,那么padding就会失去作用,因此,就需要在宽和高各自减去两个padding的值。
注意:在onSizeChanged的计算中,有一个mDefaultPadding变量,此变量的作用是,如果没有设置padding时,避免圆形刻度与控件边界相切。
以上设置处理完成之后就可以开始绘制圆形刻度了。
刻度盘

/**
* 画一圈梯度渲染的亮暗色渐变圆弧,重绘时不断旋转,上面盖一圈背景色的刻度线
*/
private void drawScaleLine() {
   mCanvas.save();
   mCanvas.translate(mCanvasTranslateX, mCanvasTranslateY);
   mScaleArcRectF.set(mPaddingLeft + 1.5f * mScaleLength + mTextRect.height() / 2,
           mPaddingTop + 1.5f * mScaleLength + mTextRect.height() / 2,
           getWidth() - mPaddingRight - mTextRect.height() / 2 - 1.5f * mScaleLength,
           getHeight() - mPaddingBottom - mTextRect.height() / 2 - 1.5f * mScaleLength);

   // matrix默认会在三点钟方向开始颜色的渐变,为了吻合钟表十二点钟顺时针旋转的方向,把秒针旋转的角度减去90度
   mGradientMatrix.setRotate(mSecondDegree - 90, getWidth() / 2, getHeight() / 2);
   mSweepGradient.setLocalMatrix(mGradientMatrix);
   mScaleArcPaint.setShader(mSweepGradient);
   mCanvas.drawArc(mScaleArcRectF, 0, 360, false, mScaleArcPaint);

   // 画背景色刻度线
   for (int i = 0; i < 200; i++) {
       mCanvas.drawLine(getWidth() / 2, mPaddingTop + mScaleLength + mTextRect.height() / 2,
               getWidth() / 2, mPaddingTop + 2 * mScaleLength + mTextRect.height() / 2, mScaleLinePaint);
       mCanvas.rotate(1.8f, getWidth() / 2, getHeight() / 2);
   }
   mCanvas.restore();
}

秒针

/**
 * 秒针
 */
private void drawSecondNeedle() {
    mCanvas.save();// ❑ save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
    mCanvas.rotate(mSecondDegree, getWidth() / 2, getHeight() / 2);// 设置指针位置
    mSecondHandPath.reset();
    float offset = mPaddingTop + mTextRect.height() / 2;

    mSecondHandPath.moveTo(getWidth() / 2, offset + 0.26f * mRadius);// 这三行绘制三角尖
    mSecondHandPath.lineTo(getWidth() / 2 - 0.05f * mRadius, offset + 0.34f * mRadius);
    mSecondHandPath.lineTo(getWidth() / 2 + 0.05f * mRadius, offset + 0.34f * mRadius);
    mSecondHandPath.close();
    mSecondHandPaint.setColor(mLightColor);
    mCanvas.drawPath(mSecondHandPath, mSecondHandPaint);
    mCanvas.restore();// ❑ restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
}

自此,自定义时钟控件就完成了。

此文控件改造自文章:《自定义 Android 钟表盘,这一篇就够了》
文章地址:https://www.cnblogs.com/yuanhao-1999/p/11065219.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码的灵魂是bug!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值