自定義折線圖的需求在 APP 開發(fā)中好像很常見了,事實上用一些第三方庫來繪制折線圖很容易實現(xiàn),但感覺一個類能搞定的事情去接入一個庫好像有點不和諧啊。本著學(xué)習(xí)的目的抬闷,繪制了一個折線圖,下面上效果圖:
我的自定義 view
- 1.新建
Zhexiantu
繼承之View
耕突,重寫下面兩個構(gòu)造函數(shù),并在里面做一些初始化操作笤成。
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); //默認(rèn)值必須
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(); //取完值,記得回收
}
第二個構(gòu)造函數(shù)中相比第一個構(gòu)造函數(shù)多了一些自定義屬性取值的操作眷茁,自定義屬性需要在 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
屬性設(shè)置的詳解 http://wuxiaolong.me/2016/08/20/Paint/
- 2.重寫
onMeasure
方法培遵,計算獲取畫布的寬高并計算尺 X浙芙、Y軸的區(qū)間間隔
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//這里的 mode 只使用了寬的值,要求嚴(yán)謹(jǐn)?shù)幕顟?yīng)該寬高都應(yīng)該考慮進(jìn)去籽腕,比如:
// int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
// int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
// int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
// int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
// } else if (widthSpecMode == MeasureSpec.AT_MOST) {
// } else if (heightSpecMode == MeasureSpec.AT_MOST) {
// }
int mode = MeasureSpec.getMode(widthMeasureSpec);
switch (mode) {
case MeasureSpec.AT_MOST: //(wrap_content)
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeigt = 3 * mWidth / 5;
break;
case MeasureSpec.EXACTLY: //固定尺寸(如100dp嗡呼、match_parent),
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軸區(qū)間間隔
xDistance = (mWidth - 4 * dp2px(padding)) / (xText.length - 1);
//Y軸區(qū)間間隔
yDistance = (mHeigt - 2 * dp2px(padding)) / (yText.length - 1);
setMeasuredDimension((int) mWidth, (int) mHeigt);
}
通過 int mode = MeasureSpec.getMode(widthMeasureSpec);
獲取測量模式,根據(jù)不同的測量模式將寬高設(shè)置為合理的相應(yīng)值皇耗。關(guān)于這三種測量模式的介紹:
-
UNSPECIFIED
==》父容器沒有對當(dāng)前View有任何限制南窗,當(dāng)前View可以任意取尺寸 -
EXACTLY
==》當(dāng)前的尺寸就是當(dāng)前View應(yīng)該取的尺寸 -
AT_MOST
==》當(dāng)前尺寸是當(dāng)前View能取的最大尺寸
理解否?不理解沒事郎楼,我們知道什么情況下調(diào)用什么模式就行:
- xml 中 設(shè)置
wrap_content
時万伤,會走AT_MOST
模式; - xml 中 設(shè)置 固定尺寸(如100dp) 時呜袁,會走
EXACTLY
模式敌买; - 這種使用比較少,xml 中 設(shè)置 比重
layout_weight="1"
時阶界,會走EXACTLY
模式放妈;
注:onMeasure
函數(shù)會多次調(diào)用
- 3.接下來就是重寫
onDraw
函數(shù)繪制 View
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制畫布顏色
canvas.drawColor(myBg);
drawTextAndGrid(canvas, textPaint, gridPaint);
drawLineChart(canvas);
drawDots(canvas);
}
繪制步驟:
- 繪制畫布顏色,背景
- 繪制X荐操、Y軸的坐標(biāo)值和表格
- 繪制折線
- 繪制小圓點
- 完成!U洳摺托启!
特別的:計算Y軸的刻度
/**
* 根據(jù)輸入數(shù)據(jù)值得到對應(yīng)Y軸坐標(biāo)
*
* 這里需求:Y軸坐標(biāo)增長 隨屏幕自上而下增加 注:變量盡量用浮點型,避免精度丟失
* 公式:Y = Y軸開始值 + 每份坐標(biāo)刻度所占Y軸長度的量{Y軸長度/總刻度} * 當(dāng)前刻度值{value-min}
* 即:Y = Y軸開始值{ dp2px(padding) + dp2px(5)} + 每份坐標(biāo)刻度所占Y軸長度的量{(yDistance * (yText.length - 1)) / (yText[yText.length-1]-yText[0])} * 當(dāng)前刻度值{(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)
- 最后:折線圖的繪制總的來說很簡單攘宙,主要點是載入數(shù)據(jù)的刻度值要和Y軸相對應(yīng)屯耸,理解計算出Y軸坐標(biāo)的公式,折線圖的繪制就簡單了蹭劈,以上代碼不全疗绣,若要查看完成 Demo ,點擊此處傳送铺韧。