自定義折線圖筆記

自定義折線圖的需求在 APP 開發中好像很常見了,事實上用一些第三方庫來繪制折線圖很容易實現,但感覺一個類能搞定的事情去接入一個庫好像有點不和諧啊。本著學習的目的,繪制了一個折線圖,下面上效果圖:

效果圖.png

我的自定義 view

  • 1.新建 Zhexiantu 繼承之 View ,重寫下面兩個構造函數,并在里面做一些初始化操作。
public Zhexiantu(Context context) {
        super(context);
        this.mContext = context;
        init();
    }

 public Zhexiantu(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.Zhexiantu);
        myBg = array.getColor(R.styleable.Zhexiantu_my_bg, 0xFFF7F7F7);  //默認值必須
        linechartColor = array.getColor(R.styleable.Zhexiantu_linechart_color,0xFF90E7FD);
        linechartSize = (int) array.getDimension(R.styleable.Zhexiantu_linechart_size,3);
        linechartBg = array.getColor(R.styleable.Zhexiantu_linechart_bg,0xFFFFFFFF);
        padding = (int) array.getDimension(R.styleable.Zhexiantu_linechart_padding,16);

        init();
        array.recycle(); //取完值,記得回收
    }

第二個構造函數中相比第一個構造函數多了一些自定義屬性取值的操作,自定義屬性需要在 res/values 文件夾下的 styles.xml 文件下定義屬性,eg:

<!--自定義屬性-->
    <declare-styleable name="Zhexiantu">
        <attr name="my_bg" format="color"/>
        <attr name="linechart_color" format="color"/>
        <attr name="linechart_size" format="dimension"/>
        <attr name="linechart_bg" format="color"/>
        <attr name="linechart_padding" format="dimension"/>
    </declare-styleable>

以上兩步都完成就可以在我們自己的自定義控件上使用自定義屬性了,eg:

  <com.lisheny.lenovo.myviewstudy.canvas.Zhexiantu
            android:id="@+id/zhexiantu"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:linechart_color="@color/color2"
            app:linechart_bg="@color/color13"/>

另外,附上 Paint 屬性設置的詳解 http://wuxiaolong.me/2016/08/20/Paint/

  • 2.重寫 onMeasure 方法,計算獲取畫布的寬高并計算尺 X、Y軸的區間間隔
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        switch (mode) {
            case MeasureSpec.AT_MOST:                             //(wrap_content)
                mWidth = MeasureSpec.getSize(widthMeasureSpec);
                mHeigt = 3 * mWidth / 5;
                break;
            case MeasureSpec.EXACTLY:                             //固定尺寸(如100dp)
                mWidth = MeasureSpec.getSize(widthMeasureSpec);
                mHeigt = MeasureSpec.getSize(heightMeasureSpec);
                break;
            case MeasureSpec.UNSPECIFIED:                         //比重 (layout_weight="1")
                mWidth = MeasureSpec.getSize(widthMeasureSpec);
                mHeigt = 3 * mWidth / 5;
                break;
        }

        //X軸區間間隔
        xDistance = (mWidth - 4 * dp2px(padding)) / (xText.length - 1);
        //Y軸區間間隔
        yDistance = (mHeigt - 2 * dp2px(padding)) / (yText.length - 1);
        setMeasuredDimension((int) mWidth, (int) mHeigt);
    }

通過 int mode = MeasureSpec.getMode(heightMeasureSpec); 獲取測量模式,根據不同的測量模式將寬高設置為合理的相應值。關于這三種測量模式的介紹:

  1. UNSPECIFIED ==》父容器沒有對當前View有任何限制,當前View可以任意取尺寸
  2. EXACTLY ==》當前的尺寸就是當前View應該取的尺寸
  3. AT_MOST ==》當前尺寸是當前View能取的最大尺寸

理解否?不理解沒事,我們知道什么情況下調用什么模式就行:

  1. xml 中 設置 wrap_content 時,會走 AT_MOST 模式;
  2. xml 中 設置 固定尺寸(如100dp) 時,會走 EXACTLY 模式;
  3. 這種使用比較少,xml 中 設置 比重 layout_weight="1" 時,會走 EXACTLY 模式;

注:onMeasure 函數會多次調用

  • 3.接下來就是重寫 onDraw 函數繪制 View
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制畫布顏色
        canvas.drawColor(myBg);

        drawTextAndGrid(canvas, textPaint, gridPaint);
        drawLineChart(canvas);
        drawDots(canvas);
    }

繪制步驟:

  1. 繪制畫布顏色,背景
  2. 繪制X、Y軸的坐標值和表格
  3. 繪制折線
  4. 繪制小圓點
  5. 完成!!!

特別的:計算Y軸的刻度

 /**
     * 根據輸入數據值的到對應Y軸坐標
     *
     * 這里需求:Y軸坐標增長 隨屏幕之上而下增加  注:變量盡量用浮點型,避免精度丟失
     * 公式:Y = Y軸開始值 + 每份坐標刻度所占Y軸長度的量{Y軸長度/總刻度} * 當前刻度值{value-min}
     * 即:Y = Y軸開始值{ dp2px(padding) + dp2px(5)} + 每份坐標刻度所占Y軸長度的量{(yDistance * (yText.length - 1)) / (yText[yText.length-1]-yText[0])} * 當前刻度值{(value - yText[0])}
     *
     * @param value
     * @return
     */
    private float getYcoordinate(float value) {
        return dp2px(padding) + dp2px(5) + (yDistance * (yText.length - 1)) / (yText[yText.length - 1] - yText[0]) * (value - yText[0]);
    }

其中:總刻度 = 最大Y刻度值(max) - 最小Y刻度值(min)

  • 最后:折線圖的繪制總的來說很簡單,主要點是載入數據的刻度值要和Y軸相對應,理解計算出Y軸坐標的公式,折線圖的繪制就簡單了,以上代碼不全,若要查看完成 Demo ,點擊此處傳送。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,841評論 25 708
  • 前言 支付寶有個查看月賬單的功能,最近一直在學習自定義View,于是就嘗試著自己實現了一個類似的折線圖。 下面是支...
    neo1949閱讀 4,085評論 2 13
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,180評論 4 61
  • 前晚,正當做著一個令人沮喪的夢,忍不住傷心淚流之時,聽到小女抽噎的聲音,拼命從夢中醒來。只見傻丫頭一吸一頓地,眼角...
    邊邊緣閱讀 246評論 11 8
  • 我也可以是個細心的人 這真是個驚人的發現。 丟三落四 毛毛躁躁 走路動不動就踢翻個凳子 這是典型我的作風。 一般這...
    荔枝PPTer閱讀 2,266評論 0 3