週末在逛慕課網的時候,看到了一張學習計劃報告圖,詳細記錄了本身一週的學習狀況,每天都是0節課啊!正好在學習Android自定義View,因而就想着本身去寫了一個,這裏先給出一張慕課網的圖,和本身的效果圖。android
yissan的博客,未經容許嚴禁轉載 http://blog.csdn.net/yissancanvas
咱們要實現這樣一個折線統計圖,必要的信息主要有下面幾個數組
先看縱軸,縱軸須要的信息有最大值,還有用來肯定每一個間距表明的單位,好比最大值是100,咱們還要有一個將值分爲幾份的數據。app
接下來看橫軸,由於橫軸的信息通常是文字,不能像數字經過累加就能夠獲得,因此直接保存一個字符串數組變量。ide
而後就到了折線了,畫折線只須要每一個橫軸單位的縱軸數據y座標肯定而後鏈接起來就ok了,這裏只須要根據左邊的單位的間距和每一個單位的值就能夠獲取到y的具體座標。佈局
那麼總結起來就須要:
一、縱軸最大值
二、縱軸分割數量
三、縱軸每一個小單位的值 經過 最大值/分割數量計算
四、用來橫軸顯示的數組
五、橫軸間距、縱軸間距
六、具體的數組(用來畫折線)學習
有了上面的信息就能夠去draw了,下面開始具體的自定義View步驟講解this
在以前的文章,寫過一篇介紹了自定義的步驟的文章——一塊兒來學習Android自定義控件1,咱們就按照這個步驟來說解說明。.net
主要肯定該繼承View仍是一些特定的View,定義和獲取屬性、添加設置屬性方法。code
定義屬性
<?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值的方法
處理佈局首先考慮的是根據須要重寫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); }
接下來就能夠實現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
聲明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}); }
再來一張效果圖
自定義View就是多練,看到一個喜歡的效果,想不想能不能本身的畫一個,時間久了,相信咱們均可以輕鬆的寫出很好的自定義View
由於最近工做有點忙,因此不少地方不完善。在這裏分享一下,但願你們喜歡。