基于MPAndroidChart的自定義LineChart(二)----添加單擊事件的處理

上一篇文章基于MPAndroidChart的自定義LineChart(一)----節(jié)點(diǎn)繪制叉號(hào)+分段繪制背景中添加了節(jié)點(diǎn)繪制叉號(hào)+分段繪制背景的功能,這篇文章繼續(xù)改造,給MPAndroidChart的LineChart單擊事件做一些處理,讓圖表能跟響應(yīng)單擊操作,改變數(shù)據(jù)重繪圖表。

效果如下:

每次單擊圖表,對(duì)應(yīng)的數(shù)據(jù)都會(huì)改變,圖表也會(huì)重新繪制

功能實(shí)現(xiàn)

由于MPAndroiChart只支持縮放、拖動(dòng)(平移)、選擇等手勢(shì),但這些手圖表的數(shù)據(jù)沒(méi)有什么改變,如果我們需要在圖標(biāo)上根據(jù)手勢(shì)修改數(shù)據(jù),繼而重繪圖表,就需要自己處理了。好在MPAndroiChart為我們提供了OnChartGestureListener和OnChartValueSelectedListener兩個(gè)接口供我們處理手勢(shì)。

OnChartGestureListener

public interface OnChartGestureListener {

    void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture);
    
    void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture);

    void onChartLongPressed(MotionEvent me);

    void onChartDoubleTapped(MotionEvent me);

    void onChartSingleTapped(MotionEvent me);

    void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY);

    void onChartScale(MotionEvent me, float scaleX, float scaleY);

    void onChartTranslate(MotionEvent me, float dX, float dY);
}

從方法名就能看出在什么時(shí)候背調(diào)用了

OnChartValueSelectedListener

public interface OnChartValueSelectedListener {

    void onValueSelected(Entry e, Highlight h);

    void onNothingSelected();
}

onValueSelected是圖表中的某一個(gè)值被選擇后被調(diào)用,但是并不是說(shuō)一定要點(diǎn)在節(jié)點(diǎn)的圓環(huán)那里才會(huì)被選中,而是把圖表分為一段一段,如下圖所示:


點(diǎn)擊整列的任何位置都會(huì)調(diào)用onValueSelected方法。

繼承Linechart,初始化

第一步還是要繼承LineChart,并進(jìn)行初始化。

    private void initSingleTapLineChart() {

        values = new ArrayList<>();

        this.getDescription().setEnabled(false);
        this.setTouchEnabled(false);
        this.setDragEnabled(false);
        this.setScaleEnabled(false);
        this.setPinchZoom(false);
        this.setDrawBorders(false);

        // 設(shè)置是否可以觸摸
        this.setTouchEnabled(true);
        // 監(jiān)聽(tīng)觸摸事件
        this.setOnChartGestureListener(this);
        this.setOnChartValueSelectedListener(this);
        // 是否可以拖拽
        this.setDragEnabled(true);
        // 是否可以縮放
        this.setScaleEnabled(false);

    }

最重要的是設(shè)置圖表可以觸摸,可以拖拽,并添加OnChartGestureListener和OnChartValueSelectedListener兩個(gè)監(jiān)聽(tīng)器。

給圖表添加數(shù)據(jù)

這里我新建了一個(gè)setChartData方法,用戶(hù)將ArrayList傳進(jìn)方法,先對(duì)折線(xiàn)進(jìn)行配置,再添加到圖表中。

    public void setChartData(ArrayList<Entry> values) {
        this.values = values;
        XAxis xAxis = this.getXAxis();
        xAxis.setAxisMinimum(-1f);
        xAxis.setAxisMaximum(9f);
        xAxis.setGranularity(1f);
        xAxis.enableGridDashedLine(10f, 10f, 0f);
        xAxis.setDrawAxisLine(false);
        xAxis.setDrawLabels(true);
        xAxis.setLabelCount(11);
        YAxis leftAxis = this.getAxisLeft();
        leftAxis.removeAllLimitLines();
        leftAxis.setAxisMaximum(80f);
        leftAxis.setAxisMinimum(10f);
        leftAxis.setGranularity(10f);
        leftAxis.enableGridDashedLine(10f, 10f, 0f);
        leftAxis.setDrawZeroLine(false);
        leftAxis.setDrawAxisLine(false);
        leftAxis.setDrawLabels(true);
        this.getAxisRight().setEnabled(false);

        Legend l = this.getLegend();
        l.setForm(Legend.LegendForm.LINE);

        LineDataSet set1 = new LineDataSet(values, "曲線(xiàn)");

        set1.enableDashedLine(10f, 5f, 0f);
        set1.enableDashedHighlightLine(10f, 5f, 0f);
        set1.setColor(Color.RED);
        set1.setDrawCircles(true);
        set1.setCircleColor(Color.RED);
        set1.setLineWidth(1f);
        set1.setValueTextSize(9f);
        set1.setDrawFilled(false);
        set1.setFormLineWidth(1f);
        set1.setFormLineDashEffect(new DashPathEffect(new float[]{10f, 5f}, 0f));
        set1.setFormSize(15.f);

        ArrayList<ILineDataSet> dataSets = new ArrayList<>();
        dataSets.add(set1);

        LineData data = new LineData(dataSets);
        //最后調(diào)用MPAndroidChart提供的setData方法
        this.setData(data);
    }

** 注意: ** 此方法是為了先配置折線(xiàn),再調(diào)用MPAndroidChart提供的setData方法。
到此時(shí)已經(jīng)可以正常的顯示一個(gè)圖表了,但還沒(méi)有加入單擊事件。

加入單擊事件

onValueSelected

在這里先獲取選中的值,拿到該值的Entry,通過(guò)getX()和getY()方法取得X,Y值,再通過(guò)getPixelForValues方法獲得該值的像素坐標(biāo)點(diǎn)。

    public void onValueSelected(Entry e, Highlight h) {
        // 獲取Entry
        iEntry = (int) e.getX();
        valEntry = e.getY();
        Log.i(TAG, "e.getX() = " + iEntry + "     e.getY() = " + valEntry);
        // 獲取選中value的坐標(biāo)
        MPPointD p = this.getPixelForValues(e.getX(), e.getY(), YAxis.AxisDependency.LEFT);
        xValuePos = p.x;
        yValuePos = p.y;
        Log.i(TAG, "xValuePos = " + xValuePos + "     yValuePos = " + yValuePos);
    }

onChartGestureEnd

我這里選了onChartGestureEnd方法而不是onChartSingleTapped方法,只是因?yàn)槲耶?dāng)時(shí)沒(méi)注意到第二個(gè),效果應(yīng)該一樣吧,嘿嘿。
先判斷手勢(shì)的類(lèi)型,是不是單擊事件,是的話(huà)就獲得單擊點(diǎn)的像素坐標(biāo)點(diǎn)。

    public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
        Log.i(TAG, "onChartGestureEnd, lastGesture: " + lastPerformedGesture);
        if (lastPerformedGesture == ChartTouchListener.ChartGesture.SINGLE_TAP) {
            Log.i(TAG, "SingleTapped");
            yTouchPostion = me.getY();
            changeTouchEntry();
        }
        this.highlightValues(null);
    }

到這里我們就取得了需要數(shù)據(jù)的X值,Y值,原本的像素坐標(biāo)點(diǎn)和單擊處像素坐標(biāo)點(diǎn)。

最后的處理


畫(huà)了一個(gè)簡(jiǎn)單的圖,比較好講解。
如圖,我們知道1點(diǎn)的Y值(Y1)和2點(diǎn)的Y值(Y2),同時(shí)求出了紅線(xiàn)的長(zhǎng)度(redLen),那么黃線(xiàn)的長(zhǎng)度(yellowLen)顯然就等于紅線(xiàn)長(zhǎng)度乘以Y2比Y1的值:

yellowLen = redLen * Y2 / Y1

換到表中,我們要求的是單擊處對(duì)應(yīng)在圖表上的值,即黃線(xiàn)長(zhǎng)度;紅線(xiàn)長(zhǎng)度是本來(lái)的Y值,Y2是單擊點(diǎn)處到0坐標(biāo)點(diǎn)處的距離,Y1是原來(lái)的值到0坐標(biāo)點(diǎn)處的距離。
最后刷新數(shù)據(jù),重繪圖表。

    public void changeTouchEntry() {
        // 獲取X軸和Y軸的0坐標(biāo)的pixel值
        MPPointD p = this.getPixelForValues(0, 0, YAxis.AxisDependency.LEFT);
        double yAixs0 = p.y;
        // 修改TouchEntry的y的值
        Log.i(TAG, "計(jì)算過(guò)程");
        Log.i(TAG, "yAixs0: " + yAixs0);
        double y1 = yValuePos - yAixs0;
        double y2 = yTouchPostion - yAixs0;
        Log.i(TAG, "原來(lái)的y值所在的坐標(biāo)減0點(diǎn)");
        Log.i(TAG, "yValuePos - yAixs0: " + y1);
        Log.i(TAG, "點(diǎn)擊的y值所在的坐標(biāo)減0點(diǎn)");
        Log.i(TAG, "yTouchPostion - yAixs0: " + y2);
        valEntry = (float) (valEntry * (y2 / y1));
        Log.i(TAG, "value");
        Log.i(TAG, "X: " + iEntry + "     Y: " + valEntry);
        values.set(iEntry, new Entry(iEntry, valEntry));
        this.notifyDataSetChanged();
        this.invalidate();
}

這樣就完成了單擊事件的處理。

添加響應(yīng)接口

當(dāng)然有時(shí)候我們要對(duì)單擊事件做處理,所以再添加一個(gè)接口就行了。

    public interface OnSingleTapListener{
        void onSingleTap(int x,float y);
    }

接口的回調(diào)我放到了重繪圖表后

this.notifyDataSetChanged();
        this.invalidate();

        if (onSingleTapListener != null){
            onSingleTapListener.onSingleTap(iEntry,valEntry);
        }

END

最后要說(shuō)到一點(diǎn),我把對(duì)圖表的配置放到了自定義類(lèi)里,這樣第一減少Activity內(nèi)的代碼量,提高可讀性;第二是比較方便,可以直接使用數(shù)據(jù);
第三是一個(gè)應(yīng)用中存在數(shù)個(gè)圖表時(shí),一般也會(huì)統(tǒng)一風(fēng)格,這樣寫(xiě)就能提高代碼復(fù)用率了。

最后,博客里畢竟說(shuō)得不詳細(xì),深入了解看代碼,歡迎交流討論:
https://github.com/xiaoniu/SingleTapLineChart

想要更了解MPAndroidChart,可以參考這一個(gè)系列的博客:
MPAndroidChart 教程----莊宏基

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容