Record a View of a Temperature Curve

image-20220713155216246.pngRecently, I saw that I needed to customize a temperature curve diagram for project needs. Because the previous colleagues did not understand the needs of the product well, they divided the temperature line graph into two views, one with high temperature and one view with low temperature. This approach is actually not a good understanding of the needs of the product. Why do you say this, because once it is split into two Views, which intersecting points are drawn will be defective. What do you mean, look at the picture.

image-20220713155901206.png

If you do it according to two Views, there will be this limitation. The intersecting points are cut. So here we re-modify this custom View.

With the above requirements, then start our design. First of all, for the better versatility of our custom View, we need to extract some things that may change. Here are only some very commonly used attributes, the rest need to be customized, you can add them yourself. look directly at the code

<declare-styleable name="NewWeatherChartView">
    <!--开始的x坐标-->
    <attr name="new_start_point_x" format="dimension"/>
    <!--两点之间x坐标的间隔-->
    <attr name="new_point_x_margin" format="dimension"/>
    <!--显示温度的字体大小-->
    <attr name="temperature_text_size" format="dimension"/>
    <!--圆点的半径-->
    <attr name="point_radius" format="dimension"/>
​
    <!--选中天气项,温度字体的颜色-->
    <attr name="select_temperature_text_color" format="reference|color"/>
    <!--未选中天气项,温度字体的颜色-->
    <attr name="unselect_temperature_text_color" format="reference|color"/>
    <!--选中天气项,圆点的颜色-->
    <attr name="select_point_color" format="reference|color"/>
    <!--未选中天气项,圆点的颜色-->
    <attr name="unselect_point_color" format="reference|color"/>
    <!--连接线的颜色-->
    <attr name="line_color" format="reference|color"/>
    <!--连接线的类型,可以是实线,也可以是虚线,默认是虚线。0虚线,1实线-->
    <attr name="line_type" format="integer"/></declare-styleable>
复制代码
public class NewWeatherChartView extends View {
    private final static String TAG = "NewWeatherChartView";
    private List<WeatherInfo> items;//温度的数据源
​
    //都是可以在XML里面配置的属性,目前项目里面都是用的默认配置。
    private int mLineColor;
    private int mSelectTemperatureColor;
    private int mUnSelectTemperatureColor;
    private int mSelectPointColor;
    private int mUnselectPointColor;
    private int mLineType;
    private int mTemperatureTextSize;
    private int mPointStartX = 0;
    private int mPointXMargin = 0;
    private int mPointRadius;
​
​
    
    private Point[] mHighPoints; //高温的点的坐标
    private Point[] mLowPoints; //低温的点的坐标
​
    //这里是为了方便写代码,多创建了几个画笔,也可以用一个画笔,然后配置不同的属性
    private Paint mLinePaint;   //用于画线画笔
    private Paint mTextPaint; // 用于画小圆点旁边的温度文字的画笔
    private Paint mCirclePaint;//用来画小圆点的画笔
   
​
    private Float mMaxTemperature = Float.MIN_VALUE;//最高温度
    private Float mMinTemperature = Float.MAX_VALUE;//最低温度
    private Path mPath;//连接线的路径
    
    private DecimalFormat mDecimalFormat;
​
​
    private int mTodayIndex = -1;//用于判断哪一个被选中
​
    private Context mContext;
    ...
}
复制代码

The above are some initialization things, so let's think about how to draw these things. The above initialization also explains that we mainly draw lines, text, and dots. So where should one start? The first is to start with point coordinates, because whether it is a line or a text, their position is related to the point. Then finding the coordinates of the point is the first job. How to find the coordinates of the point, and what is the initial X coordinate. The X coordinate of the first point is based on our configuration, so what about the x coordinate of the second point? , the x coordinate of the second point is the x coordinate of the first point plus the distance between them in the X direction, and the distance in the x direction is also configured according to the property. So we can easily get the x-coordinates of all points. What about the y-coordinate of the dot? First let's look at a picture.

image-20220713172903532.png

Our points should be evenly distributed in the remaining height.

Remaining height = control height - 2 * height of text.

The y coordinate of the point is

* Remaining height - ((current temperature-minimum temperature)/(maximum temperature-minimum temperature) remaining height) + text height

It looks a bit complicated, but the code is simpler if there are formulas. Next, you need to look at the initialization code and the code for calculating point coordinates.

code show as below:

//首先从两个参数的构造函数里面获取各种配置的值
public NewWeatherChartView(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NewWeatherChartView);
    mPointStartX = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_new_start_point_x, 0);
    mPointXMargin = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_new_point_x_margin, 0);
    mTemperatureTextSize = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_temperature_text_size, 20);
    mPointRadius = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_point_radius, 8);
​
    mSelectPointColor = typedArray.getColor(R.styleable.NewWeatherChartView_select_point_color, context.getResources().getColor(R.color.weather_select_point_color));
    mUnselectPointColor = typedArray.getColor(R.styleable.NewWeatherChartView_unselect_point_color, context.getResources().getColor(R.color.weather_unselect_point_color));
    mLineColor = typedArray.getColor(R.styleable.NewWeatherChartView_line_color, context.getResources().getColor(R.color.weather_line_color));
    mSelectTemperatureColor = typedArray.getColor(R.styleable.NewWeatherChartView_select_temperature_text_color, context.getResources().getColor(R.color.weather_select_temperature_color));
    mUnSelectTemperatureColor = typedArray.getColor(R.styleable.NewWeatherChartView_unselect_temperature_text_color, context.getResources().getColor(R.color.weather_unselect_temperature_color));
​
    mLineType = typedArray.getInt(R.styleable.NewWeatherChartView_line_type, 0);
​
    this.mContext = context;
    typedArray.recycle();
}
​
private void initData() {
    //初始化线的画笔
    mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mLinePaint.setStyle(Paint.Style.STROKE);
    mLinePaint.setStrokeWidth(2);
    mLinePaint.setDither(true);
    //配置虚线
    if (mLineType == 0) {
        DashPathEffect pathEffect = new DashPathEffect(new float[]{10, 5}, 1);
        mLinePaint.setPathEffect(pathEffect);
    }
    mPath = new Path();
​
    //初始化文字的画笔
    mTextPaint = new Paint();
    mTextPaint.setAntiAlias(true);
    mTextPaint.setTextSize(sp2px(mTemperatureTextSize));
    mTextPaint.setTextAlign(Paint.Align.CENTER);
​
    // 初始化圆点的画笔
    mCirclePaint = new Paint();
    mCirclePaint.setStyle(Paint.Style.FILL);
​
    mDecimalFormat = new DecimalFormat("0");
​
    for (int i = 0; i < items.size(); i++) {
        float highY = items.get(i).getHigh();
        float lowY = items.get(i).getLow();
        if (highY > mMaxTemperature) {
            mMaxTemperature = highY;
        }
        if (lowY < mMinTemperature) {
            mMinTemperature = lowY;
        }
        if (DateUtil.fromTodayDate(items.get(i).getDate()) == 0) {
            mTodayIndex = i;
        }
    }
    float span = mMaxTemperature - mMinTemperature;
    //这种情况是为了防止所有温度都一样的情况
    if (span == 0) {
        span = 6.0f;
    }
    mMaxTemperature = mMaxTemperature + span / 6.0f;
    mMinTemperature = mMinTemperature - span / 6.0f;
​
    mHighPoints = new Point[items.size()];
    mLowPoints = new Point[items.size()];
}
​
public int sp2px(float spValue) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, Resources.getSystem().getDisplayMetrics());
}
​
public int dip2px(float dpValue) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, Resources.getSystem().getDisplayMetrics());
​
}
复制代码

After these preparations last night, we can go to onDraw to draw.

protected void onDraw(Canvas canvas) {
    Logging.d(TAG, "onDraw: ");
    if (items == null) {
        return;
    }
    int pointX = mPointStartX; // 开始的X坐标
    int textHeight = sp2px(mTemperatureTextSize);//文字的高度
    int remainingHeight = getHeight() - textHeight * 2;//除去文字后,剩余的高度
​
    // 计算每一个点的X和Y坐标
    for (int i = 0; i < items.size(); i++) {
        int x = pointX + mPointXMargin * i;
        float highTemp = items.get(i).getHigh();
        float lowTemp = items.get(i).getLow();
        int highY = remainingHeight - (int) (remainingHeight * ((highTemp - mMinTemperature) / (mMaxTemperature - mMinTemperature))) + textHeight;
        int lowY = remainingHeight - (int) (remainingHeight * ((lowTemp - mMinTemperature) / (mMaxTemperature - mMinTemperature))) + textHeight;
        mHighPoints[i] = new Point(x, highY);
        mLowPoints[i] = new Point(x, lowY);
    }
​
    // 画线
    drawLine(mHighPoints, canvas);
    drawLine(mLowPoints, canvas);
    for (int i = 0; i < mHighPoints.length; i++) {
        // 画文本度数 例如:3°
        String yHighText = mDecimalFormat.format(items.get(i).getHigh());
        String yLowText = mDecimalFormat.format(items.get(i).getLow());
        int highDrawY = mHighPoints[i].y - dip2px(mPointRadius + 8);
        int lowDrawY = mLowPoints[i].y + dip2px(mPointRadius + 8 + sp2px(mTemperatureTextSize));
​
        if (i == mTodayIndex) {
            mTextPaint.setColor(mSelectTemperatureColor);
            mCirclePaint.setColor(mSelectPointColor);
        } else {
            mTextPaint.setColor(mUnSelectTemperatureColor);
            mCirclePaint.setColor(mUnselectPointColor);
        }
        canvas.drawText(yHighText + "°", mHighPoints[i].x, highDrawY, mTextPaint);
        canvas.drawText(yLowText + "°", mLowPoints[i].x, lowDrawY, mTextPaint);
        canvas.drawCircle(mHighPoints[i].x, mHighPoints[i].y, mPointRadius, mCirclePaint);
        canvas.drawCircle(mLowPoints[i].x, mLowPoints[i].y, mPointRadius, mCirclePaint);
​
    }
}
​
​
private void drawLine(Point[] ps, Canvas canvas) {
    Point startp;
    Point endp;
    mPath.reset();
    mLinePaint.setAntiAlias(true);
    for (int i = 0; i < ps.length - 1; i++) {
        startp = ps[i];
        endp = ps[i + 1];
        mLinePaint.setColor(mLineColor);
        canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mLinePaint);
    }
}
复制代码

The above is all the key code, of course, there is also an assignment code

public void setData(List<WeatherInfo> list) {
    this.items = list;
    initData();
}
复制代码

Let's take a look at the final renderings.

image-20220713194524550.pngThe above is a simple temperature map, but there are many places in this map that can be optimized, and many places can be extracted as attributes. For example, let me give an optimization point, the measurement of text, the measurement of text in the above code is actually very rough. If you look closely, you will find that the distance between the text and the point on the line above is different from the distance between the text and the point on the line below. This is the result of no text measurement above. I have carried out a round of text measurement optimization here, as shown in the figure below: image-20220713194423946.pngIs it much better here? You can also optimize in many places. That's all for this article.

Guess you like

Origin juejin.im/post/7119826029463470088