轉載請注明出處:http://write.blog.csdn.net/postedit/50434634
接上篇?[Android 圓形百分比(進度條) 自定義view](http://blog.csdn.net/wingichoy/article/details/50334595)
昨天分手了,不開心,來練練自定義view麻痹自己,畢竟菜鳥只能靠不斷練習提高。#程序員不應該有女朋友#
我們要實現的是一種只有來看趨勢,不需要看具體數值,比較簡約的折線圖。比如下圖這樣的:

這個時候,一些比較優秀的第三方圖表庫如:MPChart 就顯得比較臃腫了。所以我們需要自定義一個折線圖。
老規矩,先來看最終的實現效果:

其實這種做的很簡約,大概分三個步驟:
一、畫坐標軸
二、畫點
三、畫線
那么我們開始吧Let's go (Let it go)。
設計一下大概需要的東西。首先把X軸和Y軸的數據存放在兩個String[]里。
具體的點的位置用一個Map<Integer,Integer>來存放.
**步驟:**
一、新建一個類,取名為SimpleLineChart繼承View 重寫他的構造方法。這里為了簡便,就不添加自定義屬性了attr.xml。
~~~
<span style="font-size:18px;"> </span><span style="font-size:12px;"> public SimpleLineChart(Context context) {
this(context, null);
}
public SimpleLineChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SimpleLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}</span>
~~~
二、測量大小。(繼續偷懶,只支持EXACTLY,AT_MOST直接丟異常)
~~~
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;
}else if(widthMode == MeasureSpec.AT_MOST){
throw new IllegalArgumentException("width must be EXACTLY,you should set like android:width=\"200dp\"");
}
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
}else if(widthMeasureSpec == MeasureSpec.AT_MOST){
throw new IllegalArgumentException("height must be EXACTLY,you should set like android:height=\"200dp\"");
}
setMeasuredDimension(mWidth, mHeight);
}
~~~
三、重點來了,開始draw。首先處理幾種異常情況。當X軸或者Y軸為沒有文字(也就是沒有刻度的時候),拋出異常。
~~~
if(mXAxis.length==0||mYAxis.length==0){
throw new IllegalArgumentException("X or Y items is null");
}
~~~
當沒有任何點的數據的時候,顯示字符串提醒用戶沒有數據(實際上是往中心drawText)。?
~~~
?//畫坐標線的軸
? ? ? ? Paint axisPaint = new Paint();
? ? ? ? axisPaint.setTextSize(mYAxisFontSize);
? ? ? ? axisPaint.setColor(Color.parseColor("#3F51B5"));
~~~
~~~
if (mPointMap == null || mPointMap.size() == 0) {
int textLength = (int) axisPaint.measureText(mNoDataMsg);
canvas.drawText(mNoDataMsg, mWidth/2 - textLength/2, mHeight/2, axisPaint);
}
~~~
異常情況處理完了,開始上面三個步驟挨個畫,首先來畫個Y軸,計算每個刻度的間隔。他的值應該是mWidth / Y軸文字個數,然后用循環把每個刻度都畫出來。再申請一個數組yPoints,存放每個Y刻度的具體坐標。
~~~
//畫 Y 軸
//存放每個Y軸的坐標
int[] yPoints = new int[mYAxis.length];
//計算Y軸 每個刻度的間距
int yInterval = (int) ((mHeight - mYAxisFontSize - 2) / (mYAxis.length));
//測量Y軸文字的高度 用來畫第一個數
Paint.FontMetrics fm = axisPaint.getFontMetrics();
int yItemHeight = (int) Math.ceil(fm.descent - fm.ascent);
Log.e("wing", mHeight + "");
for (int i = 0; i < mYAxis.length; i++) {
canvas.drawText(mYAxis[i], 0, mYAxisFontSize + i * yInterval, axisPaint);
yPoints[i] = (int) (mYAxisFontSize + i * yInterval);
}
~~~
我們運行一下,看到了如下效果:

需要注意的是,這里的坐標需要微調,大家多試一下。同理開始畫X軸:
~~~
//畫 X 軸
//x軸的刻度集合
int[] xPoints = new int[mXAxis.length];
Log.e("wing", xPoints.length + "");
//計算Y軸開始的原點坐標
int xItemX = (int) axisPaint.measureText(mYAxis[1]);
//X軸偏移量
int xOffset = 50;
//計算x軸 刻度間距
int xInterval = (int) ((mWidth - xOffset) / (mXAxis.length));
//獲取X軸刻度Y坐標
int xItemY = (int) (mYAxisFontSize + mYAxis.length * yInterval);
for (int i = 0; i < mXAxis.length; i++) {
canvas.drawText(mXAxis[i], i * xInterval + xItemX + xOffset, xItemY, axisPaint);
xPoints[i] = (int) (i * xInterval + xItemX + axisPaint.measureText(mXAxis[i]) / 2 + xOffset + 10);
// Log.e("wing", xPoints[i] + "");
}
~~~
注意這里X軸的y坐標就是畫Y軸時候的最下面的文字(最后一個)的坐標,存成了xItemY。

之后我們來畫點,這里我采用的方法是畫圓。直接drawCircle。從map中取出所有點的對應i,j然后再從兩個數組 xPoints[] yPoints[]取出真實的X,Y坐標,最后畫出來
~~~
//畫點
Paint pointPaint = new Paint();
pointPaint.setColor(mLineColor);
Paint linePaint = new Paint();
linePaint.setColor(mLineColor);
linePaint.setAntiAlias(true);
//設置線條寬度
linePaint.setStrokeWidth(mStrokeWidth);
pointPaint.setStyle(Paint.Style.FILL);
for (int i = 0; i < mXAxis.length; i++) {
if (mPointMap.get(i) == null) {
throw new IllegalArgumentException("PointMap has incomplete data!");
}
//畫點
canvas.drawCircle(xPoints[i], yPoints[mPointMap.get(i)], mPointRadius, pointPaint);
if (i > 0) {//畫線
canvas.drawLine(xPoints[i - 1], yPoints[mPointMap.get(i - 1)], xPoints[i], yPoints[mPointMap.get(i)], linePaint);
}
}
~~~
上面畫完點之后開始畫線drawLine,參數是前一個點的坐標,和后一個點的坐標。挨個畫出來。

??這時候我們的最復雜的繪制就完成了。接下來來加入一點功能:參數的設置。
~~~
/**
* 設置map數據
* @param data
*/
public void setData(HashMap<Integer,Integer> data){
mPointMap = data;
invalidate();
}
/**
* 設置Y軸文字
* @param yItem
*/
public void setYItem(String[] yItem){
mYAxis = yItem;
}
/**
* 設置X軸文字
* @param xItem
*/
public void setXItem(String[] xItem){
mXAxis = xItem;
}
public void setLineColor(int color){
mLineColor = color;
invalidate();
}
~~~
以上代碼很簡單 我就不多說了。整個View完工,接下來介紹如何使用。
**使用:**
****和普通的View一樣,我們直接在XML布局文件中加入SimpleLineChart,注意不要忘記包名。
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.wingsofts.simplelinechart.MainActivity">
<com.wingsofts.simplelinechart.SimpleLineChart
android:id="@+id/simpleLineChart"
android:layout_width="400dp"
android:layout_height="200dp" />
</RelativeLayout>
~~~
然后在Activity中findviewbyid,給他設置X軸的文字 Y軸的文字 還有數據源
~~~
public class MainActivity extends AppCompatActivity {
private SimpleLineChart mSimpleLineChart;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSimpleLineChart = (SimpleLineChart) findViewById(R.id.simpleLineChart);
String[] xItem = {"1","2","3","4","5","6","7"};
String[] yItem = {"10k","20k","30k","40k","50k"};
if(mSimpleLineChart == null)
Log.e("wing","null!!!!");
mSimpleLineChart.setXItem(xItem);
mSimpleLineChart.setYItem(yItem);
HashMap<Integer,Integer> pointMap = new HashMap();
for(int i = 0;i<xItem.length;i++){
pointMap.put(i, (int) (Math.random()*5));
}
mSimpleLineChart.setData(pointMap);
}
}
~~~
簡單的幾步,就可以得到預覽圖的效果了!是不是很好玩!覺得好的話評論一下,star一下。祭奠我死去的愛情。
項目下載地址(求關注 求星星 ):[點擊打開鏈接](https://github.com/githubwing/SimpleLineChart/)
下一篇來一個比較炫 比較復雜的view 自定義儀表盤 :[時尚自定義儀表盤](http://blog.csdn.net/wingichoy/article/details/50468674)
- 前言
- android自定義viewgroup初步之一----抽屜菜單
- Android 自定義view --圓形百分比(進度條)
- Android 自定義View -- 簡約的折線圖
- 新手自定義view練習實例之(一) 泡泡彈窗
- 新手自定義view練習實例之(二) 波浪view
- 手把手帶你畫一個 時尚儀表盤 Android 自定義View
- 手把手帶你畫一個動態錯誤提示 Android自定義view
- 手把手帶你做一個超炫酷loading成功動畫view Android自定義view
- 關于Android自定義view 你所需要知道的基本函數
- Android自定義view進階-- 神奇的貝塞爾曲線
- wing帶你玩轉自定義view系列(1) 仿360內存清理效果
- wing帶你玩轉自定義view系列(2) 簡單模仿qq未讀消息去除效果
- wing帶你玩轉自定義view系列(3)模仿微信下拉眼睛
- 手把手教你畫一個 逼格滿滿圓形水波紋loadingview Android
- 有坑?? 為何wing墜入PorterDuffXferMode的萬丈深淵(PorterDuffXferMode深入試驗)
- 手把手帶你畫一個漂亮蜂窩view Android自定義view
- 一個炫字都不夠??!!!手把手帶你打造3D自定義view
- 恭喜發財! -- 手把手教你仿造一個qq下拉搶紅包 Android自定義view