自定义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

时间: 2024-10-08 19:42:55

自定义View来显示多条支付信息的相关文章

【Android 应用开发】 自定义 View 组件 -- 圆形进度条

转载著名出处 : http://blog.csdn.net/shulianghan/article/details/40351487 代码下载 : -- CSDN 下载地址 : http://download.csdn.net/detail/han1202012/8069497 ; -- GitHub 地址 : https://github.com/han1202012/CircleProcess.git ; -- 工程示例 : 一. 相关知识点解析 1. 自定义 View 组件构造方法 构造方

自定义View不显示的问题

问题描述: 我自定义了一个把 SwipeRefreshLayout 和 RecyclerView 封装在一起的 View ,但是发现 List 不能正常的显示出来,本以为是数据源出现问题,debug了之后发现是 RecycylerView 的 getItemCount() 方法没有被调用,google了一下发现网上出现这种问题的大多数是没写 LayoutMananger ,但我确实有写.一般如果你给 recyclerView 添加了 adapter,Adapter 的 getItemCount(

自定义view之圆形进度条

本节介绍自定义view-圆形进度条 思路: 根据前面介绍的自定义view内容可拓展得之: 1:新建类继承自View 2:添加自定义view属性 3:重写onDraw(Canvas canvas) 4:实现功能 下面上代码 1.自定义view代码: public class CustomView extends View { //背景圆环颜色 private int circleColor; //进度条颜色&字体颜色(为了美观,所以设计字体颜色和进度条颜色值一致) private int seco

自定义View调用onDraw方法

============问题描述============ 我现在有一个需求,要自定义View,初始化的时候调用了onDraw方法,完后我自定义一个方法,调用该方法的时候,要重新调用onDraw方法,但我用postInvalidate无效,代码如下: 启动类: public class MainActivity extends Activity {         TestOnDraw view;                  @Override         protected void 

android 自定义View onMeasure中 super.onMeasure 和 setMeasuredDimension

练习写一个自定义的view,代码是抄网上的,第一次写,没有问题,与网上的示例一样的效果, 第二次.第三次,都出现问题,但是解决了. 昨天进行第四次写再写,又出问题不一样的问题了. 首先是想加一个子包,结果在app上目录直接创建子包全名,结果子包级别与父包同级目录了.但是在父包上直接加子包名,子包目录可以在父包目录下了. 其次遇到了,自定义view老是显示不出来,经过仔细对比,发现是在重写onMeasure中 后是调用 super.onMeasure(width,height); super.on

4.自定义view总结

自定义view 对象显示的屏幕上,有几个重要步骤: 1.构造方法 创建 对象.(就是那三个构造方法) 第二个是创建布局文件调用的构造函数 2.测量view的大小. 设置自己显示在屏幕上的宽高, onMeasure(int,int) setMeasuredDimension(backgroundBitmap.getWidth(),backgroundBitmap.getHeight());如果知道了自定义控件的大小,那么就这样设置当前view的大小,用这个需要super去掉,继承view的话这样写

Android自定义View——圆形进度条式按钮

介绍 今天上班的时候有个哥们问我怎么去实现一个按钮式的进度条,先来看看他需要实现的效果图. 和普通的圆形进度条类似,只是中间的地方有两个状态表示,未开始,暂停状态.而且他说圆形进度的功能已经实现了.那么我们只需要对中间的两个状态做处理就行了. 先来看看实现的效果图: 上面说了我们只需要处理中间状态的变化就可以了,对于进度的处理直接使用了弘洋文章中实现: http://blog.csdn.net/lmj623565791/article/details/43371299 下面开始具体实现. 具体实

Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)

转载请注明地址:http://blog.csdn.net/xiaanming/article/details/10298163 很多的时候,系统自带的View满足不了我们功能的需求,那么我们就需要自己来自定义一个能满足我们需求的View,自定义View我们需要先继承View,添加类的构造方法,重写父类View的一些方法,例如onDraw,为了我们自定义的View在一个项目中能够重用,有时候我们需要自定义其属性,举个很简单的例子,我在项目中的多个界面使用我自定义的View,每个界面该自定义View

我的Android进阶之旅------&gt;Android自定义View实现带数字的进度条(NumberProgressBar)

今天在Github上面看到一个来自于 daimajia所写的关于Android自定义View实现带数字的进度条(NumberProgressBar)的精彩案例,在这里分享给大家一起来学习学习!同时感谢daimajia的开源奉献! 第一步.效果展示 图1.蓝色的进度条 图2.红色的进度条 图3.多条颜色不同的进度条 图4.多条颜色不同的进度条 版权声明:本文为[欧阳鹏]原创文章,欢迎转载,转载请注明出处! [http://blog.csdn.net/ouyang_peng/article/deta