Android自定义View-渐变的温度指示器

废话少说,先上图


ED9EF9312D01ADABDFADF481CF32A26C.jpg

1、自定义View的分类

image.png

2、自定义View要点

  1. View需要支持wrap_content
  2. View需要支持padding
  3. 尽量不要再View中使用Handler,View已经有post系列方法
  4. View如果有线程或者动画,需要及时停止(onDetachedFromWindow会在View被remove时调用)——避免内存泄露
  5. View如果有滑动嵌套情形,需要处理好滑动冲突

3、直接继承自View的实现步骤和方法

  1. 重写onDraw,在onDraw中处理padding
  2. 重写onMeasure,额外处理wrap_content的情况
  3. 设定自定义属性attrs(属性相关xml文件,以及在onDraw中进行处理)

4、实现效果图步骤

  1. 重写onMeasure,额外处理wrap_content的情况
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        mHeight = mDefaultTextSize + mDefaultTempHeight + mDefaultIndicatorHeight + textSpace;

        if (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, mHeight);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, mHeight);
        }
    }
  1. 重写onDraw,在onDraw中处理padding,并画出温度计及指针
@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();
        final int paddingRight = getPaddingRight();
        final int paddingBottom = getPaddingBottom();

        //确定圆角矩形的范围,在TmepView的最底部,top位置为总高度-圆角矩形的高度
        rectProgressBg = new RectF();
        rectProgressBg.left = 0 + paddingLeft;
        rectProgressBg.top = mHeight - mDefaultTempHeight;
        rectProgressBg.right = mWidth - paddingRight;
        rectProgressBg.bottom = mHeight - paddingBottom;

        shader = new LinearGradient(0, mHeight - mDefaultTempHeight, mWidth, mHeight, SECTION_COLORS, null, Shader.TileMode.MIRROR);
        mPaint.setShader(shader);
        //绘制圆角矩形 mDefaultTempHeight / 2确定圆角的圆心位置
        canvas.drawRoundRect(rectProgressBg, mDefaultTempHeight / 2, mDefaultTempHeight / 2, mPaint);

        //当前位置占比
        selction = currentCount / maxCount;
        //绘制指针 指针的位置在当前温度的位置 也就是三角形的顶点落在当前温度的位置
        //定义三角形的左边点的坐标 x= tempView的宽度*当前位置占比-三角形的宽度/2  y=tempView的高度-圆角矩形的高度
        indexPath.moveTo(mWidth * selction - mDefaultIndicatorWidth / 2, mHeight - mDefaultTempHeight);
        //定义三角形的右边点的坐标 = tempView的宽度*当前位置占比+三角形的宽度/2  y=tempView的高度-圆角矩形的高度
        indexPath.lineTo(mWidth * selction + mDefaultIndicatorWidth / 2, mHeight - mDefaultTempHeight);
        //定义三角形的左边点的坐标 x= tempView的宽度*当前位置占比  y=tempView的高度-圆角矩形的高度-三角形的高度
        indexPath.lineTo(mWidth * selction, mHeight - mDefaultTempHeight - mDefaultIndicatorHeight + paddingTop);
        indexPath.close();
        indexPaint.setShader(shader);
        canvas.drawPath(indexPath, indexPaint);

        //绘制文本
        String text = (int) currentCount + "°c";
        //确定文本的位置 x=tempViwe的宽度*当前位置占比 y=tempView的高度-圆角矩形的高度-三角形的高度-文本的间隙
        canvas.drawText(text, mWidth * selction, mHeight - mDefaultTempHeight - mDefaultIndicatorHeight - textSpace, textPaint);
    }

ps:绘制三角形指针,由于位置会变 所以要确定绘制的位置如图


image

代码还算比较好理解,详细代码如下:

package androidtest.project.com.customview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by liuboyu on 2018/10/15.
 */

public class TemperatureView extends View {

    private Context mContext;

    /**
     * 分段颜色
     */
    private static final int[] SECTION_COLORS = {Color.GREEN, Color.YELLOW, Color.RED};

    /**
     * 默认宽度
     */
    private int mWidth = 1000;

    /**
     * 默认高度
     */
    private int mHeight = 200;

    /**
     * 设置温度的最大范围
     */
    private float maxCount = 100f;

    /**
     * 设置当前温度
     */
    private float currentCount = 50f;

    /**
     * 当前刻度位置
     */
    private float selction;

    /**
     * 主画笔,画刻度尺
     */
    private Paint mPaint;

    /**
     * 文字画笔
     */
    private Paint textPaint;

    /**
     * 当前刻度指针
     */
    private Path indexPath;
    private Paint indexPaint;

    /**
     * 画圆柱
     */
    private RectF rectProgressBg;
    private LinearGradient shader;

    /**
     * 指针的宽高
     */
    private int mDefaultIndicatorWidth = dipToPx(10);
    private int mDefaultIndicatorHeight = dipToPx(8);
    /**
     * 圆角矩形的高度
     */
    private int mDefaultTempHeight = dipToPx(20);
    /**
     * 默认字体大小
     */
    private int mDefaultTextSize = 30;
    private int textSpace = dipToPx(5);


    public TemperatureView(Context context) {
        super(context);
        init(context);
    }

    public TemperatureView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public TemperatureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    /**
     * 初始化各种画笔
     *
     * @param context
     */
    private void init(Context context) {
        this.mContext = context;
        //圆角矩形paint
        mPaint = new Paint();
        //防止边缘的锯齿
        mPaint.setAntiAlias(true);

        //文本paint
        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(mDefaultTextSize);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(mContext.getResources().getColor(R.color.colorAccent));

        //三角形指针paint
        indexPath = new Path();
        indexPaint = new Paint();
        indexPaint.setAntiAlias(true);
        indexPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        mHeight = mDefaultTextSize + mDefaultTempHeight + mDefaultIndicatorHeight + textSpace;

        if (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, mHeight);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, mHeight);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();
        final int paddingRight = getPaddingRight();
        final int paddingBottom = getPaddingBottom();

        //确定圆角矩形的范围,在TmepView的最底部,top位置为总高度-圆角矩形的高度
        rectProgressBg = new RectF();
        rectProgressBg.left = 0 + paddingLeft;
        rectProgressBg.top = mHeight - mDefaultTempHeight;
        rectProgressBg.right = mWidth - paddingRight;
        rectProgressBg.bottom = mHeight - paddingBottom;

        shader = new LinearGradient(0, mHeight - mDefaultTempHeight, mWidth, mHeight, SECTION_COLORS, null, Shader.TileMode.MIRROR);
        mPaint.setShader(shader);
        //绘制圆角矩形 mDefaultTempHeight / 2确定圆角的圆心位置
        canvas.drawRoundRect(rectProgressBg, mDefaultTempHeight / 2, mDefaultTempHeight / 2, mPaint);

        //当前位置占比
        selction = currentCount / maxCount;
        //绘制指针 指针的位置在当前温度的位置 也就是三角形的顶点落在当前温度的位置
        //定义三角形的左边点的坐标 x= tempView的宽度*当前位置占比-三角形的宽度/2  y=tempView的高度-圆角矩形的高度
        indexPath.moveTo(mWidth * selction - mDefaultIndicatorWidth / 2, mHeight - mDefaultTempHeight);
        //定义三角形的右边点的坐标 = tempView的宽度*当前位置占比+三角形的宽度/2  y=tempView的高度-圆角矩形的高度
        indexPath.lineTo(mWidth * selction + mDefaultIndicatorWidth / 2, mHeight - mDefaultTempHeight);
        //定义三角形的左边点的坐标 x= tempView的宽度*当前位置占比  y=tempView的高度-圆角矩形的高度-三角形的高度
        indexPath.lineTo(mWidth * selction, mHeight - mDefaultTempHeight - mDefaultIndicatorHeight + paddingTop);
        indexPath.close();
        indexPaint.setShader(shader);
        canvas.drawPath(indexPath, indexPaint);

        //绘制文本
        String text = (int) currentCount + "°c";
        //确定文本的位置 x=tempViwe的宽度*当前位置占比 y=tempView的高度-圆角矩形的高度-三角形的高度-文本的间隙
        canvas.drawText(text, mWidth * selction, mHeight - mDefaultTempHeight - mDefaultIndicatorHeight - textSpace, textPaint);
    }


    /**
     * 单位转换
     *
     * @param dip
     * @return
     */
    private int dipToPx(int dip) {
        float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dip * scale + 0.5f * (dip >= 0 ? 1 : -1));
    }


    /***
     * 设置最大的温度值
     * @param maxCount
     */
    public void setMaxCount(float maxCount) {
        this.maxCount = maxCount;
    }

    /***
     * 设置当前的温度
     * @param currentCount
     */
    public void setCurrentCount(float currentCount) {
        if (currentCount > maxCount) {
            this.currentCount = maxCount - 5;
        } else if (currentCount < 0f) {
            currentCount = 0f + 5;
        } else {
            this.currentCount = currentCount;
        }
        invalidate();
    }

    /**
     * 设置温度指针的大小
     *
     * @param width
     * @param height
     */
    public void setIndicatorSize(int width, int height) {
        this.mDefaultIndicatorWidth = width;
        this.mDefaultIndicatorHeight = height;
    }

    /**
     * 设置温度计厚度
     *
     * @param height
     */
    public void setTempHeight(int height) {
        this.mDefaultTempHeight = height;
    }

    /**
     * 设置文字大小
     *
     * @param textSize
     */
    public void setTextSize(int textSize) {
        this.mDefaultTextSize = textSize;
    }

    /**
     * 获取温度计最大刻度
     *
     * @return
     */
    public float getMaxCount() {
        return maxCount;
    }

    /**
     * 获取当前刻度
     *
     * @return
     */
    public float getCurrentCount() {
        return currentCount;
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容