Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中自定義折線圖

Android中自定義折線圖

編輯:關於Android編程

有時候,我們在做開發的時候,需要讓用戶更直觀的看到數據變化,而又不應該給其提供一堆表格顯示,有時候就需要用到,類似Excel中的圖表,可是Google官方並沒有提供自帶的圖表控件,這時候就需要我們自定義一個圖表控件用於顯示,我們想要的效果。(直接上圖)如下:

 

趨勢圖

 

先分析一下,效果圖(忽略標題欄)。最上面是運動天數共31天,間隔一定距離向右偏移。向下是Y軸虛線圖,數量點圖標及折線趨勢,最後做了一下區域渲染。

此自定義控件需要繼承View,一般情況下(不需要特殊要求的時候)我們自定義控件是都是繼承自View/ViewGroup。當需要在某個控件基礎上擴展的時候,才繼承View/ViewGroup的子類。繼承後需要從寫onDraw()方法,在此方法中繪制圖像,當然不可避免的需要用到幾個東西:Canvas,Paint,Path。

Paint:英文意思為油漆.繪畫.描繪。在計算機中稱為畫筆。作用就類似於繪制圖像要用的一樣筆,當然可以為paint設置不同的顏色,實心或空心,透明度,粗細,抗鋸齒等等,這些都可以通過set系列方法設置。比如,P.setAntiAlias(true)設置抗鋸齒;P.setStrokeWidth(float)設置畫筆粗細;P.setTextSize(float)設置畫筆寫的文字大小;P.setColor(Color.BLUE)設置顏色;P.setAlpha(int)設置透明度;P.setShader(shader)設置渲染器。

Canvas:英文意思是帆布,畫布。簡單理解就是,一個工具類,用於畫我們想要的效果,比如直線,曲線,圓,矩形……調用draw系列方法可以實現。在從寫ondraw(Canvas c)方法時系統會給我們一個Canvas類的對象,這樣就可以通過調用一系列方法繪制我們想要的圖形了。本片文章需要用到:

根據路徑畫線c.drawPath(path,paint);第一個參數為繪制的路徑path對象,第二個參數為畫筆對象paint。

根據起始點和終點畫線c.drawLine(startX, startY, stopX, stopY, paint);前兩個參數為起始點坐標(x,y)。後兩個參數為終點坐標(x,y)。注意,我們在手機上的坐標系與生活中的坐標系0點位置不一樣,在Android中屏幕坐標系,x軸為橫軸,y軸為縱軸。但是起點即零點為屏幕左上角頂點,X軸向右逐漸增大,Y軸向下逐漸增大。通俗講,X軸向右為正方向,Y軸向下為正方向。

畫圖c.drawBitmap(bitmap, left, top, paint);第一個參數bitmap對象,第二個與第三個參數為起始點(圖的左上角點),第四個參數為paint畫筆對象。注意,在Android中畫任何圖的時候,都應該有一個左上角點,不管是圓形,橢圓,不規則的圖,都可以想象成一個矩形框包裹住,這樣矩形的左上角頂點,就是繪制圖形開始的起點,即向右邊畫。這樣在控制圖像位置時候,就會很精確。

畫點c.drawPoint(x, y, paint);前兩個參數為,點的坐標。最後為畫筆工具。

畫矩形c.drawRect(left, top, right, bottom, paint);前兩個參數為矩形的左上角頂點坐標,然後兩個參數為矩形的右下角頂點坐標,最後參數為paint畫筆工具。用一張圖更形象的理解。四根紅色線代表前4個參數的距離。

 

這裡寫圖片描述

 

畫文本(寫字)c.drawText(text, x, y, paint);第一個參數為要寫的文本String,後面兩個參數為畫文本開始的左上角點位置(想象成在一個矩形框內寫字,開始點為矩形的左上角點),一直在強調左上角。

Path:英文意思是路徑,道路。Android中也是繪圖路徑的意思,一般和canvas一起用。介紹幾個常用的方法看意思也知道意思的方法,p.lineTo(x, y)就是畫線至(x,y)坐標位置。p.moveTo(x, y);將路徑移動到(x,y)位置。p.close();關閉路徑,即把終點和起點連起來。

說了一大堆廢話,現在開始了,首先我們需要定義一些變量,用於繪制圖形

//折線圖點集合

private List milliliter;

//基准寬度

private float tb;

// 左右間距

private float interval_left_right;

// 左間距

private float interval_left;

// 畫筆

private Paint paint_date, paint_brokenLine, paint_dottedline,

paint_brokenline_big, framPanint;

//文本畫筆

private Paint textPaint;

// 點圖

private Bitmap bitmap_point;

//路徑

private Path path;

//折線點上數字

private float dotted_text;

private int fineLineColor = 0x5faaaaaa; // 灰色,背景柱狀圖

private int blueLineColor = 0xff00ffff; // 藍色,線色

然後初始化要用到的變量參數。

if (null == milliliter || milliliter.size() == 0)

return;

this.milliliter = delZero(milliliter);

Resources res = getResources();

// 定義基准寬度

tb = res.getDimension(R.dimen.historyscore_tb);

interval_left_right = tb * 2.0f;

interval_left = tb * 0.5f;

paint_date = new Paint();

paint_date.setStrokeWidth(tb * 0.1f);

paint_date.setTextSize(tb * 0.7f);

paint_date.setColor(fineLineColor);

paint_brokenLine = new Paint();

paint_brokenLine.setStrokeWidth(tb * 0.1f);

paint_brokenLine.setColor(blueLineColor);

paint_brokenLine.setAntiAlias(true);

paint_dottedline = new Paint();

//設置畫筆風格

paint_dottedline.setStyle(Paint.Style.STROKE);

paint_dottedline.setColor(fineLineColor);

paint_brokenline_big = new Paint();

paint_brokenline_big.setStrokeWidth(tb * 0.2f);

paint_brokenline_big.setColor(fineLineColor);

paint_brokenline_big.setAntiAlias(true);

framPanint = new Paint();

framPanint.setAntiAlias(true);

//設置空心寬度

framPanint.setStrokeWidth(2f);

textPaint = new Paint();

textPaint.setAntiAlias(true);

textPaint.setStrokeWidth(1f);

textPaint.setTextSize(tb * 0.4f);

textPaint.setColor(Color.BLUE);

Paint P = new Paint();

path = new Path();

bitmap_point = BitmapFactory.decodeResource(getResources(),

R.drawable.icon_point_blue);

// 設置寬高尺寸

setLayoutParams(new LayoutParams((int) (31 * interval_left_right),

LayoutParams.MATCH_PARENT));

數據初始化完成後,我們就可以開始,進行繪制操作了。這裡要注意一下上面再定義基准寬度的時候用了一個方法res.getDimension(R.dimen.historyscore_tb);這個是 從xml資源文件中的到尺寸大小的。好處就在於屏幕適配,和當需要更改的時候,只需要在dimens.xml文件中找到對應的dimen標簽,更改數據就可以了,當然,在不同的手機分辨率不一樣,屏幕大小不一樣,這個時候通過xml文件適配,就很合適了。關於屏幕適配,在接下來的blog中有介紹。數據源,這個,我沒有在自定義控件中寫死。主要是為了方便擴展,用了set方法。

public void setMilliliter(List milliliter)

{

this.milliliter = milliliter;

}

當然,也可以在自定義控件的構造方法內賦值。

public ChatView(Context context, List milliliter)

{

super(context);

//給集合賦值(提供數據源)

init(milliliter);

}

接下來,我們要做的就是,把圖表的背景及Y軸繪制出啦。 這裡有個虛線的繪制,是通過DashPathEffect類,DashPathEffect是PathEffect類的一個子類,可以使paint畫出類似虛線的形式,並且可以任意指定虛實的排列方式(如間距,樣式)通過構造函數傳入兩個參數,傳入float[]為虛線的ON和OFF數組(實線,空白),該數組的length必須大於等於2,第二個參數為繪制時的偏移量。使用paint的setPathEffect方法設置畫筆參數。再drawPath出想要的虛線。

public void drawStraightLine(Canvas c)

{

paint_dottedline.setColor(fineLineColor);

Path path = new Path();

for (int i = 0; i < 32; i++)

{

path.moveTo(interval_left_right * i + tb, tb * 1.5f);

//getHeight()用於得到控件的高度

path.lineTo(interval_left_right * i + tb, getHeight());

// 虛線

PathEffect effects = new DashPathEffect(new float[] { tb * 0.3f,

tb * 0.3f, tb * 0.3f, tb * 0.3f }, tb * 0.1f);

paint_dottedline.setPathEffect(effects);

c.drawPath(path, paint_dottedline);

}

//底部粗實線(可有可無)

c.drawLine(0, getHeight() - tb * 0.2f, getWidth(), getHeight() - tb

* 0.2f, paint_brokenline_big);

}

繪制頂部時間/天數。用for循環增加X軸的距離,起到間隔作用。

public void drawDate(Canvas c)

{

for (int i = 0; i < 31; i++)

{

paint_date.setStrokeWidth(tb * 0.4f);

c.drawText(i + 1 + "天", interval_left_right * i + tb / 2,

tb * 1.0f, paint_date);

}

}

接下來。就開始,我們的折線圖繪制了,有點繞,請看注釋。

public void drawBrokenLine(Canvas c)

{

//getHeight()用於得到控件的高度

float base = (getHeight() - tb * 3.0f) / Collections.max(milliliter)

* 0.7f;

// 渲染,給折線區域渲染顏色的,

Shader mShader = new LinearGradient(0, 0, 0, getHeight(), new int[] {

Color.argb(150, 0, 200, 250), Color.argb(70, 0, 200, 250),

Color.argb(22, 0, 200, 250) }, null, Shader.TileMode.CLAMP);

framPanint.setShader(mShader);

//畫點,及數值

for (int i = 0; i < milliliter.size() - 1; i++)

{

float x1 = interval_left_right * i + tb;

float y1 = getHeight() - tb * 1.5f - (base * milliliter.get(i));

//下一點的坐標

float x2 = interval_left_right * (i + 1) + tb;

float y2 = getHeight() - tb * 1.5f - (base * milliliter.get(i + 1));

if (i == 0)

{

path.moveTo(tb,

getHeight() - tb * 1.5f - (base * milliliter.get(0)));

}

c.drawLine(x1, y1, x2, y2, paint_brokenLine);

path.lineTo(x1, y1);

// 打點,最後參數paint可不用

c.drawBitmap(bitmap_point, x1 - bitmap_point.getWidth() / 2, y1

- bitmap_point.getHeight() / 2, null);

Log.w("drawBitmap", "" + milliliter.size());

// 畫數值(文本)

c.drawText("" + milliliter.get(i),

x1 - bitmap_point.getWidth() / 2,

y1 - bitmap_point.getHeight() / 2, textPaint);

//此處注意,-2.因為當在最後一個點的時候,是沒有X2,Y2的。所以,有個判斷

if (i == milliliter.size() - 2)

{

path.lineTo(x2, y2);

path.lineTo(x2, getHeight());

path.lineTo(tb, getHeight());

path.close();//一定要閉合

//設置渲染效果

c.drawPath(path, framPanint);

//畫最後一個點

c.drawBitmap(bitmap_point, x2 - bitmap_point.getWidth() / 2, y2

- bitmap_point.getHeight() / 2, null);

//畫最後點的數值

c.drawText("" + milliliter.get(i + 1),

x2 - bitmap_point.getWidth() / 2,

y2 - bitmap_point.getHeight() / 2, textPaint);

}

}

}

上面代碼中用到了LinearGradient類叫做線性渲染。作用是實現某一區域(閉合)內顏色的線性漸變效果,構造方法為LinearGradient (float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile);

其中,參數x0表示漸變的起始點x坐標;參數y0表示漸變的起始點y坐標;參數x1表示漸變的終點x坐標;參數y1表示漸變的終點y坐標;參數colors表示漸變的顏色數組;參數positions用來指定顏色數組的相對位置;參數tile表示平鋪方式。通常,參數positions設為null,表示顏色數組以斜坡線的形式均勻分布。

Y軸 單元base值的計算,是通過控件的總高getHeight()得到減去上面文字的高度和間隔。除以數據源集合中的最大值用Collections.max()方法得到,再乘以0.7的系數,是希望不要讓最大值在頂上,影響美觀。還需要注意的是,path路徑是跟隨折線圖一起運動,當到達最後一個點時候,讓path路徑直接path.lineTo(x2, getHeight());到達最下邊底部。再path.lineTo(tb, getHeight());到達第一根豎虛線底部,調用close()方法閉合路徑。這樣就可以使用渲染效果了。

最後,在onDraw()方法中將上面三個方法。添加到裡面。這樣就完成了,onDraw()的重寫,當然裡面有個判斷數據源是否為空,避免報空指針異常。

@Override

protected void onDraw(Canvas c)

{

//判斷

if (null == milliliter || milliliter.size() == 0)

{

Log.w("onDraw()", "為空");

return;

}

drawStraightLine(c);

drawBrokenLine(c);

drawDate(c);

}

是不是很簡單啊。哈哈!第一篇博客。。有不足之處請多指教!看著窗外的風景,聽聽音樂,敲敲代碼其實也挺好的。是吧!

下面添加自定義類java文件,裡面有個方法用於去除集合中首尾為0的點,可以看情況自己使用。謝謝!

public class ChatView extends View

{

private List milliliter;

private float tb;

// 左右間距

private float interval_left_right;

// 左間距

private float interval_left;

// 畫筆

private Paint paint_date, paint_brokenLine, paint_dottedline,

paint_brokenline_big, frampaint;

private Paint textPaint;

private int time_index;

// 點圖

private Bitmap bitmap_point;

private Path path;

private float dotted_text;

public float getDotted_text()

{

return dotted_text;

}

public void setDotted_text(float dotted_text)

{

this.dotted_text = dotted_text;

}

public void setMilliliter(List milliliter)

{

this.milliliter = milliliter;

}

private int fineLineColor = 0x5faaaaaa; // 灰色,背景柱狀圖

private int blueLineColor = 0xff00ffff; // 藍色,線色

private int orangeLineColor = 0xffd56f2b; // 橙色,基准線色

public ChatView(Context context, List milliliter)

{

super(context);

init(milliliter);

}

public void init(List milliliter)

{

if (null == milliliter || milliliter.size() == 0)

return;

this.milliliter = delZero(milliliter);

Resources res = getResources();

// 定義基准寬度

tb = res.getDimension(R.dimen.historyscore_tb);

interval_left_right = tb * 2.0f;

interval_left = tb * 0.5f;

paint_date = new Paint();

paint_date.setStrokeWidth(tb * 0.1f);

paint_date.setTextSize(tb * 0.7f);

paint_date.setColor(fineLineColor);

paint_brokenLine = new Paint();

paint_brokenLine.setStrokeWidth(tb * 0.1f);

paint_brokenLine.setColor(blueLineColor);

paint_brokenLine.setAntiAlias(true);

paint_dottedline = new Paint();

paint_dottedline.setStyle(Paint.Style.STROKE);

paint_dottedline.setColor(fineLineColor);

paint_brokenline_big = new Paint();

paint_brokenline_big.setStrokeWidth(tb * 0.2f);

paint_brokenline_big.setColor(fineLineColor);

paint_brokenline_big.setAntiAlias(true);

frampaint = new Paint();

frampaint.setAntiAlias(true);

frampaint.setStrokeWidth(2f);

textPaint = new Paint();

textPaint.setAntiAlias(true);

textPaint.setStrokeWidth(1f);

textPaint.setTextSize(tb * 0.4f);

textPaint.setColor(Color.BLUE);

Paint P = new Paint();

path = new Path();

bitmap_point = BitmapFactory.decodeResource(getResources(),

R.drawable.icon_point_blue);

// 設置寬高尺寸

setLayoutParams(new LayoutParams((int) (31 * interval_left_right),

LayoutParams.MATCH_PARENT));

}

/**

* 移除左右為零的數據

*

* @return

*/

public List delZero(List milliliter)

{

List list = new ArrayList();

int sta = 0;

int end = 0;

for (int i = 0; i < milliliter.size(); i++)

{

if (milliliter.get(i) != 0)

{

sta = i;

break;

}

}

for (int i = milliliter.size() - 1; i >= 0; i--)

{

if (milliliter.get(i) != 0)

{

end = i;

break;

}

}

for (int i = 0; i < milliliter.size(); i++)

{

if (i >= sta && i <= end)

{

list.add(milliliter.get(i));

}

}

time_index = sta;

dotted_text = ((Collections.max(milliliter) - Collections

.min(milliliter)) / 12.0f * 5.0f);

return list;

}

@Override

protected void onDraw(Canvas c)

{

if (null == milliliter || milliliter.size() == 0)

{

Log.w("onDraw()", "為空");

return;

}

drawStraightLine(c);

drawBrokenLine(c);

drawDate(c);

}

/**

* 繪制豎線 繪制背景Y軸

*

* @param c

*/

public void drawStraightLine(Canvas c)

{

paint_dottedline.setColor(fineLineColor);

Path path = new Path();

for (int i = 0; i < 32; i++)

{

path.moveTo(interval_left_right * i + tb, tb * 1.5f);

path.lineTo(interval_left_right * i + tb, getHeight());

// 虛線

PathEffect effects = new DashPathEffect(new float[] { tb * 0.3f,

tb * 0.3f, tb * 0.3f, tb * 0.3f }, tb * 0.1f);

paint_dottedline.setPathEffect(effects);

c.drawPath(path, paint_dottedline);

}

c.drawLine(0, getHeight() - tb * 0.2f, getWidth(), getHeight() - tb

* 0.2f, paint_brokenline_big);

}

/**

* 繪制折線

*

* @param c

*/

public void drawBrokenLine(Canvas c)

{

float base = (getHeight() - tb * 3.0f) / Collections.max(milliliter)

* 0.7f;

// 渲染

Shader mShader = new LinearGradient(0, 0, 0, getHeight(), new int[] {

Color.argb(150, 0, 200, 250), Color.argb(70, 0, 200, 250),

Color.argb(22, 0, 200, 250) }, null, Shader.TileMode.CLAMP);

frampaint.setShader(mShader);

for (int i = 0; i < milliliter.size() - 1; i++)

{

float x1 = interval_left_right * i + tb;

float y1 = getHeight() - tb * 1.5f - (base * milliliter.get(i));

float x2 = interval_left_right * (i + 1) + tb;

float y2 = getHeight() - tb * 1.5f - (base * milliliter.get(i + 1));

if (i == 0)

{

path.moveTo(tb,

getHeight() - tb * 1.5f - (base * milliliter.get(0)));

}

c.drawLine(x1, y1, x2, y2, paint_brokenLine);

path.lineTo(x1, y1);

// 打點

c.drawBitmap(bitmap_point, x1 - bitmap_point.getWidth() / 2, y1

- bitmap_point.getHeight() / 2, null);

Log.w("drawBitmap", "" + milliliter.size());

// 畫步數值

c.drawText("" + milliliter.get(i),

x1 - bitmap_point.getWidth() / 2,

y1 - bitmap_point.getHeight() / 2, textPaint);

if (i == milliliter.size() - 2)

{

path.lineTo(x2, y2);

path.lineTo(x2, getHeight());

path.lineTo(tb, getHeight());

path.close();

c.drawPath(path, frampaint);

c.drawBitmap(bitmap_point, x2 - bitmap_point.getWidth() / 2, y2

- bitmap_point.getHeight() / 2, null);

c.drawText("" + milliliter.get(i + 1),

x2 - bitmap_point.getWidth() / 2,

y2 - bitmap_point.getHeight() / 2, textPaint);

}

}

}

/**

* 繪制時間

*

* @param c

*/

public void drawDate(Canvas c)

{

for (int i = 0; i < 31; i++)

{

paint_date.setStrokeWidth(tb * 0.4f);

c.drawText(i + 1 + "天", interval_left_right * i + tb / 2,

tb * 1.0f, paint_date);

}

}

}

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved