Android 自定义房价均价走势折线图

最近接手一个二手房项目,类似于房天下,安居客等app,里面需要用到一个每月均价走势折线图,目测功能比较简单,没必要动用MpAndroidChart,也不能适应项目要求,所以觉得自己写一个简单的。在此做一个记录。先上图
这里写图片描述

这里主要是Y轴的刻度不明确,所以这里为了简单起见,固定最低点和最高点的位置,中间再均等分,算出每一份高度对应的价格,再确定具体价格对应的高度,具体如下:
- 确定最低点和最高点的高度
- 中间数据根据差值计算均等分
- 中间部分每一份高度对应的值 = (最大价格 - 最小价格)/(最低点高度 - 最高点高度)
- 中间具体价格对应的实际高度 = 最低点高度 - (实际价格 - 最小价格) / 每一份高度对应的值
其他就是自定义View的一些基础,各种绘制,要注意顺序,避免覆盖。
Github地址

package com.samluys.linechartdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue;
import android.view.View;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author luys
 * @describe 自定义折线图
 * @date 2018/6/6
 * @email samluys@foxmail.com
 */
public class LineChartView extends View {

    private int mSpaceLength;
    private Paint mBottomLinePaint;
    private Paint mYLinePaint;
    private Paint mBrokenLinePaint;
    private Paint mOutCirclePaint;
    private Paint mInCirclePaint;
    private Paint mXTextPaint;
    private Paint mYTextPaint;
    /**
     * 左右两边距边缘的距离
     */
    private int mSideLength = Utils.dp2px(getContext(), 28);
    /**
     * 整个折线图的高度
     */
    private int mHeight = Utils.dp2px(getContext(),220);
    /**
     * 屏幕的宽度
     */
    private int mScreenWidth = Utils.getScreenWidth(getContext());
    /**
     * 距离上边距的高度
     */
    private int mPaddingTop = Utils.dp2px(getContext(),15);
    /**
     * X轴底部文字的高度
     */
    private int mXTextHeight = Utils.dp2px(getContext(), 40);
    /**
     * 节点外圆的半径
     */
    private int outRadius = Utils.dp2px(getContext(), 6);
    /**
     * 节点内圆的半径
     */
    private int inRadius = Utils.dp2px(getContext(), 4);
    /**
     * 线条和节点内圆的颜色
     */
    private int mMainColor;

    /**
     * 节点外圆的颜色
     */
    private int mOutCircleColor;


    /**
     * 固定最高点在Y轴上的高度
     */
    private int mPeakHeight = 60;

    /**
     * Y轴的数量
     */
    private int mYAxisNum = 6;

    /**
     * x轴数据
     */
    private List<String> mXAxis = new ArrayList<>();

    /**
     * y轴数据
     */
    private List<String> mYAxis = new ArrayList<>();

    /**
     * Y轴上数值与高度集合
     */
    private List<Pair<String,Integer>> valueHeight = new ArrayList<>();


    private List<Pair> circlexy = new ArrayList<>();

    public LineChartView(Context context) {
        this(context,null);
    }

    public LineChartView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray a = null;

        try {
            a = context.obtainStyledAttributes(attrs, R.styleable.LineChartView);
            mMainColor = a.getColor(R.styleable.LineChartView_lc_main_color, Color.parseColor("#D61939"));
            mYAxisNum = a.getInteger(R.styleable.LineChartView_lc_yaxis_num, 6);
            mOutCircleColor = a.getColor(R.styleable.LineChartView_lc_out_circle_color,Color.parseColor("#F7D1D7"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (a != null) {
                a.recycle();
            }
        }

        init();

    }

    private void init() {

        // 获取间隔距离
        getSpaceLength();
        // 初始化Paint
        initYLine();
        initBottomLine();
        initOutCircle();
        initInCircle();
        initBrokenLine();
        initXtext();
        initYtext();
    }

    /**
     * 初始化Y轴上数值
     */
    private void initYtext() {
        mYTextPaint = new Paint();
        mYTextPaint.setColor(Color.WHITE);
        mYTextPaint.setTextSize(26);
        mYTextPaint.setStyle(Paint.Style.FILL);
        mYTextPaint.setAntiAlias(true);

    }

    /**
     * 初始化X轴标签
     */
    private void initXtext() {
        mXTextPaint = new Paint();
        mXTextPaint.setColor(Color.parseColor("#666666"));
        mXTextPaint.setTextSize(26);
        mXTextPaint.setStyle(Paint.Style.FILL);
        mXTextPaint.setAntiAlias(true);
    }

    /**
     * 初始化折线
     */
    private void initBrokenLine() {
        mBrokenLinePaint = new Paint();
        mBrokenLinePaint.setColor(mMainColor);
        mBrokenLinePaint.setStrokeWidth(Utils.dp2px(getContext(), 2));
        mBrokenLinePaint.setStyle(Paint.Style.STROKE);
        mBrokenLinePaint.setAntiAlias(true);
    }

    /**
     * 初始化节点内圆
     */
    private void initInCircle() {
        mInCirclePaint = new Paint();
        mInCirclePaint.setColor(mMainColor);
        mInCirclePaint.setStyle(Paint.Style.FILL);
        mInCirclePaint.setAntiAlias(true);
    }

    /**
     * 初始化外圆
     */
    private void initOutCircle() {
        mOutCirclePaint = new Paint();
        mOutCirclePaint.setColor(mOutCircleColor);
        mOutCirclePaint.setStyle(Paint.Style.FILL);
        mOutCirclePaint.setAntiAlias(true);
    }

    /**
     * 获取间隔距离
     */
    private void getSpaceLength() {
        mSpaceLength = (mScreenWidth - mSideLength*2)/(mYAxisNum - 1);
    }

    /**
     * 初始化竖直方向的线条
     */
    private void initYLine() {
        mYLinePaint = new Paint();
        mYLinePaint.setColor(Color.parseColor("#e7e7e7"));
        mYLinePaint.setStrokeWidth(Utils.dp2px(getContext(), 2));
        mYLinePaint.setStyle(Paint.Style.STROKE);
        mYLinePaint.setAntiAlias(true);
    }

    /**
     * 初始化底部横线paint
     */
    private void initBottomLine() {
        mBottomLinePaint = new Paint();
        mBottomLinePaint.setColor(Color.parseColor("#e7e7e7"));
        mBottomLinePaint.setStrokeWidth(Utils.dp2px(getContext(), 4));
        mBottomLinePaint.setStyle(Paint.Style.STROKE);
        mBottomLinePaint.setAntiAlias(true);
    }

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

        // 注意顺序,避免线条覆盖文字
        // 绘制竖直方向上的6条线
        drawYLine(canvas);
        // 绘制底部的X轴线
        drawBottomLine(canvas);
        // 绘制节点和折线图
        drawCircleLine(canvas);
        // 绘制Y轴上的数据和背景
        drawYtext(canvas);
        // 绘制X轴标签文字
        drawBottomText(canvas);
    }

    /**
     * 绘制Y轴上的数据和背景
     * @param canvas
     */
    private void drawYtext(Canvas canvas) {

        for (int i = 0; i < valueHeight.size(); i++) {
            Pair<String, Integer> pair = valueHeight.get(i);
            // 绘制节点上文字的背景
            Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.icon_price_trend);
            int bitmapWidth = bitmap.getWidth();
            int bitmapHeight = bitmap.getHeight();
            canvas.drawBitmap(bitmap, mSideLength+mSpaceLength*i - bitmapWidth/2,pair.second - bitmapHeight - 20, new Paint());
            // 用Rect计算Text内容宽度
            Rect bounds = new Rect();
            mYTextPaint.getTextBounds(pair.first, 0, pair.first.length(), bounds);
            int textWidth = bounds.right-bounds.left;
            // 绘制节点上的文字
            canvas.drawText(pair.first, mSideLength+mSpaceLength*i - textWidth/2,pair.second - bitmapHeight/2 - 14, mYTextPaint);
        }
    }

    /**
     * 绘制X轴标签文字
     * @param canvas
     */
    private void drawBottomText(Canvas canvas) {

        for (int i = 0; i < mXAxis.size(); i++) {
            String xValue = mXAxis.get(i);
            // 获取Text内容宽度
            Rect bounds = new Rect();
            mXTextPaint.getTextBounds(xValue, 0, xValue.length(),bounds);
            int width = bounds.right - bounds.left;
            canvas.drawText(xValue,mSideLength - width/2 + mSpaceLength*i ,mHeight - mXTextHeight/2,mXTextPaint);
        }
    }

    /**
     * 绘制节点和折线图
     * @param canvas
     */
    private void drawCircleLine(Canvas canvas) {
        circlexy.clear();
        for (int i = 0; i < valueHeight.size(); i++) {
            Pair<String, Integer> pair = valueHeight.get(i);
            // 绘制节点外圆
            canvas.drawCircle(mSideLength+mSpaceLength*i,pair.second,outRadius,mOutCirclePaint);
            // 绘制节点内圆
            canvas.drawCircle(mSideLength+mSpaceLength*i,pair.second,inRadius,mInCirclePaint);
            // 保存圆心坐标
            Pair<Integer,Integer> pairs = new Pair<>(mSideLength+mSpaceLength*i, pair.second);
            circlexy.add(pairs);
        }

        for (int i = 0; i < circlexy.size(); i++) {

            if (i != circlexy.size() - 1) {
                canvas.drawLine((int)circlexy.get(i).first,
                        (int)circlexy.get(i).second,
                        (int)circlexy.get(i+1).first,
                        (int)circlexy.get(i+1).second,mBrokenLinePaint);
            }
        }
    }

    /**
     * 绘制竖直方向上的6条线
     * @param canvas
     */
    private void drawYLine(Canvas canvas) {
        for (int i = 0; i < mYAxis.size(); i++) {
            canvas.drawLine(mSideLength+mSpaceLength*i,mPaddingTop,mSideLength+mSpaceLength*i,mHeight - mXTextHeight,mYLinePaint);
        }
    }

    /**
     * 绘制底部的X轴线
     * @param canvas
     */
    private void drawBottomLine(Canvas canvas) {
        canvas.drawLine(0,mHeight - mXTextHeight,mScreenWidth,mHeight - mXTextHeight,mBottomLinePaint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(mSpaceLength * 24, mHeight);
    }

    /**
     * 设置数据
     * @param xAxis
     * @param yAxis
     */
    public void setData(List<String> xAxis, List<String> yAxis) {

        if (xAxis.size() !=  mYAxisNum) {
            throw new IllegalArgumentException("The X-axis number is not the same as Y-axis number");
        }

        mXAxis.clear();
        mXAxis.addAll(xAxis);
        mYAxis.clear();
        mYAxis.addAll(yAxis);

        List<Double> list = new ArrayList<>();
        for (int i = 0; i < mYAxis.size(); i++) {
            String yValue = mYAxis.get(i);
            double ydouble = TextUtils.isEmpty(yValue) ? 0 : Double.valueOf(yValue);
            list.add(ydouble);
        }
        // 最小值
        double minValue = Collections.min(list);
        // 最大值
        double maxValue = Collections.max(list);
        // 差值
        double differValue = maxValue - minValue;

        // y轴总高度
        int yHeight = mHeight - mXTextHeight - mPaddingTop;
        // 最低点的高度 即 最小值对应的高度 固定
        int lowHeight = yHeight - mPeakHeight;
        // 最高点的高度 即 最大值对应的高度 固定
        int highHeight = mPeakHeight + mPaddingTop;
        // 轴线上每一份对应的值
        double eachValue = differValue / (lowHeight - highHeight);

        valueHeight.clear();
        for (int i = 0; i < mYAxis.size(); i++) {
            String yValue = mYAxis.get(i);
            double ydouble = TextUtils.isEmpty(yValue) ? 0 : Double.valueOf(yValue);

            if(ydouble == maxValue) {
                // 最高点 和 对应的值
                valueHeight.add(new Pair<String,Integer>(yValue, highHeight));
            } else if (ydouble == minValue) {
                // 最低点 和 对应的值
                valueHeight.add(new Pair<String,Integer>(yValue, lowHeight));
            } else {
                // 对应的高度和值
                int eachHeight = lowHeight - (int) ((ydouble-minValue) / eachValue);
                valueHeight.add(new Pair<String,Integer>(yValue, eachHeight));
            }
        }
        invalidate();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ltym2014

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

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

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

打赏作者

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

抵扣说明:

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

余额充值