Android自定义漏斗图View
程序员文章站
2022-05-26 21:41:52
...
import android.app.Activity;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.support.v4.content.LocalBroadcastManager;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.axeac.android.sdk.R;
import com.axeac.android.sdk.tools.StringUtil;
import com.axeac.android.sdk.utils.CommonUtil;
import com.axeac.android.sdk.utils.StaticObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class FunnelChart extends View {
private static final int DEFAULT_PADDING_LENGTH = 25;
private static final float DEFAULT_EMPTYPX = 5;
private Map<String, RectF> rectMap = new HashMap<String, RectF>();
private Map<String, String[]> dataMap = new HashMap<String, String[]>();
private Activity ctx;
private RectF rect;
private String click = "";
private String title;
private String titleFont;
private String subTitle;
private String subTitleFont;
private String dataTitleFont;
private String dataFont;
private ArrayList<Integer> colors;
private List<String[]> datas;
public FunnelChart(Activity ctx){
super(ctx);
this.ctx = ctx;
int height = 0;
Rect frame = new Rect();
ctx.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
height = frame.top;
height += ctx.findViewById(R.id.toolbar).getHeight();
height += ctx.findViewById(R.id.layout_bottom).getHeight();
this.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
(int) (StaticObject.deviceWidth*0.618)));
this.setBackgroundColor(getResources().getColor(R.color.background));
this.getBackground().setAlpha(180);
}
public void setTitle(String title){
this.title = title;
}
public void setTitleFont(String titleFont){
this.titleFont = titleFont;
}
public void setSubTitle(String subTitle){
this.subTitle = subTitle;
}
public void setSubTitleFont(String subTitleFont){
this.subTitleFont = subTitleFont;
}
public void setClick(String click){
this.click = click;
}
public void setDataTitleFont(String dataTitleFont) {
this.dataTitleFont = dataTitleFont;
}
public void setDataFont(String dataFont) {
this.dataFont = dataFont;
}
public void setDatas(List<String[]> datas) {
this.datas = datas;
}
public void setColor(ArrayList<Integer> colors) {
this.colors = colors;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawChart(canvas);
}
/**
* 绘制漏斗图
* @param canvas
* Canvas对象
* */
private void drawChart(Canvas canvas) {
Rect leftRect = drawTitle(canvas);
Rect rightRect = drawDataTitle(canvas, leftRect);
int titleHeight = leftRect.bottom > rightRect.bottom ? leftRect.bottom : rightRect.bottom;
rect = new RectF(0, titleHeight, this.getWidth(), this.getHeight());
initChartDatas();
drawDiagram(canvas);
}
/**
* 绘制主副标题
* @param canvas
* Canvas对象
* */
private Rect drawTitle(Canvas canvas) {
Paint paint = new Paint();
float titleTextSize = 30;
if (titleFont != null && !"".equals(titleFont)) {
if (titleFont.indexOf(";") != -1) {
String[] strs = titleFont.split(";");
for (String str : strs) {
if (str.startsWith("font-size")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
paint.setTextSize(Float.parseFloat(s.replace("px", "").trim()));
titleTextSize = Float.parseFloat(s.replace("px", "").trim());
} else if(str.startsWith("style")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if ("bold".equals(s)){
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else if("italic".equals(s)) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
} else {
if (s.indexOf(",") != -1) {
if ("bold".equals(s.split(",")[0]) && "italic".equals(s.split(",")[1])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
if ("bold".equals(s.split(",")[1]) && "italic".equals(s.split(",")[0])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
}
}
} else if(str.startsWith("color")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if (CommonUtil.validRGBColor(s)) {
int r = Integer.parseInt(s.substring(0, 3));
int g = Integer.parseInt(s.substring(3, 6));
int b = Integer.parseInt(s.substring(6, 9));
paint.setColor(Color.rgb(r, g, b));
} else {
paint.setColor(Color.WHITE);
}
}
}
}
}
paint.setStyle(Style.STROKE);
paint.setAntiAlias(true);
canvas.drawText(title, DEFAULT_PADDING_LENGTH, paint.getFontMetrics().bottom - paint.getFontMetrics().top, paint);
int titleWidth = (int) paint.measureText(title) + DEFAULT_PADDING_LENGTH * 2;
int titleHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top + titleTextSize * 0.75);
paint = new Paint();
float subTitleTextSize = 23;
if (subTitleFont != null && !"".equals(subTitleFont)) {
if (subTitleFont.indexOf(";") != -1) {
String[] strs = subTitleFont.split(";");
for (String str : strs) {
if (str.startsWith("font-size")) {
int index = str.indexOf(":");
if (index == -1) continue;
String s = str.substring(index + 1).trim();
paint.setTextSize(Float.parseFloat(s.replace("px", "").trim()));
subTitleTextSize = Float.parseFloat(s.replace("px", "").trim());
} else if(str.startsWith("style")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if ("bold".equals(s)){
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else if("italic".equals(s)) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
} else {
if (s.indexOf(",") != -1) {
if ("bold".equals(s.split(",")[0]) && "italic".equals(s.split(",")[1])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
if ("bold".equals(s.split(",")[1]) && "italic".equals(s.split(",")[0])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
}
}
} else if(str.startsWith("color")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if (CommonUtil.validRGBColor(s)) {
int r = Integer.parseInt(s.substring(0, 3));
int g = Integer.parseInt(s.substring(3, 6));
int b = Integer.parseInt(s.substring(6, 9));
paint.setColor(Color.rgb(r, g, b));
} else {
paint.setColor(Color.WHITE);
}
}
}
}
}
paint.setStyle(Style.STROKE);
paint.setAntiAlias(true);
canvas.drawText(subTitle, DEFAULT_PADDING_LENGTH, paint.getFontMetrics().bottom - paint.getFontMetrics().top + titleHeight, paint);
int subTitleWidth = (int) paint.measureText(subTitle) + DEFAULT_PADDING_LENGTH * 2;
int subTitleHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top + subTitleTextSize * 0.75);
int width = titleWidth > subTitleWidth ? titleWidth : subTitleWidth;
int height = titleHeight + subTitleHeight;
return new Rect(0, 0, width, height);
}
//绘制数据标题
private Rect drawDataTitle(Canvas canvas, Rect rectF) {
Paint paint = new Paint();
float dataTitleTextSize = 23;
if (dataTitleFont != null && !"".equals(dataTitleFont)) {
if (dataTitleFont.indexOf(";") != -1) {
String[] strs = dataTitleFont.split(";");
for (String str : strs) {
if (str.startsWith("font-size")) {
int index = str.indexOf(":");
if (index == -1) continue;
String s = str.substring(index + 1).trim();
paint.setTextSize(Float.parseFloat(s.replace("px", "").trim()));
dataTitleTextSize = Float.parseFloat(s.replace("px", "").trim());
} else if(str.startsWith("style")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if ("bold".equals(s)){
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else if("italic".equals(s)) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
} else {
if (s.indexOf(",") != -1) {
if ("bold".equals(s.split(",")[0]) && "italic".equals(s.split(",")[1])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
if ("bold".equals(s.split(",")[1]) && "italic".equals(s.split(",")[0])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
}
}
} else if(str.startsWith("color")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if (CommonUtil.validRGBColor(s)) {
int r = Integer.parseInt(s.substring(0, 3));
int g = Integer.parseInt(s.substring(3, 6));
int b = Integer.parseInt(s.substring(6, 9));
paint.setColor(Color.rgb(r, g, b));
} else {
paint.setColor(Color.WHITE);
}
}
}
}
}
paint.setStyle(Style.STROKE);
paint.setAntiAlias(true);
Rect rect = new Rect(rectF);
rect.set(rect.right + DEFAULT_PADDING_LENGTH, rect.top + 15, this.getWidth() - DEFAULT_PADDING_LENGTH, rect.bottom);
if (datas.size() > 0) {
Integer[] lengths = new Integer[datas.size()];
for (int i = 0; i < datas.size(); i++) {
lengths[i] = (int) paint.measureText(datas.get(i)[1]);
}
lengths = CommonUtil.sortDesc(lengths);
int itemWidth = lengths[0];
int itemHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top);
int lineHeight = (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top + dataTitleTextSize * 0.75);
int colsCount = rect.width() / (itemWidth + DEFAULT_PADDING_LENGTH * 2);
int rowsCount = datas.size()/ colsCount + (datas.size() % colsCount > 0 ? 1 : 0);
rect.set(rect.right - (itemWidth + DEFAULT_PADDING_LENGTH * 2) * colsCount, rect.top, rect.right, rect.top + lineHeight * rowsCount);
for (int i = 0; i < rowsCount; i++) {
if (i < rowsCount - 1) {
for (int j = 0; j < colsCount; j++) {
canvas.drawText(datas.get(i * colsCount + j)[1], rect.left + (itemWidth + DEFAULT_PADDING_LENGTH * 2) * j + DEFAULT_PADDING_LENGTH * 2, rect.top + lineHeight * i + itemHeight, paint);
Paint p = new Paint();
p.setColor(colors.get(i * colsCount + j));
p.setStyle(Style.FILL_AND_STROKE);
p.setAntiAlias(true);
canvas.drawCircle(rect.left + (itemWidth + DEFAULT_PADDING_LENGTH * 2) * j + DEFAULT_PADDING_LENGTH, rect.top + lineHeight * i + lineHeight / 2, 13, p);
}
} else {
int index = datas.size() - (rowsCount - 1) * colsCount;
for (int j = 0; j < index; j++) {
canvas.drawText(datas.get(i * colsCount + j)[1], rect.left + (itemWidth + DEFAULT_PADDING_LENGTH * 2) * (j + colsCount - index) + DEFAULT_PADDING_LENGTH * 2, rect.top + lineHeight * i + itemHeight, paint);
Paint p = new Paint();
p.setColor(colors.get(i * colsCount + j));
p.setStyle(Style.FILL_AND_STROKE);
p.setAntiAlias(true);
canvas.drawCircle(rect.left + (itemWidth + DEFAULT_PADDING_LENGTH * 2) * (j + colsCount - index) + DEFAULT_PADDING_LENGTH, rect.top + lineHeight * i + lineHeight / 2, 13, p);
}
}
}
}
return rect;
}
/**
* 初始化操作
* */
private void initChartDatas() {
float originX = 0;
float originY = 0;
originX = rect.left + DEFAULT_EMPTYPX * 2;
originY = rect.bottom - DEFAULT_EMPTYPX * 2;
rect = new RectF(originX, rect.top + DEFAULT_EMPTYPX * 2, rect.right - DEFAULT_EMPTYPX * 5, originY);
}
/**
* 绘制漏斗图
* @param canvas
* Canvas对象
* */
private void drawDiagram(Canvas canvas) {
int dataNum = datas.size();
int everyHeight = (int) (rect.height()/dataNum);
int perWidth = (int) ((rect.width() - DEFAULT_PADDING_LENGTH * 2)/100);
Point firstPoint = new Point((int)rect.width()/2,(int)rect.bottom);
Path mPath = new Path();
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Style.FILL);
//每份占比
int max = 0;
if (datas.size()>0){
max = Integer.parseInt(datas.get(0)[2])/100;
}
for (int i=0;i<datas.size();i++){
RectF areaRectF;
if (i==datas.size()-1){
int data = Integer.parseInt(datas.get(i)[2])/max;
mPath.moveTo(firstPoint.x,firstPoint.y);
mPath.lineTo(rect.width()/2 + data * perWidth/2,rect.top + everyHeight * i);
mPath.lineTo(rect.width()/2 - data * perWidth/2,rect.top + everyHeight * i);
areaRectF = new RectF(rect.width()/2 - data * perWidth/2, rect.top + everyHeight * i, rect.width()/2 + data * perWidth/2, rect.bottom);
mPath.close();
}else{
int data = Integer.parseInt(datas.get(i)[2])/max;
int nextData = Integer.parseInt(datas.get(i+1)[2])/max;
mPath.moveTo(rect.width()/2 + nextData*perWidth/2,rect.top + everyHeight * (i+1));
mPath.lineTo(rect.width()/2 - nextData*perWidth/2,rect.top + everyHeight * (i+1));
mPath.lineTo(rect.width()/2 - data*perWidth/2,rect.top + everyHeight * i);
mPath.lineTo(rect.width()/2 + data*perWidth/2,rect.top + everyHeight * i);
areaRectF = new RectF(rect.width()/2 - data*perWidth/2,rect.top + everyHeight * i,rect.width()/2 + data*perWidth/2,rect.top + everyHeight * (i+1));
mPath.close();
}
paint.setColor(colors.get(i));
canvas.drawPath(mPath,paint);
mPath.reset();
String uuid = UUID.randomUUID().toString();
rectMap.put(uuid, areaRectF);
dataMap.put(uuid, datas.get(i));
}
if (dataFont != null && !"".equals(dataFont)) {
if (dataFont.indexOf(";") != -1) {
String[] strs = dataFont.split(";");
for (String str : strs) {
if (str.startsWith("font-size")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
paint.setTextSize(Float.parseFloat(s.replace("px", "").trim()));
} else if (str.startsWith("style")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if ("bold".equals(s)) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else if ("italic".equals(s)) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
} else {
if (s.indexOf(",") != -1) {
if ("bold".equals(s.split(",")[0]) && "italic".equals(s.split(",")[1])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
if ("bold".equals(s.split(",")[1]) && "italic".equals(s.split(",")[0])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
}
}
} else if (str.startsWith("color")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if (CommonUtil.validRGBColor(s)) {
int r = Integer.parseInt(s.substring(0, 3));
int g = Integer.parseInt(s.substring(3, 6));
int b = Integer.parseInt(s.substring(6, 9));
paint.setColor(Color.rgb(r, g, b));
} else {
paint.setColor(Color.WHITE);
}
}
}
}
}
paint.setStyle(Style.STROKE);
for (int i=0;i<datas.size();i++){
canvas.drawText(datas.get(i)[1],rect.width()/2 - paint.measureText(datas.get(i)[1])/2,
rect.top + everyHeight * i + everyHeight/2 + obtainPaintXYHeight(dataFont)/2,paint);
}
}
private int obtainPaintXYHeight(String font) {
Paint paint = obtainPaintXYPaint(font);
return (int) (paint.getFontMetrics().bottom - paint.getFontMetrics().top);
}
/**
* 返回绘制XY坐标点文字的Paint对象
* @param font
* 文字尺寸
* @return
* Paint对象
* */
private Paint obtainPaintXYPaint(String font) {
Paint paint = new Paint();
if (font != null && !"".equals(font)) {
if (font.indexOf(";") != -1) {
String[] strs = font.split(";");
for (String str : strs) {
if (str.startsWith("font-size")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
paint.setTextSize(Float.parseFloat(s.replace("px", "").trim()));
} else if(str.startsWith("style")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if ("bold".equals(s)){
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else if("italic".equals(s)) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
} else {
if (s.indexOf(",") != -1) {
if ("bold".equals(s.split(",")[0]) && "italic".equals(s.split(",")[1])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
if ("bold".equals(s.split(",")[1]) && "italic".equals(s.split(",")[0])) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD_ITALIC));
}
}
}
} else if(str.startsWith("color")) {
int index = str.indexOf(":");
if (index == -1)
continue;
String s = str.substring(index + 1).trim();
if (CommonUtil.validRGBColor(s)) {
int r = Integer.parseInt(s.substring(0, 3));
int g = Integer.parseInt(s.substring(3, 6));
int b = Integer.parseInt(s.substring(6, 9));
paint.setColor(Color.rgb(r, g, b));
} else {
paint.setColor(Color.WHITE);
}
}
}
}
}
paint.setStyle(Style.FILL_AND_STROKE);
paint.setAntiAlias(true);
return paint;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
this.setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
return result;
}
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
return result;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
super.dispatchTouchEvent(event);
String[] data = obtainOnClickSelectedArea(event);
if (data == null) {
try {
if(click != null && !"".equals(click)) {
String str = "";
String vs[] = StringUtil.split(click, "||");
if(vs.length >= 1){
String[] op = vs[0].split(":");
if (op.length >= 2) {
if(click.startsWith("PAGE")) {
str = "MEIP_PAGE=" + op[1] + "\r\n";
} else if(click.startsWith("OP")) {
str = "MEIP_ACTION=" + op[1] + "\r\n";
}
if (!str.equals("")) {
if (vs.length >= 2) {
String[] args = StringUtil.split(vs[1], ",");
for (String arg : args) {
str += arg + "\r\n";
}
}
Intent intent = new Intent();
intent.setAction(StaticObject.ismenuclick == true ? StaticObject.MENU_CLICK_ACTION : StaticObject.CLICK_ACTION);
intent.putExtra("meip", str);
LocalBroadcastManager
.getInstance(ctx).sendBroadcast(intent);
}
}
}
}
} catch (Throwable e) {
String clsName = this.getClass().getName();
clsName = clsName.substring(clsName.lastIndexOf(".") + 1);
String info = ctx.getString(R.string.axeac_toast_exp_click);
Toast.makeText(ctx, clsName + info, Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(ctx, data[3], Toast.LENGTH_SHORT).show();
}
return false;
}
/**
* 判断点击手势是否在漏斗图上,并返回点击处设置的数据
* @param event
* MotionEvent对象
* @return
* 包含数据的String数组
* */
private String[] obtainOnClickSelectedArea(MotionEvent event) {
String[] data = null;
String u_uid = "";
String[] uuids = rectMap.keySet().toArray(new String[0]);
for (String uuid : uuids) {
RectF rectF = rectMap.get(uuid);
if (event.getX() > rectF.left && event.getX() < rectF.right
&& event.getY() > rectF.top && event.getY() < rectF.bottom) {
u_uid = uuid;
break;
}
}
if (!u_uid.equals("")) {
data = dataMap.get(u_uid);
}
return data;
}
public View getView(){
return this;
}
}
其中漏斗图数据格式为:id||name||num||toast"
id:不同数据项的id
name:漏斗图区域代表的名称
num:数据大小
toast:点击漏斗图区域显示的弹出文本
上一篇: 漏斗分析之SQL示例
下一篇: 漏斗图