import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.LinearGradient;import android.graphics.Paint;import android.graphics.Path;import android.graphics.Shader;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.view.View;import com.careeach.sport.R;import java.util.List;/** * 心率热图 */public class HeartRateView extends View {    private List<Integer> values;    private int[] guidesValues;    private float height;    private float width;    private Paint linkLinePoint;    private Paint shadePoint;    private Paint xTextPoint;    private Paint xLintPoint;    private Paint yTextPoint;    private float itemHeight;    //ATTR    private int attrXTextColor;    private float attrXTextSize;    private int attrXLineColor;    private float attrXLineWidth;    private int attrYTextColor;    private float attrYTextSize;    private float attrLineWidth;    private int attrLinkLineColor;    private int attrShadeColor;    private float attrGuidesTextMarginLeft;    private float attrGuidesTextMarginRight;    private float attrPaddingRight;    private int maxValue; // 最大值    private float itemWidth;    private float contentEndY;    private float contentStartX;    public HeartRateView(Context context) {        super(context);    }    public HeartRateView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init(context, attrs);    }    public HeartRateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context, attrs);    }    public HeartRateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        init(context, attrs);    }    private void init(Context context, AttributeSet attrs) {        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HeartRateView);        attrLineWidth = typedArray.getDimension(R.styleable.HeartRateView_hr_lineWidth, dip2px(0.5f));        attrLinkLineColor = typedArray.getColor(R.styleable.HeartRateView_hr_linkLineColor, Color.RED);        attrShadeColor = typedArray.getColor(R.styleable.HeartRateView_hr_shadeColor, Color.RED);        maxValue = typedArray.getInteger(R.styleable.HeartRateView_hr_maxValue, 180);        attrXTextColor = typedArray.getColor(R.styleable.HeartRateView_hr_xTextColor, Color.GRAY);        attrXTextSize = typedArray.getDimension(R.styleable.HeartRateView_hr_xTextSize, 20);        attrXLineColor = typedArray.getColor(R.styleable.HeartRateView_hr_xLineColor, Color.GRAY);        attrYTextSize = typedArray.getDimension(R.styleable.HeartRateView_hr_yTextSize, 20);        attrYTextColor = typedArray.getColor(R.styleable.HeartRateView_hr_yTextColor, Color.GRAY);        attrXLineWidth = typedArray.getDimension(R.styleable.HeartRateView_hr_xLineWidth, dip2px(0.5f));        attrGuidesTextMarginLeft = typedArray.getDimension(R.styleable.HeartRateView_hr_guidesTextMarginLeft, 20);        attrGuidesTextMarginRight = typedArray.getDimension(R.styleable.HeartRateView_hr_guidesTextMarginRight, 20);        attrPaddingRight = typedArray.getDimension(R.styleable.HeartRateView_hr_paddingRight, 20);        typedArray.recycle();        linkLinePoint = new Paint();        linkLinePoint.setColor(attrLinkLineColor);        linkLinePoint.setStrokeWidth(attrLineWidth);        linkLinePoint.setStyle(Paint.Style.STROKE);        linkLinePoint.setAntiAlias(true);        shadePoint = new Paint();        shadePoint.setStyle(Paint.Style.FILL);        xTextPoint = new Paint();        xTextPoint.setColor(attrXTextColor);        xTextPoint.setTextSize(attrXTextSize);        xTextPoint.setAntiAlias(true);        yTextPoint = new Paint();        yTextPoint.setColor(attrYTextColor);        yTextPoint.setTextSize(attrYTextSize);        yTextPoint.setAntiAlias(true);        xLintPoint = new Paint();        xLintPoint.setColor(attrXLineColor);        xLintPoint.setStrokeWidth(attrXLineWidth);        xLintPoint.setAntiAlias(true);        xLintPoint.setStyle(Paint.Style.STROKE);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        width = getMeasuredWidth();        height = getMeasuredHeight();        contentEndY = height - measureTextHeight(xTextPoint);        itemHeight = contentEndY / maxValue;        contentStartX = attrGuidesTextMarginLeft + yTextPoint.measureText(String.valueOf(maxValue)) + attrGuidesTextMarginRight;    }    public static float measureTextHeight(Paint paint) {        float height = 0f;        if (null == paint) {            return height;        }        Paint.FontMetrics fontMetrics = paint.getFontMetrics();        height = fontMetrics.descent - fontMetrics.ascent;        return height;    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (guidesValues != null && guidesValues.length > 0) {            for (int guidesValue : guidesValues) {                float x = attrGuidesTextMarginLeft;                float y = contentEndY - guidesValue * itemHeight;                canvas.drawText(String.valueOf(guidesValue), x, y, yTextPoint);            }        }        if (values != null && values.size() > 1) {            Path path = new Path();            Path pathLine = new Path();            path.moveTo(contentStartX, contentEndY - values.get(0) * itemHeight);            pathLine.moveTo(contentStartX, contentEndY - values.get(0) * itemHeight);            for (int i = 1; i < values.size(); i++) {                float x = contentStartX + i * itemWidth;                float y = contentEndY - values.get(i) * itemHeight;                // 为了好看,计算误差在最初一个点打消                if (i == values.size() - 1) {                    x = width - attrPaddingRight;                }                path.lineTo(x, y);                pathLine.lineTo(x, y);            }            path.lineTo(width - attrPaddingRight, contentEndY);            path.lineTo(contentStartX, contentEndY);            path.lineTo(contentStartX, contentEndY - values.get(0) * itemHeight);            path.close();            Shader mShader = new LinearGradient(0, contentEndY, 0, 0, Color.WHITE, attrShadeColor, Shader.TileMode.MIRROR);            shadePoint.setShader(mShader);            canvas.drawPath(path, shadePoint);            canvas.drawPath(pathLine, linkLinePoint);        }        // X 轴        canvas.drawLine(0, contentEndY, width, contentEndY, xLintPoint);    }    public void setValues(List<Integer> values) {        this.values = values;        itemWidth = (width - attrPaddingRight - contentStartX) / values.size();        postInvalidate();    }    /**     * 设置参考值     *     * @param values     */    public void setGuidesValues(int[] values) {        this.guidesValues = values;    }    private float dip2px(float dpValue) {        final float scale = getContext().getResources().getDisplayMetrics().density;        return (dpValue * scale + 0.5f);    }}

attrs.xml增加属性

<declare-styleable name="HeartRateView">    <attr name="hr_lineWidth" format="dimension" />    <attr name="hr_linkLineColor" format="color" />    <attr name="hr_maxValue" format="integer" />    <attr name="hr_xTextSize" format="dimension" />    <attr name="hr_xTextColor" format="color" />    <attr name="hr_xLineColor" format="color" />    <attr name="hr_xLineWidth" format="dimension" />    <attr name="hr_yTextSize" format="dimension" />    <attr name="hr_yTextColor" format="color" />    <attr name="hr_shadeColor" format="color" />    <attr name="hr_guidesTextMarginLeft" format="dimension" />    <attr name="hr_guidesTextMarginRight" format="dimension" />    <attr name="hr_paddingRight" format="dimension" /></declare-styleable>