自定义View来显示多条支付信息

在做项目开发时,有个这样的需求:

就中间的那个支付明细,要求点击时能收缩,这个功能非常简单,从界面来看,用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


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ytmfdw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值