1. 程式人生 > >MPAndroidChart LineChart 折線圖 你要的都在這裡了

MPAndroidChart LineChart 折線圖 你要的都在這裡了

這裡寫圖片描述

前言

  MPAndroidChart已經出了很長的一段時間,相信大家也有所耳聞,自己也使用了有一段時間,固在此寫下文章,根據專案的需求,記錄一些見解與問題,作為參考。望大家取其精華去其糟粕。

最終效果圖

這裡寫圖片描述

涉及到的問題以及知識點
  1. 圖表樣式以及基礎資料 (快速入門)
  2. x軸標籤自定義標籤(Formatting Data Values (ValueFormatter))
  3. 自定義覆蓋物(MarkerView)
  4. 自定義多個覆蓋物(MarkerView)
  5. 預設選中覆蓋物(Highlighting Values)
  6. 線條的隱藏以及顯示(visible)
  7. 實現左右滑動
  8. 資料更新

當前演示 Demo

快速入門

1.編寫佈局檔案

 <com.github.mikephil.charting.charts.LineChart
        android:id="@+id/chart"
        android:layout_width="match_parent"
        android:layout_height="195dp"
        />

2.例項化並且,設定x軸和y軸的點

mLineChart = findViewById(R.id.chart);

//1.設定x軸和y軸的點
List<Entry> entries = new
ArrayList<>(); for (int i = 0; i < 12; i++) entries.add(new Entry(i, new Random().nextInt(300)));

3 .把資料賦值到你的線條

  LineDataSet dataSet = new LineDataSet(entries, "Label"); // add entries to dataset

4.設定資料重新整理圖表

//3.chart設定資料
  LineData lineData = new LineData(dataSet);
  mLineChart.setData(lineData);
  mLineChart.invalidate(); // refresh

這裡寫圖片描述

很簡單吧,但是離我們的效果圖還差了好多現在我們開始完善樣式,一步一步去設定

樣式設定

1.線條樣式

        LineDataSet dataSet = new LineDataSet(entries, "Label"); // add entries to dataset
        dataSet.setColor(Color.parseColor("#7d7d7d"));//線條顏色
        dataSet.setCircleColor(Color.parseColor("#7d7d7d"));//圓點顏色
        dataSet.setLineWidth(1f);//線條寬度

2.x和y軸樣式

        //設定樣式
        YAxis rightAxis = mLineChart.getAxisRight();

        //設定圖表右邊的y軸禁用
        rightAxis.setEnabled(false);
        YAxis leftAxis = mLineChart.getAxisLeft();
        //設定圖表左邊的y軸禁用
        leftAxis.setEnabled(false);
        //設定x軸
        XAxis xAxis = mLineChart.getXAxis();
        xAxis.setTextColor(Color.parseColor("#333333"));
        xAxis.setTextSize(11f);
        xAxis.setAxisMinimum(0f);
        xAxis.setDrawAxisLine(true);//是否繪製軸線
        xAxis.setDrawGridLines(false);//設定x軸上每個點對應的線
        xAxis.setDrawLabels(true);//繪製標籤  指x軸上的對應數值
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);//設定x軸的顯示位置
        xAxis.setGranularity(1f);//禁止放大後x軸標籤重繪

3.隱藏圖例與描述


        //透明化圖例
        Legend legend = mLineChart.getLegend();
        legend.setForm(Legend.LegendForm.NONE);
        legend.setTextColor(Color.WHITE);

        //隱藏x軸描述
        Description description = new Description();
        description.setEnabled(false);
        mLineChart.setDescription(description);

4.填充資料

        //chart設定資料
        LineData lineData = new LineData(dataSet);
        //是否繪製線條上的文字
        lineData.setDrawValues(false);
        mLineChart.setData(lineData);
        mLineChart.invalidate(); // refresh

效果圖
這裡寫圖片描述

是不是已經很接近效果圖了,我們在格式化一下x軸標籤

x軸標籤自定義標籤(Formatting Data Values (ValueFormatter))

格式化x軸標籤有好幾種方式,這裡說兩個方法
1.要麼自己實現介面的方式

 XAxis xAxis = mLineChart.getXAxis();
 xAxis.setValueFormatter(new IAxisValueFormatter() {
            @Override
            public String getFormattedValue(float value, AxisBase axis) {
                return String.valueOf((int) value + 1).concat("月");
            }
        });

2.要麼用庫已經寫好的類

//準備好每個點對應的x軸數值
List<String> list = new ArrayList<>();
 for (int i = 0; i < 12; i++) {
     list.add(String.valueOf(i+1).concat("月"));
 }
  XAxis xAxis = mLineChart.getXAxis();
 xAxis.setValueFormatter(new IndexAxisValueFormatter(list));

格式化Y軸也是同樣的道理

效果圖
這裡寫圖片描述

自定義覆蓋物(MarkerView)

(1) 繼承MarkerView複寫其中的方法就OJBK了

直接上程式碼解釋吧 -v-

public class DetailsMarkerView extends MarkerView {

    private TextView mTvMonth;
    private TextView mTvChart1;

    /**
     * 在構造方法裡面傳入自己的佈局以及例項化控制元件    
     * @param context 上下文
     * @param 自己的佈局 
   */
    public DetailsMarkerView(Context context, int layoutResource) {
        super(context, layoutResource);
        mTvMonth = findViewById(R.id.tv_chart_month);
        mTvChart1 = findViewById(R.id.tv_chart_1);
    }

    //每次重繪,會呼叫此方法重新整理資料
    @Override
    public void refreshContent(Entry e, Highlight highlight) {
        super.refreshContent(e, highlight);
        try {
            //收入
            if (e.getY() == 0) {
                mTvChart1.setText("暫無資料");
            } else {
                mTvChart1.setText(concat(e.getY(), "支出:"));
            }
            mTvMonth.setText(String.valueOf((int) e.getX() + 1).concat("月"));
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        super.refreshContent(e, highlight);
    }

    //佈局的偏移量。就是佈局顯示在圓點的那個位置
    // -(width / 2) 佈局水平居中
    //-(height) 佈局顯示在圓點上方
    @Override
    public MPPointF getOffset() {
        return new MPPointF(-(getWidth() / 2), -getHeight());
    }

    public String concat(float money, String values) {
        return values + new BigDecimal(money).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "元";
    }

}

(2) 設定覆蓋物

DetailsMarkerView detailsMarkerView = new DetailsMarkerView(this);
//一定要設定這個玩意,不然到點選到最邊緣的時候不會自動調整佈局
detailsMarkerView.setChartView(mLineChart);
mLineChart.setDetailsMarkerView(detailsMarkerView);

效果圖

這裡寫圖片描述

自定義多個覆蓋物(MarkerView)

接下來我們繼續完善,達到下面的效果圖

這裡寫圖片描述

要達到上面的效果,我們可以把它當作3個覆蓋物
就是這個意思
這裡寫圖片描述

1.先定義好3個覆蓋物,DetailsMarkerView(詳情),PositionMarker (中間的標杆)RoundMarker(圓點)

class DetailsMarkerView  extends MarkerView{...}
class PositionMarker  extends MarkerView{...}
class RoundMarkerextends MarkerView{...}

2.繼承LineChart,重寫drawMarkers 方法。我們直接把drawMarkers方法直接複製下來,加上自己所需要的MarkerView,然後計算它們的位置即可

public class MyLineChart extends LineChart {
      //弱引用覆蓋物物件,防止記憶體洩漏,不被回收
    private WeakReference<DetailsMarkerView> mDetailsReference;
    private WeakReference<RoundMarker> mRoundMarkerReference;
    private WeakReference<PositionMarker> mPositionMarkerReference;

    /**
     * 所有覆蓋物是否為空
     *
     * @return TRUE FALSE
     */
    public boolean isMarkerAllNull() {
        return mDetailsReference.get() == null && mRoundMarkerReference.get() == null && mPositionMarkerReference.get() == null;
    }

    public void setDetailsMarkerView(DetailsMarkerView detailsMarkerView) {
        mDetailsReference = new WeakReference<>(detailsMarkerView);
    }

    public void setRoundMarker(RoundMarker roundMarker) {
        mRoundMarkerReference = new WeakReference<>(roundMarker);
    }

    public void setPositionMarker(PositionMarker positionMarker) {
        mPositionMarkerReference = new WeakReference<>(positionMarker);
    }


   /**
      複製父類的 drawMarkers方法,並且更換上自己的markerView
     * draws all MarkerViews on the highlighted positions
     */
    protected void drawMarkers(Canvas canvas) {
        DetailsMarkerView mDetailsMarkerView = mDetailsReference.get();
        RoundMarker mRoundMarker = mRoundMarkerReference.get();
        PositionMarker mPositionMarker = mPositionMarkerReference.get();

        // if there is no marker view or drawing marker is disabled
        if (mDetailsMarkerView == null || mRoundMarker == null || mPositionMarker == null || !isDrawMarkersEnabled() || !valuesToHighlight())
            return;

        for (int i = 0; i < mIndicesToHighlight.length; i++) {

            Highlight highlight = mIndicesToHighlight[i];

            IDataSet set = mData.getDataSetByIndex(highlight.getDataSetIndex());

            Entry e = mData.getEntryForHighlight(mIndicesToHighlight[i]);

            int entryIndex = set.getEntryIndex(e);

            // make sure entry not null
            if (e == null || entryIndex > set.getEntryCount() * mAnimator.getPhaseX())
                continue;

            float[] pos = getMarkerPosition(highlight);

            LineDataSet dataSetByIndex = (LineDataSet) getLineData().getDataSetByIndex(highlight.getDataSetIndex());

            // check bounds
            if (!mViewPortHandler.isInBounds(pos[0], pos[1]))
                continue;

            float circleRadius = dataSetByIndex.getCircleRadius();

            //pos[0], pos[1] x 和 y 
            // callbacks to update the content
            mDetailsMarkerView.refreshContent(e, highlight);

            mDetailsMarkerView.draw(canvas, pos[0], pos[1] - mPositionMarker.getHeight());


            mPositionMarker.refreshContent(e, highlight);
            mPositionMarker.draw(canvas, pos[0] - mPositionMarker.getWidth() / 2, pos[1] - mPositionMarkerl.getHeight());

            mRoundMarker.refreshContent(e, highlight);
            mRoundMarker.draw(canvas, pos[0] - mRoundMarker.getWidth() / 2, pos[1] + circleRadius - mRoundMarker.getHeight());
        }
}

設定覆蓋物 activity主要程式碼

protected void onCreate(Bundle savedInstanceState) {
    ......
    //點選圖表座標監聽
        mLineChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
            @Override
            public void onValueSelected(Entry e, Highlight h) {
                //檢視覆蓋物是否被回收
                if (mLineChart.isMarkerAllNull()) {
                    //重新繫結覆蓋物
                    createMakerView();
                    //並且手動高亮覆蓋物
                    mLineChart.highlightValue(h);
                }
            }

            @Override
            public void onNothingSelected() {

            }
        });
    ......
}


  /**
    * 建立覆蓋物
    */
   public void createMakerView() {
       DetailsMarkerView detailsMarkerView = new DetailsMarkerView(this);
       detailsMarkerView.setChartView(mLineChart);
       mLineChart.setDetailsMarkerView(detailsMarkerView);
       mLineChart.setPositionMarker(new PositionMarker(this));
       mLineChart.setRoundMarker(new RoundMarker(this));
   }

這樣就大功告成啦!!

預設顯示覆蓋物(Highlighting Values)

這裡寫圖片描述

可以通過上面的方法,預設顯示覆蓋物,比如

   //預設顯示第一個覆蓋物
   mLineChart.highlightValue(0,0);

線條的隱藏以及顯示(Highlighting Values)

可以通過LineChart物件獲取到線條LineDataSet實體類。然後呼叫LineDataSet.setVisible(true或者false);,進行隱藏或顯示

mLineChart.getLineData().getDataSets().get(0).setVisible(true);

左右滑動,並動態切換放大倍數

這裡寫圖片描述

程式碼

//x放大5倍  1f代表不放大
mLineChart.zoomToCenter(5, 1f);


//切記如果要動態的更換倍數,或者還原倍數一定要呼叫下面的這個方法停止慣性滑動
//不然在拖動過程當中是無法更換倍數
BarLineChartTouchListener barLineChartTouchListener = (BarLineChartTouchListener) mLineChart.getOnTouchListener();
 barLineChartTouchListener.stopDeceleration();

更新資料

主要的邏輯:
1. 準備要更新的資料來源
2. 檢查是否有LineDataSet 存在
3. 有,則通過LineDataSet 的setValues更換整個座標,或者 data.addEntry(…) 新增一個或者 data.removeEntry(…)刪除一個。
4. 無,則建立LineDataSet ,重新構造資料來源
4. 呼叫mLineChart.invalidate();更新圖表

程式碼例項:

 //1,準備要更換的資料
     List<Entry> entries = new ArrayList<>();
        for (int i = 0; i < 12; i++)
            entries.add(new Entry(i, new Random().nextInt(300)));

        //2. 獲取LineDataSet線條資料集
        List<ILineDataSet> dataSets = mLineChart.getLineData().getDataSets();

       //是否存在
         if (dataSets != null && dataSets.size() > 0) {
             //直接更換資料來源
             for (ILineDataSet set : dataSets) {
                 LineDataSet data = (LineDataSet) set;
                 data.setValues(entries);
             }
         } else {
             //重新生成LineDataSet線條資料集
             LineDataSet dataSet = new LineDataSet(entries, "Label"); // add entries to dataset
            dataSet.setDrawCircles(false);
               dataSet.setColor(Color.parseColor("#7d7d7d"));//線條顏色
               dataSet.setCircleColor(Color.parseColor("#7d7d7d"));//圓點顏色
               dataSet.setLineWidth(1f);//線條寬度
               LineData lineData = new LineData(dataSet);
               //是否繪製線條上的文字
               lineData.setDrawValues(false);
               mLineChart.setData(lineData);
           }
           //更新
           mLineChart.invalidate();

折線圖的內容暫時就那麼多,如果有不懂的可以留言,希望可以幫到大家。

最後附上 Demo