Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義View來顯示多條支付信息

自定義View來顯示多條支付信息

編輯:關於Android編程

在做項目開發時,有個這樣的需求:

\

就中間的那個支付明細,要求點擊時能收縮,這個功能非常簡單,從界面來看,用LinearLayout或TableLayout來做,沒啥難度,但是如果是用布局來寫的話,那麼要寫的可多了,這只是列出了幾種支付方式,有可能還有更多的,也有可能沒這麼多,那麼用這種方式來寫,代碼非常啰嗦,維護起來更麻煩,針對這種情況,我采用的是自定義控件來寫,動態畫出來這些文本,詳細代碼:

 

package com.example.viewtest;

import java.util.LinkedHashMap;
import java.util.Map;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;

/**
 * 支付詳情自定義控件
 * 

* 傳入參數:Map *

* key:支付方式,value:支付金額 * * @author xiec * */ public class PayDetailView extends View implements OnClickListener { Map data; TextView tv_title; String title = "線上支付明細"; /** * 是否顯示詳情,標題點擊時會切換 */ boolean isShow = true; /** * 自己的寬度 */ int width; /** * 自己的高度 */ int hight; /** * 字體大小  */ float textSize; Resources res; /** * 畫筆 */ Paint p; FontMetrics fm; /** * 畫筆粗細 */ float penSize = 3f; public PayDetailView(Context context) { super(context); init(); } public PayDetailView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { res = getResources(); setBackgroundColor(res.getColor(R.color.white)); textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, res.getDimension(R.dimen.textsize_middle), res.getDisplayMetrics()); p = new Paint(); p.setAntiAlias(true);// 設置抗鋸齒F p.setStrokeWidth(penSize); p.setTextAlign(Paint.Align.LEFT); p.setTextSize(textSize); fm = p.getFontMetrics(); data = new LinkedHashMap(); LogUtils.d("init width=" + width); this.setOnClickListener(this); } /** * 設置數據 * * @param map */ public void setData(Map map) { // 設置數據 data.clear(); if (map != null) { // String[] tmp = new String[map.size()]; // map.keySet().toArray(tmp); // LogUtils.d(Arrays.toString(tmp)); data.putAll(map); } // 重繪 invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ViewGroup parent = (ViewGroup) getParent(); width = parent.getMeasuredWidth(); // 計算控件高度,根據數據來算 hight = (int) (textSize * 2); if (data != null && data.size() > 0 && isShow) { hight += (data.keySet().size() + 1) * textSize * 2; } setMeasuredDimension(width, hight); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub LogUtils.d("onDraw isShow:" + isShow); drawTitle(canvas, isShow, p); if (isShow) { // 畫值 drawCotent(canvas, data, p); } } /** * 畫支付詳情 * * @param data * 數據 * @param p * 畫筆 */ private void drawCotent(Canvas canvas, Map data, Paint p) { if (data != null && data.size() > 0) { int size = data.size(); float[] pts = new float[8 + size * 4]; // 畫框 int ptsLen = pts.length; LogUtils.d("data size=" + size); LogUtils.d("pts size=" + ptsLen); // 豎線 pts[0] = (width - penSize) / 2; pts[1] = textSize * 2; pts[2] = (width - penSize) / 2; pts[3] = hight; pts[4] = 0; pts[5] = textSize * 4; pts[6] = width; pts[7] = textSize * 4; // 是否是左邊 for (int i = 8; i < ptsLen; i += 4) { pts[i] = 0; pts[i + 1] = textSize * (i / 4 + 1) * 2; pts[i + 2] = width; pts[i + 3] = textSize * (i / 4 + 1) * 2; } p.setColor(res.getColor(R.color.black_90)); canvas.drawLines(pts, p); // 頭部 // 支付方式 float tmp = getTextlen(p, "支付方式"); p.setColor(res.getColor(R.color.main_color)); // 設置粗體 Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD); p.setTypeface(font); float d_x = (width / 2 - tmp) / 2; float d_y = textSize * 2 + (textSize - fm.descent + (fm.bottom - fm.top) / 2); canvas.drawText("支付方式", d_x, d_y, p); tmp = getTextlen(p, "金額"); d_x = (int) (width / 2 + ((width / 2 - tmp) / 2)); canvas.drawText("金額", d_x, d_y, p); size = data.keySet().size(); String[] payway = new String[size]; data.keySet().toArray(payway); // size = payway.length; font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); p.setTypeface(font); for (int i = 0; i < size; i++) { // 每種支付方式都畫出來 p.setColor(res.getColor(R.color.black)); tmp = getTextlen(p, payway[i]); d_x = (width / 2 - tmp) / 2; d_y = (textSize * (2 + i) * 2) + (textSize - fm.descent + (fm.bottom - fm.top) / 2); canvas.drawText(payway[i], d_x, d_y, p); p.setColor(res.getColor(R.color.tab_spinner_color)); String value = data.get(payway[i]); tmp = getTextlen(p, value); d_x = (int) (width / 2 + ((width / 2 - tmp) / 2)); canvas.drawText(value, d_x, d_y, p); } } } // 畫標題 private void drawTitle(Canvas canvas, boolean isShow, Paint p) { LogUtils.d("drawTitle isShow:" + isShow); Resources res = getResources(); p.setColor(res.getColor(R.color.msdcolorbg));// 設置紅色 p.setStyle(Paint.Style.FILL);// 設置填滿 // 畫矩形 canvas.drawRect(0, 0, width, textSize * 2, p);// 長方形 p.setColor(res.getColor(R.color.white)); // 畫右邊箭頭 drawArrow(canvas, isShow, p); // 計算文字長度 float textlen = getTextlen(p, title); float d_x = (width - textlen) / 2; float d_y = textSize - fm.descent + (fm.bottom - fm.top) / 2; canvas.drawText(title, d_x, d_y, p); } /** * 畫右上角箭頭 * * @param canvas * @param isShow * @param p */ private void drawArrow(Canvas canvas, boolean isShow, Paint p) { float size = textSize; float d_x = width - size - size / 2; float d_y = textSize / 2; // 大小固定在80px // {x0,y0,x1,y1,x2,y2},總共四個點,八個坐標 float[] pts = new float[8]; if (isShow) { // 向下箭頭 pts[0] = d_x; pts[1] = d_y; pts[2] = d_x + size / 2; pts[3] = d_y + size / 2; pts[4] = d_x + size / 2; pts[5] = d_y + size / 2; pts[6] = d_x + size; pts[7] = d_y; } else { // 向上箭頭 pts[0] = d_x; pts[1] = d_y + size / 2; pts[2] = d_x + size / 2; pts[3] = d_y; pts[4] = d_x + size / 2; pts[5] = d_y; pts[6] = d_x + size; pts[7] = d_y + size / 2; } canvas.drawLines(pts, p); } /** * 計算文本長度 * * @param p * @param text * @return */ private float getTextlen(Paint p, String text) { if (text == null) { return 0; } return p.measureText(text); } @Override public void onClick(View v) { // 單擊時隱藏支付詳情 LogUtils.d("onClick isShow=" + isShow); isShow = !isShow; // invalidate(); requestLayout(); } }


下面對一些地方作補充:

 

首先,支付方式是不固定的,在解析完成後,以Map的形式傳給這個View:

 

	/**
	 * 設置數據
	 * 
	 * @param map
	 */
	public void setData(Map map) {
		// 設置數據
		data.clear();
		if (map != null) {
			// String[] tmp = new String[map.size()];
			// map.keySet().toArray(tmp);
			// LogUtils.d(Arrays.toString(tmp));
			data.putAll(map);
		}
		// 重繪
		invalidate();
	}

當收到Map數據後,先清下原數據,再加載數據,再重新繪制界面,會調用onDraw方法,這裡Map采用的是LinkedHashMap
,使用LinkedHashMap目的是使傳入的Map按順序來畫出來。

 

重點是在onDraw方法,

 

@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		LogUtils.d("onDraw isShow:" + isShow);
		drawTitle(canvas, isShow, p);
		if (isShow) {
			// 畫值
			drawCotent(canvas, data, p);
		}
	}
這個方法裡很簡單,首先是畫標題,再看下是否要畫內容區域(有個收縮功能),如果是縮的狀態,就不用往下走了。isShow這個變量在點擊的時候進行切換

 

 

@Override
	public void onClick(View v) {
		// 單擊時隱藏支付詳情
		LogUtils.d("onClick isShow=" + isShow);
		isShow = !isShow;
		// invalidate();
		requestLayout();
	}

點擊時,要求重新布局:requestLayout();

 

重新布局的時候會重新計算控件的大小(onMeasure()),並重新繪制界面,關於控件的大小計算,我這裡的寬,用的是父布局的寬:

  ViewGroup parent = (ViewGroup) getParent();
width = parent.getMeasuredWidth();

要注意一下,getParent()要強轉成ViewGroup。關鍵是高度的計算:

  // 計算控件高度,根據數據來算
hight = (int) (textSize * 2);
if (data != null && data.size() > 0 && isShow) {
hight += (data.keySet().size() + 1) * textSize * 2;
}
setMeasuredDimension(width, hight);

我這裡高度計算是根據傳入的Map的大小來算的,首先頭部高度是固定的,我用的是2倍的字體大小,字體大小是從dime中獲取的

textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
res.getDimension(R.dimen.textsize_middle),
res.getDisplayMetrics());

關鍵是setMeasuredDimension方法,使用重新布局時控件大小重新布局。

再回到onDraw方法來,drawTitle方法是畫頭部,即   線上支付明細

記住一條:在onDraw方法中,不要用new來新建對象,不然會有警告:onDraw會常調用,在這裡用new來create對象,內存會使用比較多。

drawTitle代碼就不貼了,上面有,只是簡單說一下,有幾個問題需要注意:

一、文字居中,

canvas.drawText有四個參數,特別是第二個參數和第三個參數,是決定這個文本從哪個地方開始寫的,

// 計算文字長度
float textlen = getTextlen(p, title);
float d_x = (width - textlen) / 2;
float d_y = textSize - fm.descent + (fm.bottom - fm.top) / 2;
canvas.drawText(title, d_x, d_y, p);

居中的方法:(總長度-文本長度)/2

文本長度的獲取:

ps:這裡的Paint參數,最好是全局的,不然會出現計算出來的文本長度與預期的不一樣,原因就是Paint使用的不是同一個對象

 

/**
	 * 計算文本長度
	 * 
	 * @param p
	 * @param text
	 * @return
	 */
	private float getTextlen(Paint p, String text) {
		if (text == null) {
			return 0;
		}
		return p.measureText(text);
	}
還有一個d_y的計算(可以理解成文本的y軸),Android比較坑的是:這個文本的y軸,有點難理解,drawText畫文本是基於baseLine來畫的,什麼是baseLine?見下圖:

 

\

因此,這個d_y不能像d_x那樣計算,得用FontMetrics來計算,計算方法:

fm = p.getFontMetrics();//獲取FontMetrice對象,根據Paint對象來獲取

float d_y = textSize - fm.descent + (fm.bottom - fm.top) / 2;

//控件的高度-fm.descent+(fm.bottom - fm.top) / 2;

 

寫完頭部文本後,再畫上一個箭頭(其實就是兩條線段組成,利用drawLine方法,參數是一個float[]數組,這個一維數據{x0,y0,x1,y1,x2,y2,x3,y3....},這有兩條線段,所以得要8個坐標):

 

/**
	 * 畫右上角箭頭
	 * 
	 * @param canvas
	 * @param isShow
	 * @param p
	 */
	private void drawArrow(Canvas canvas, boolean isShow, Paint p) {
		float size = textSize;
		float d_x = width - size - size / 2;
		float d_y = textSize / 2;
		// 大小固定在80px
		// {x0,y0,x1,y1,x2,y2},總共四個點,八個坐標
		float[] pts = new float[8];
		if (isShow) {
			// 向下箭頭
			pts[0] = d_x;
			pts[1] = d_y;
			pts[2] = d_x + size / 2;
			pts[3] = d_y + size / 2;
			pts[4] = d_x + size / 2;
			pts[5] = d_y + size / 2;
			pts[6] = d_x + size;
			pts[7] = d_y;
		} else {
			// 向上箭頭
			pts[0] = d_x;
			pts[1] = d_y + size / 2;
			pts[2] = d_x + size / 2;
			pts[3] = d_y;
			pts[4] = d_x + size / 2;
			pts[5] = d_y;
			pts[6] = d_x + size;
			pts[7] = d_y + size / 2;
		}
		canvas.drawLines(pts, p);

	}

畫完頭部,再根據isShow來畫Content:

 

內容區域分兩部分來畫:

黑線組成的框和文字部分,

先畫框:框是由線段組成,根據data.size來決定有多少條橫線,豎線就一條,用float[]數組來存組成線段的點的坐標。

 

int size = data.size();
			float[] pts = new float[8 + size * 4];
			// 畫框
			int ptsLen = pts.length;
			LogUtils.d("data size=" + size);
			LogUtils.d("pts size=" + ptsLen);
			// 豎線
			pts[0] = (width - penSize) / 2;
			pts[1] = textSize * 2;
			pts[2] = (width - penSize) / 2;
			pts[3] = hight;
			pts[4] = 0;
			pts[5] = textSize * 4;
			pts[6] = width;
			pts[7] = textSize * 4;
			// 是否是左邊
			for (int i = 8; i < ptsLen; i += 4) {
				pts[i] = 0;
				pts[i + 1] = textSize * (i / 4 + 1) * 2;
				pts[i + 2] = width;
				pts[i + 3] = textSize * (i / 4 + 1) * 2;
			}
			p.setColor(res.getColor(R.color.black_90));
			canvas.drawLines(pts, p);


畫完線後,再寫文本,這裡需要注意的就是文本的坐標,d_x,d_y的計算,d_x好說,關鍵是d_y的值,詳情見代碼注釋,

 

 

// 頭部
			// 支付方式
			float tmp = getTextlen(p, "支付方式");
			p.setColor(res.getColor(R.color.main_color));
			// 設置粗體
			Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
			p.setTypeface(font);
			float d_x = (width / 2 - tmp) / 2;
			float d_y = textSize * 2
					+ (textSize - fm.descent + (fm.bottom - fm.top) / 2);
			canvas.drawText("支付方式", d_x, d_y, p);
			tmp = getTextlen(p, "金額");
			d_x = (int) (width / 2 + ((width / 2 - tmp) / 2));
			canvas.drawText("金額", d_x, d_y, p);
			size = data.keySet().size();
			String[] payway = new String[size];
			data.keySet().toArray(payway);
			// size = payway.length;
			font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
			p.setTypeface(font);
			for (int i = 0; i < size; i++) {
				// 每種支付方式都畫出來
				p.setColor(res.getColor(R.color.black));
				tmp = getTextlen(p, payway[i]);
				d_x = (width / 2 - tmp) / 2;
				d_y = (textSize * (2 + i) * 2)
						+ (textSize - fm.descent + (fm.bottom - fm.top) / 2);
				canvas.drawText(payway[i], d_x, d_y, p);
				p.setColor(res.getColor(R.color.tab_spinner_color));
				String value = data.get(payway[i]);
				tmp = getTextlen(p, value);
				d_x = (int) (width / 2 + ((width / 2 - tmp) / 2));
				canvas.drawText(value, d_x, d_y, p);

			}

 

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