Android自訂View4——統計圖View

來源:互聯網
上載者:User

標籤:

1、介紹

周末在逛慕課網的時候,看到了一張學習計劃報告圖,詳細記錄了自己一周的學習情況,天天都是0節課啊!正好在學習Android自訂View,於是就想著自己去寫了一個,這裡先給出一張慕課網的圖,和自己的。

yissan的部落格,未經允許嚴禁轉載 http://blog.csdn.net/yissan

2、實現分析

我們要實現這樣一個折線統計圖,必要的資訊主要有下面幾個

先看縱軸,縱軸需要的資訊有最大值,還有用來確定每個間距代表的單位,比如最大值是100,我們還要有一個將值分為幾份的資料。

接下來看橫軸,因為橫軸的資訊一般是文字,不能像數字通過累加就可以得到,所以直接儲存一個字串陣列變數。

然後就到了折線了,畫折線只需要每個橫軸單位的縱軸資料y座標確定然後串連起來就ok了,這裡只需要根據左邊的單位的間距和每個單位的值就可以擷取到y的具體座標。

那麼總結起來就需要:
1、縱軸最大值
2、縱軸分割數量
3、縱軸每個小單位的值 通過 最大值/分割數量計算
4、用來橫軸顯示的數組
5、橫軸間距、縱軸間距
6、具體的數組(用來畫折線)

有了上面的資訊就可以去draw了,下面開始具體的自訂View步驟講解

3、具體實現

在之前的文章,寫過一篇介紹了自訂的步驟的文章——一起來學習Android自訂控制項1,我們就按照這個步驟來講解說明。

(1) 建立View

主要確定該繼承View還是一些特定的View,定義和擷取屬性、添加設定屬性方法。

定義屬性

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="StatisticsView">        <attr name="maxValue" format="integer"></attr>        <attr name="dividerCount" format="integer"></attr>        <attr name="title" format="integer"></attr>        <attr name="lineColor" format="color"></attr>        <attr name="textColor" format="color"></attr>        <attr name="pathColor" format="color"></attr>    </declare-styleable></resources>

在構造方法中擷取屬性

public class StatisticsView extends View {    //畫橫縱軸    private Paint mBorderPaint;    //畫座標點的圓心    private Paint circlePaint;    //畫折線圖    private Paint mPathPaint;    private Path mPath;    //縱軸最大值    private int maxValue = 100;    //縱軸分割數量    private int dividerCount = 10;    private String title = "七日學習情況(單位節)";    //縱軸每個單位值    private int perValue = maxValue/dividerCount;    //底部顯示String    private String[] bottomStr = {};    //具體的值    private float[] values = {};    //底部橫軸單位間距    private float bottomGap;    //左邊縱軸間距    private float leftGap;    private TextPaint textPaint;    public void setValues(float[] values) {        this.values = values;        invalidate();    }    public void setBottomStr(String[] bottomStr) {        this.bottomStr = bottomStr;        requestLayout();    }    public StatisticsView(Context context) {        super(context);    }    public StatisticsView(Context context, AttributeSet attrs) {        this(context, attrs,0);    }    public StatisticsView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.StatisticsView);        maxValue =array.getInt(R.styleable.StatisticsView_maxValue,100);        dividerCount = array.getInt(R.styleable.StatisticsView_dividerCount,10);        title = array.getString(R.styleable.StatisticsView_title);        int lineColor = array.getColor(R.styleable.StatisticsView_lineColor,Color.BLACK);        int textColor =array.getColor(R.styleable.StatisticsView_textColor,Color.BLACK);        mBorderPaint = new Paint();        circlePaint = new Paint();        mPathPaint = new Paint();        mBorderPaint.setAntiAlias(true);        mBorderPaint.setColor(lineColor);        mBorderPaint.setStrokeWidth(1);        mBorderPaint.setStyle(Paint.Style.STROKE);        mPathPaint.setAntiAlias(true);        mPathPaint.setStyle(Paint.Style.STROKE);        mPathPaint.setStrokeWidth(3);        textPaint = new TextPaint();        textPaint.setColor(textColor);        textPaint.setTextSize(dip2px(getContext(),12));        mPath = new Path();        circlePaint.setStyle(Paint.Style.FILL);        circlePaint.setAntiAlias(true);        array.recycle();    }}

上面的代碼簡單的擷取到了屬性、初始化了一些資訊。同時對外提供了設定values值的方法

(2)處理View的布局

處理布局首先考慮的是根據需要重寫onMeasure方法。這裡為了簡單就直接讓wrap_content的情況下直接寬高相等。當然你也可以有一個代表每個間距寬高的屬性,然後去計算wrap_content下的寬高。

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        if (widthMode==MeasureSpec.EXACTLY&&heightMode==MeasureSpec.EXACTLY){            setMeasuredDimension(widthSize,heightSize);        }else if (widthMeasureSpec==MeasureSpec.EXACTLY){            setMeasuredDimension(widthSize,widthSize);        }else if (heightMeasureSpec==MeasureSpec.EXACTLY){            setMeasuredDimension(heightSize,heightSize);        }    }

由於在draw的時候要確定橫軸的單位間距,我們需要擷取它,一般我們擷取值可以在onSizeChange方法中擷取,但是由於我們底部的gap需要根據要顯示幾個來確定。但是才開始的時候bottomStr[]的length為0,之後通過set方法為bottomStr設定不會再次調用onSizeChange。bottomGap就會是最開始的值,這樣效果會出問題,所以就在onLayout方法中擷取。

    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        bottomGap = getWidth()/(bottomStr.length+1);        leftGap = getHeight()/(dividerCount+2);        super.onLayout(changed, left, top, right, bottom);    }
(3)、繪製View(Draw)

接下來就可以實現onDraw()來繪製View了

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (bottomStr==null||bottomStr.length==0){            return;        }        //畫左邊的線        canvas.drawLine(bottomGap,getHeight()-leftGap,bottomGap,leftGap,mBorderPaint);        float fontHeight =(textPaint.getFontMetrics().descent-textPaint.getFontMetrics().ascent);        //畫下邊線        canvas.drawLine(bottomGap,getHeight()-leftGap,getWidth()-bottomGap,getHeight()-leftGap,mBorderPaint);        for (int i = 1;i<=bottomStr.length;i++){            canvas.drawCircle(i*bottomGap,getHeight()-leftGap,6,circlePaint);            canvas.drawText(bottomStr[i-1],i*bottomGap-(textPaint.measureText(bottomStr[i-1])/2),getHeight()-leftGap/2+fontHeight/2,textPaint);        }        canvas.drawText(title,bottomGap,leftGap/2,textPaint);        for (int i = 1;i<=dividerCount+1;i++){             //畫左邊的字            canvas.drawText(perValue*(i-1)+"",bottomGap/2-(textPaint.measureText(perValue*(i-1)+"")/2),(((dividerCount+2-i)))*leftGap+fontHeight/2,textPaint);            //畫橫線            canvas.drawLine(bottomGap,getHeight()-((i)*leftGap),getWidth()-bottomGap,getHeight()-((i)*leftGap),mBorderPaint);        }        /**         * 畫軌跡         * y的座標點根據 y/leftGap = values[i]/perValue 計算         *         */        for (int i = 0;i<values.length;i++){            if (i==0){                mPath.moveTo(bottomGap,(dividerCount+1)*leftGap-(values[i]*leftGap/perValue));            }else{                mPath.lineTo((i+1)*bottomGap,(dividerCount+1)*leftGap-(values[i]*leftGap/perValue));            }            /**             * 畫軌跡圓點             */            canvas.drawCircle((i+1)*bottomGap,(dividerCount+1)*leftGap-(values[i]*leftGap/perValue),6,circlePaint);        }        canvas.drawPath(mPath,mPathPaint);    }    public static int dip2px(Context context, float dpValue) {        final float scale = context.getResources().getDisplayMetrics().density;        return (int) (dpValue * scale + 0.5f);    }

代碼都加了注釋,主要是一些計算,還有drawLine,drawPath,drawText,以及擷取text寬高的一些知識。

yissan的部落格,未經允許嚴禁轉載 http://blog.csdn.net/yissan

4、使用

聲明View,然後在Activity裡擷取View並且調用setBottomStr和setValues方法

<com.qiangyu.test.statisticsview.view.StatisticsView        android:id="@+id/statisticsView"        android:layout_width="match_parent"        android:layout_height="300dp"        app:viewTitle="七日學習情況(單位 節)"/>
 public void invalidate(View view) {        this.view.setBottomStr(new String[]{"星期一","星期二","星期三","星期四","星期五","星期六","星期天"});        this.view.setValues(new float[]{10f,90f,33f,66f,42f,99f,0f});    }

再來一張

5、總結

自訂View就是多練,看到一個喜歡的效果,想不想能不能自己的畫一個,時間久了,相信我們都可以輕鬆的寫出很好的自訂View

因為最近工作有點忙,所以很多地方不完善。在這裡分享一下,希望大家喜歡。

Android自訂View4——統計圖View

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.