Android图表控件MPAndroidChart实现左右滑动以及联动

本文介绍了如何使用MPAndroidChart在Android中实现图表的左右滑动和联动功能。通过获取图表Matrix并进行操作,实现图表放大和平移。文章提供了关键代码示例,并展示了效果:拖动滑块可控制图表滑动,图表滑动或缩放时,联动滑块会同步更新。源码可在Gitee和GitHub找到。
前言

MPAndroidChart是一个功能强大的Android图表控件库,它实现了许多常见的图表效果。关于MPAndroidChart的使用文章已经很多了,这里不再详细介绍它的使用。当数据量过多时,一部手机小小的屏幕是无法显示完全的。因此需要让图表能够放大显示,并提供左右滑动功能。

实现

原理:获取图表的Matrix,通过Matrix来实现图表的放大,平移操作。
关键代码如下:

// 获取图标Matrix参数
mMatrix = mMainChart.getViewPortHandler().getMatrixTouch();
// 设置平移距离(这里是平移到图标的最右边)
float distance = mMainChart.getContentRect().width() * (scaleX - 1);
mMatrix.setTranslate(-distance, 0);
//两个参数分别是x,y轴的缩放比例。
mMatrix.preScale(scaleX, 1f);
// 刷新
mMatrix = mMainChart.getViewPortHandler().refresh(mMatrix, mMainChart, true);
效果如下
  • 可以拖动滑块控制图表的滑动
  • 当图标滑动或缩放时,滑块也会做出相应的改变
源码解读

源码地址:
https://gitee.com/yvxiang/ChartDemo
https://github.com/TangLiangs/ChartDemo

DataModel: 数据模型,在使用图表时,需要转换为Entry。

public class DataModel {

    public int x;

    public int y;

    public DataModel(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

MainChartConfig: 图表的基础设置,以及对联动视图的管理。

public class MainChartConfig {

    // 图表
    private LineChart mMainChart;
    // 图表控制滑块
    private ScrollBlockView mSubScroll;
    // 表格默认缩放比例
    private float scaleX = 6;

    private Context mContext;
    private Matrix mMatrix;

    private List<Entry> entryList = new ArrayList<>();
    private List<Integer> colorList = new ArrayList<>();


    public MainChartConfig(LineChart mainChart, ScrollBlockView scrollView, Context context) {
        mMainChart = mainChart;
        mSubScroll = scrollView;
        mContext = context;
        mMatrix = mMainChart.getViewPortHandler().getMatrixTouch();
        initData();
    }

    /**
     * 初始化数据
     */
    private void initData() {
        setMainChart();
        setMainXConfig();
        setMainYConfig();
        setScrollConfig();
        setScrollViewRate();
    }

    /**
     * 图表绘制
     */
    public void setData(List<DataModel> dataModels) {
        parseEntry(dataModels);
        setMainLineData();
        refresh();
    }

    /**
     * 刷新表格
     */
    private void refresh() {
        // 设置自动缩放
        float distance = mMainChart.getContentRect().width() * (scaleX - 1);
        mMatrix.setTranslate(-distance, 0);
        //两个参数分别是x,y轴的缩放比例。
        mMatrix.preScale(scaleX, 1f);
        mMatrix = mMainChart.getViewPortHandler().refresh(mMatrix, mMainChart, true);
        mMainChart.notifyDataSetChanged();
        mMainChart.invalidate();
        mSubScroll.setScrollRate(1.0f, scaleX);
    }

    /**
     * 解析数据
     */
    private void parseEntry(List<DataModel> models) {
        // 清除旧的数据
        entryList.clear();
        colorList.clear();

        if (models != null && models.size() > 0) {
            // 取最值,适应高度
            float max = 0;
            float min = 0;
            for (int i = 0; i < models.size(); i++) {
                // 设置Y轴数据
                DataModel model = models.get(i);
                Entry entry = new Entry(model.x, model.y);
                entryList.add(entry);

                // 根据y值,设置点的颜色
                if (model.y <= 4) {
                    colorList.add(mContext.getResources().getColor(R.color.orange));
                } else if (model.y <= 10) {
                    colorList.add(mContext.getResources().getColor(R.color.blue));
                } else {
                    colorList.add(mContext.getResources().getColor(R.color.red));
                }

                if (max < model.y) {
                    max = model.y;
                }

                if (min > model.y) {
                    min = model.y;
                }
            }
            updateY(max, min);
        } else {
            // 设置一个超出范围的值,保证列表不空,可以解决空数据下,不触发绘制的问题
            entryList.add(new Entry(1000, 1000));
        }
    }

    /**
     * 根据图表中的最值进行自适应Y轴的高度
     */
    private void updateY(float max, float min) {
        if (max <= 10) {
            // 最大值不低于15
            max = 15;
        } else {
            max = max + 5;
        }

        if (min > 2) {
            // 最小值不大于0,为了能让4.0的限制线显示完全
            min = 2;
        } else {
            min = min - 2;
        }

        mMainChart.getAxisLeft().setAxisMaximum(max);
        mMainChart.getAxisLeft().setAxisMinimum(min);
    }

    /**
     * 图表设置
     */
    private void setMainChart() {
        Description description = new Description();
        // 不需要右下角的描述文字
        description.setEnabled(false);
        mMainChart.setDescription(description);
        // 不需要背景
        mMainChart.setBackground(null);
        // 可以拖动,而不影响缩放比例
        mMainChart.setDragEnabled(true);
        // 设置没有数据时候的字体样式
        mMainChart.setNoDataText("");
        mMainChart.setNoDataTextColor(mContext.getResources().getColor(R.color.gray));
        // 不需要网格背景
        mMainChart.setDrawGridBackground(false);
        // 不需要边界
        mMainChart.setDrawBorders(false);
        // X轴可以缩放,Y轴不能缩放
        mMainChart.setScaleXEnabled(true);
        mMainChart.setScaleYEnabled(false);
        // 左右两边预留点空白部分
        mMainChart.setExtraOffsets(20, 0, 20, 0);
        // 不需要展示图例
        mMainChart.getLegend().setEnabled(false);
    }


    /**
     * 图表曲线设置
     */
    private void setMainLineData() {

        LineDataSet lineDataSet = new LineDataSet(entryList, null);
        // 使用贝塞尔曲线
        lineDataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
        // 画过的区域不填充
        lineDataSet.setDrawFilled(false);
        // 不需要展示数据的值
        lineDataSet.setDrawValues(false);
        // 使用圆形标记
        lineDataSet.setDrawCircles(true);
        lineDataSet.setCircleRadius(5);
        lineDataSet.setCircleHoleRadius(3);
        // 使用空心圆标记
        lineDataSet.setDrawCircleHole(true);
        // 设置每个圆形标记的颜色
        lineDataSet.setCircleColors(colorList);
        // 设置曲线的颜色
        lineDataSet.setColor(mContext.getResources().getColor(R.color.white));
        // 设置线宽
        lineDataSet.setLineWidth(3f);
        // 需要高亮的指示器
        lineDataSet.setDrawHighlightIndicators(true);
        lineDataSet.setHighlightLineWidth(1);
        lineDataSet.setHighLightColor(mContext.getResources().getColor(R.color.white));
        lineDataSet.setDrawHorizontalHighlightIndicator(false);
        lineDataSet.enableDashedHighlightLine(8, 8, 0f);
        LineData lineData = new LineData(lineDataSet);
        mMainChart.setData(lineData);
    }

    /**
     * 图表X坐标轴设置
     */
    private void setMainXConfig() {

        XAxis xAxis = mMainChart.getXAxis();
        //X轴设置显示位置在底部
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
        // 设置最大值,最小值
        xAxis.setAxisMaximum(24f);
        xAxis.setAxisMinimum(0f);
        //设置标签的数量
        xAxis.setLabelCount(5, false);
        xAxis.setDrawGridLines(false);
        // 设置X轴字体
        xAxis.setTextSize(11);
        xAxis.setTextColor(mContext.getResources().getColor(R.color.black));
        // 不要轴线
        xAxis.setDrawAxisLine(false);
        // 不要网格线
        xAxis.setDrawGridLines(false);
        xAxis.setGranularity(1);
    }


    /**
     * 图表Y坐标轴设置
     */
    private void setMainYConfig() {
        YAxis yAxis = mMainChart.getAxisLeft();
        // 不需要右边Y轴
        mMainChart.getAxisRight().setEnabled(false);
        // 添加最高和最低两条限制线
        yAxis.addLimitLine(getLimitLine(10, "10", mContext.getResources().getColor(R.color.white)));
        yAxis.addLimitLine(getLimitLine(4, "4.0", mContext.getResources().getColor(R.color.white)));
        // 设置Y轴最小值
        yAxis.setAxisMinimum(0);
        // 设置Y轴最大值
        yAxis.setAxisMaximum(15);
        // 不要网格背景
        yAxis.setDrawGridLines(false);
        yAxis.setGranularityEnabled(false);
        yAxis.setDrawAxisLine(false);
        // 不要标签
        yAxis.setDrawLabels(false);
        // 限制线在数据下方
        yAxis.setDrawLimitLinesBehindData(true);
    }

    /**
     * 获取限制线,限制线设置
     */
    private LimitLine getLimitLine(float limit, String label, int color) {
        // 设置限制值,和标签文字,限制值是血糖中的限制值,标签就是限制值
        LimitLine highLimit = new LimitLine(limit, label);
        // 限制线颜色为白色
        highLimit.setLineColor(color);
        // 线宽度为1
        highLimit.setLineWidth(1);
        // 标签位置在左边
        highLimit.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
        // 标签字体
        highLimit.setTextSize(10);
        // 标签颜色
        highLimit.setTextColor(color);
        // 使用虚线,设置每个短线的长度,和间隔长度
        highLimit.enableDashedLine(15, 15, 0);
        return highLimit;
    }

    /**
     * 滑块滑动时,图表改变相应的位置
     */
    private void setScrollConfig() {
        mSubScroll.setOnScrollListener(new ScrollBlockView.OnScrollListener() {
            @Override
            public void onScroll(float scrollRate) {
                float distance = mMainChart.getContentRect().width() * (scaleX - 1) * scrollRate;
                mMatrix.setTranslate(-distance, 0);
                mMatrix.preScale(scaleX, 1);
                mMainChart.getViewPortHandler().refresh(mMatrix, mMainChart, true);
            }
        });
    }

    /**
     * 图表拖动或缩放的时候,改变滑块的位置和大小
     */
    private void setScrollViewRate() {
        mMainChart.setOnChartGestureListener(new SimpleChartGestureListener() {
            float start = mMainChart.getViewPortHandler().getTransX();

            @Override
            public void onChartTranslate(MotionEvent me, float dX, float dY) {
                updateMoveView(start);
                start = mMainChart.getViewPortHandler().getTransX();
            }

            @Override
            public void onChartScale(MotionEvent me, float sX, float sY) {
                scaleX = mMainChart.getViewPortHandler().getScaleX();
                updateMoveView(start);
                start = mMainChart.getViewPortHandler().getTransX();
            }
        });
    }

    /**
     * 更新滑块的位置
     */
    private void updateMoveView(float start) {
        float distance = mMainChart.getViewPortHandler().getTransX();
        // 两次滑动间隔小于10,不触发滑块的滑动
        if (distance - start < scaleX && distance - start > -scaleX && scaleX > 1.1) {
            return;
        }

        // 基本不缩放的时候直接充满
        if (scaleX < 1.01) {
            scaleX = 1;
        }

        float scrollRate = 1;
        float total = mMainChart.getContentRect().width() * (scaleX - 1);
        if (total != 0) {
            scrollRate = -distance / total;
        }

        mSubScroll.setScrollRate(scrollRate, scaleX);
    }
}

ScrollBlockView: 图表下方的联动滑块,标识图表当前显示的数据位置。

public class ScrollBlockView extends View {
    // 背景颜色
    private int mBgColor;
    private Paint mBgPaint;
    // 滑块画笔
    private Paint mBlockPaint;
    // 滑块颜色
    private int mBlockColor;
    // 滑块的宽度
    private int mBlockWidth;
    // 滑块区域
    private RectF rectF;
    private Region mRegion;
    // View的宽度
    private int mWidth;
    // 当前滑动的位置[0, 1]
    private float scrollRate = 1f;
    // 缩放比例
    private float scale = 6f;

    // 滑动监听
    private OnScrollListener listener;

    public ScrollBlockView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initAttrs(context, attrs);
        initPaint();
        initData();
    }


    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScrollBlockView);
        mBgColor = typedArray.getColor(R.styleable.ScrollBlockView_bgColor, Color.parseColor("#ECFBFF"));
        mBlockColor = typedArray.getColor(R.styleable.ScrollBlockView_blockColor, Color.parseColor("#D6F6FF"));
        typedArray.recycle();
    }

    private void initPaint() {
        mBlockPaint = new Paint();
        mBlockPaint.setColor(mBlockColor);
        mBlockPaint.setStyle(Paint.Style.FILL);

        mBgPaint = new Paint();
        mBgPaint.setColor(mBgColor);
        mBgPaint.setStyle(Paint.Style.FILL);
    }

    private void initData() {
        rectF = new RectF();
        mRegion = new Region();
        setBackgroundColor(mBgColor);
    }

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

        mWidth = getMeasuredWidth();
        mBlockWidth = (int) (mWidth / scale);
        rectF.left = (mWidth - mBlockWidth) * scrollRate;
        rectF.top = 0;
        rectF.right = (mWidth - mBlockWidth) * scrollRate + mBlockWidth;
        rectF.bottom = getHeight();
        mRegion.set((int)rectF.left, (int) rectF.top, (int)rectF.right, (int)rectF.bottom);
        canvas.drawRect(rectF, mBlockPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        float x = event.getX();
        float y = event.getY();

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            return mRegion.contains((int) x, (int) y);
        }

        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            if (x < mBlockWidth / 2f || x > getWidth() - mBlockWidth / 2f) {
                return false;
            }

            // scrollRate是以滑块的left位置为基准计算出来的,范围[0, 1]
            float scrollRate = (x - mBlockWidth / 2f) / (mWidth - mBlockWidth);
            // 滑块位置已经改变,不需要重新绘制了
            setScrollRate(scrollRate, scale);

            if (listener != null) {
                listener.onScroll(scrollRate);
            }
            return true;
        }
        return false;
    }

    /**
     * 设置滑块移动的比例
     */
    public void setScrollRate(float scrollRate, float scale) {
        // 当值重复时,不再重绘
        if (this.scrollRate == scrollRate && this.scale == scale) {
            return;
        }
        if (scale < 1) {
            this.scale = 1;
        } else {
            this.scale = scale;
        }

        if (this.scrollRate > 1) {
            this.scrollRate = 1;
        } else if (this.scrollRate < 0) {
            this.scrollRate = 0;
        } else {
            this.scrollRate = scrollRate;
        }
        invalidate();
    }

    public void setOnScrollListener(OnScrollListener listener) {
        this.listener = listener;
    }

    public interface OnScrollListener {
        void onScroll(float scrollRate);
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值