在做项目开发时,有个这样的需求:
就中间的那个支付明细,要求点击时能收缩,这个功能非常简单,从界面来看,用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;
/**
* 支付详情自定义控件
* <p>
* 传入参数:Map<String,String>
* <p>
* key:支付方式,value:支付金额
*
* @author xiec
*
*/
public class PayDetailView extends View implements OnClickListener {
Map<String, String> 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<String, String>();
LogUtils.d("init width=" + width);
this.setOnClickListener(this);
}
/**
* 设置数据
*
* @param map
*/
public void setData(Map<String, String> 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<String, String> 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<String,String>的形式传给这个View:
/**
* 设置数据
*
* @param map
*/
public void setData(Map<String, String> 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);
}
基本情况就这样
源码下载:http://download.csdn.net/detail/ytmfdw/9518286