您的位置:首页 > 移动开发 > Android开发

【自定义控件系列四】android绘制实战(一)通过Canvas+Path+Paint组合绘制图表

2014-11-06 22:16 381 查看
转载请注明:http://blog.csdn.net/duguang77/article/details/40869079

前面几篇博客,简单介绍了一下Canvas+Path+Paint的API,下面我们通过这几个类中的方法画出下面的效果图的样式

Demo下载地址:https://github.com/z56402344/Android_Graphics_Instance_One

效果图:



动态效果图:



这样的图在做项目的时候,一般是不会让美工去切图的,一是麻烦,二是没有办法去做好适配,所以大家只能通过绘图类进行绘制了

我们先来看下这个效果图最难的点怎么画.

这张效果图最难的点,我个人认为就是圆上的箭头怎么指向某一个柱状体顶点中间位置

图好像看起来还蛮复杂的,其实这些都是假象,我们先来拆分下图层吧



效果拆分层



简化图

这样拆分出来的图,大家就应该知道这张图示怎么画的吧!

我们来细讲一下,圆心点坐标我们通过

protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mWidth = canvas.getWidth();
mHeight = canvas.getHeight();
mCenterX = mWidth/2;
mCenterY = mHeight/4;
}


继承的View类 OnDraw()方法中的Canvas获取出屏幕一半宽,1/4高的点的位置,这就是上图中的O点坐标,而柱状体我们也是通过自己给的坐标点画出的,所以这两个点都是已知的。

我们最重要的是控制好过圆心,画出三角形,

我们通过之前了解到通过Canvas+Path+Paint组合API可以画出三角形,但是我们并不知道点P和P'的坐标位置,

//开始画三角形
Path path = new Path();// 三角形

path.moveTo((float)(x2), (float)(y2));//P点坐标
path.lineTo((float)(mPointB.x), (float)(mPointB.y));//圆心点坐标
path.lineTo((float)x1, (float)y1);//P'点坐标
path.close();//闭合画笔路径
canvas.drawPath(path, paint);//开始画


通过简化图,我们可以看出,其实就是一个数学问题,通过点O坐标和点H坐标,OP'和OP边长是自己给定的定值所以也是已知的,OH边长已知,PH和P'H通过勾三股四算出可得,有了这些参数我们就可以组成一个二元一次方程组来算出P和P'坐标如下所示

<span style="white-space:pre">	</span>PointBean mPointA;<span style="white-space:pre">	</span>//柱状体顶部中心坐标
<span style="white-space:pre">	</span>PointBean mPointB = new PointBean(760, 400); //初始化时,假设的一个圆心点坐标
//下面公式通过圆心点坐标和柱状体顶部中心点坐标,通过二元一次方程组计算出其余两个三角形的坐标点位置
// x=x1-a*sin{arctan[(y2-y1)/(x2-x1)]}
// y=y1+a*cos{arctan[(y2-y1)/(x2-x1)]}

//求出坐标点P
double x1 = mPointA.x - 50 * Math.sin(Math.atan((mPointB.y - mPointA.y) / (mPointB.x - mPointA.x)));
double y1 = mPointA.y + 50 * Math.cos(Math.atan((mPointB.y - mPointA.y) / (mPointB.x - mPointA.x)));

//求出坐标点P'
double x2 = mPointA.x + 50 * Math.sin(Math.atan((mPointB.y - mPointA.y) / (mPointB.x - mPointA.x)));
double y2 = mPointA.y - 50 * Math.cos(Math.atan((mPointB.y - mPointA.y) / (mPointB.x - mPointA.x)));


这就是这图最难的点,知道点P; P' ,H三点坐标,就可以轻松画出过圆心的三角形了

下面是所有代码,之后我会把项目放到github上,大家可以去下载

/**
* 通过柱状体顶部中心点坐标和圆心点坐标,画出过圆心的三角形
* @author DuGuang
*
*/
public class CustomTrigon extends View {

PointBean mPointA; //柱状体顶部中心坐标
PointBean mPointB = new PointBean(760, 400); //初始化时,假设的一个圆心点坐标

private float mCenterX; //圆心点坐标X
private float mCenterY; //圆心点坐标Y
private int mWidth; //画布的宽 == 手机屏幕的宽
private int mHeight;//画布的高 == 手机屏幕的高 - ActionBar - 顶部title

public CustomTrigon(Context context) {
super(context);
}

public CustomTrigon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public CustomTrigon(Context context, AttributeSet attrs) {
super(context, attrs);
}

@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mWidth = canvas.getWidth();
mHeight = canvas.getHeight();
mCenterX = mWidth/2;
mCenterY = mHeight/4;

mPointA = new PointBean((int)mCenterX, (int)mCenterY);

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Style.FILL);
paint.setStrokeWidth(30f);
paint.setDither(true);
paint.setColor(getResources().getColor(R.color.cril));

getDot2(paint, canvas);

}

public void getDot2(Paint paint, Canvas canvas) {
//下面公式通过圆心点坐标和柱状体顶部中心点坐标,通过二元一次方程组计算出其余两个三角形的坐标点位置 // x=x1-a*sin{arctan[(y2-y1)/(x2-x1)]} // y=y1+a*cos{arctan[(y2-y1)/(x2-x1)]} //求出坐标点P double x1 = mPointA.x - 50 * Math.sin(Math.atan((mPointB.y - mPointA.y) / (mPointB.x - mPointA.x))); double y1 = mPointA.y + 50 * Math.cos(Math.atan((mPointB.y - mPointA.y) / (mPointB.x - mPointA.x))); //求出坐标点P' double x2 = mPointA.x + 50 * Math.sin(Math.atan((mPointB.y - mPointA.y) / (mPointB.x - mPointA.x))); double y2 = mPointA.y - 50 * Math.cos(Math.atan((mPointB.y - mPointA.y) / (mPointB.x - mPointA.x)));

Log.i("dg", "x >>> " + x1 + " y >>> " + y1);

//开始画三角形 Path path = new Path();// 三角形 path.moveTo((float)(x2), (float)(y2));//P点坐标 path.lineTo((float)(mPointB.x), (float)(mPointB.y));//圆心点坐标 path.lineTo((float)x1, (float)y1);//P'点坐标 path.close();//闭合画笔路径 canvas.drawPath(path, paint);//开始画

}

/**
* 通过不同等级,塞入一个柱状体顶部中心点坐标
* @param pointB
*/
public void setData(PointBean pointB){
mPointB = pointB;
invalidate();
}

}


/**
* 自定义控件圆形
* @author DuGuang
*
*/
public class CustomCircle extends View {

private float mCenterX; // 圆形X轴中心
private float mCenterY;	//圆形Y轴中心
private float mCircleSize;	//圆形直径大小
private Context mContext;
private int mWidth;	//画布的宽 == 手机屏幕的宽
private int mHeight;//画布的高 == 手机屏幕的高 - ActionBar - 顶部title

public CustomCircle(Context context) {
super(context);
init(context);
}

public CustomCircle(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}

public CustomCircle(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

/**
* 初始化数据
* @param context
*/
private void init(Context context) {
this.mContext = context;
this.mCenterX = 350f;
this.mCenterY = 350f;
this.mCircleSize = 285f;

}

@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

mWidth = canvas.getWidth();
mHeight = canvas.getHeight();
mCenterX = mWidth/2;
mCenterY = mHeight/4;
mCircleSize = mHeight/6;

//第一个画笔,画出一个空心圆
Paint paint = new Paint();
paint.setAntiAlias(true); //消除齿距
paint.setStyle(Style.STROKE); //空心画笔
paint.setStrokeWidth(30f);	//画笔宽度
paint.setDither(true);	//消除脱颖
paint.setColor(getResources().getColor(R.color.cril)); //设置画笔颜色

//通过Path 用canvas在画布上画出圆形
Path path = new Path();
path.addCircle(mCenterX, mCenterY, mCircleSize, Path.Direction.CCW);
canvas.drawPath(path, paint);

//第二个画笔,画出一个实心圆
Paint paint_white = new Paint();
Path path_white = new Path();
paint_white.setAntiAlias(true);
paint_white.setStyle(Style.FILL);
paint_white.setDither(true);
paint_white.setColor(getResources().getColor(R.color.white));
//		path_white.addCircle(mCenterX, mCenterY, mCircleSize-15, Path.Direction.CCW);
path_white.addCircle(mCenterX, mCenterY, 5, Path.Direction.CCW);
canvas.drawPath(path_white, paint_white);

//第三个画笔,画出一个空心圆
Paint paint_STROKE = new Paint();
Path path_STROKE = new Path();
paint_STROKE.setAntiAlias(true);
paint_STROKE.setStyle(Style.STROKE);
paint.setStrokeWidth(5f);	//画笔宽度
paint_STROKE.setDither(true);
paint_STROKE.setColor(getResources().getColor(R.color.cril));
path_STROKE.addCircle(mCenterX, mCenterY, mCircleSize-25, Path.Direction.CCW);
canvas.drawPath(path_STROKE, paint_STROKE);

}

}

/**
* 自定义空间,带圆角的柱状体
* @author DuGuang
*
*/
public class CustomRect extends View {

//圆角柱状体4个角的值
private float[] radii = { 12f, 12f, 12f, 12f, 0f, 0f, 0f, 0f };

//柱状体的颜色
private int[] colors = { R.color.rect_cril_leve1, R.color.rect_cril_leve2,
R.color.rect_cril_leve3, R.color.rect_cril_leve4,
R.color.rect_cril_leve5, R.color.rect_cril_leve6 };

private int mWidth; //画布的宽 == 手机屏幕的宽
private int mHeight;//画布的高 == 手机屏幕的高 - ActionBar - 顶部title
private int mRectWidth;	//矩形宽
private int mRectHeight;//矩形高
private Paint mPaint;
private String mLevel;	//画的L1-L3 字样
private String mName;	//画的初级,高级,专家字样
private static float mToY = 15f; //小于1,整体往下移动;大于1,整体往上移动
private static float mRectY = 4;//往1方向,矩形长度拉长,往10方向,矩形长度缩短
private ArrayList<String> mPointList;	//柱状体顶部中心坐标的集合

public CustomRect(Context context) {
super(context);
init(context);
}

public CustomRect(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}

public CustomRect(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

private void init(Context context) {

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPointList = new ArrayList<String>();
mWidth = canvas.getWidth();
mHeight = canvas.getHeight();

mRectWidth = (int) (mWidth / 9.5);
mRectHeight = mHeight/2;

//循环出6个柱状体
for (int i = 0; i < 6; i++) {
initBitmaps(canvas,i);
}

}

/**
* 画矩形
* @param canvas
* @param index
*/
private void initBitmaps(Canvas canvas,int index) {

mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.FILL);
mPaint.setStrokeWidth(30f);
mPaint.setDither(true);
mPaint.setColor(getResources().getColor(colors[index]));

//通过Path路径,计算出每个柱状体宽高,并把柱状体顶部中心坐标放入集合
//柱状体顶部中心坐标放入集合,用于和圆心中央的三角,通过中心坐标和柱状体坐标,画出三角形
Path path = new Path();
int width = (int) (mRectWidth/2+(index*mRectWidth*1.5));
int height_top = (int) (mRectHeight+(mRectHeight/15)*(6-index)+mRectWidth*1.5);
int height_bootom = height_top-mRectHeight/10+(mRectHeight/15)*index;
height_top = (int) (height_top - mRectHeight/mRectY);//高度起始位置向0方向移动1/10屏幕
path.addRoundRect(new RectF(width, height_top, width+mRectWidth, height_bootom), radii,
Path.Direction.CCW);
canvas.drawPath(path, mPaint);
String RectX = String.valueOf(width+mRectWidth/2);
String RectY = String.valueOf(height_top);
mPointList.add(RectX+"-"+RectY);
Log.i("dg", "mPointList >>> "+ mPointList.size());

Path path1 = new Path();
path1.addRoundRect(new RectF(width, height_bootom+10, width+mRectWidth, height_bootom+12), radii,
Path.Direction.CCW);
canvas.drawPath(path1, mPaint);

switch (index) {
case 0:
mLevel = "L1-L3";
mName = "入门";
break;
case 1:
mLevel = "L4-L6";
mName = "初级";
break;
case 2:
mLevel = "L7-L9";
mName = "中级";
break;
case 3:
mLevel = "L10-L12";
mName = "中高级";
break;
case 4:
mLevel = "L13-L15";
mName = "高级";
break;
case 5:
mLevel = "L16";
mName = "专家";
break;

default:
break;
}
drawLevel(canvas, index, width, height_bootom,mLevel);
drawText(canvas, index, width, height_bootom,mName);

}

/**
* 画名称
* @param canvas
* @param index
* @param width
* @param height_bootom
* @param name
*/
private void drawText(Canvas canvas, int index, int width, int height_bootom, String name) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Style.FILL);
paint.setStrokeWidth(30f);
paint.setDither(true);
paint.setColor(getResources().getColor(colors[index]));
paint.setTextSize(30);
canvas.drawText(name , width+mRectWidth/5, height_bootom+100, paint);
}

/**
* 画等级
* @param canvas
* @param index
* @param width
* @param height_bootom
* @param level
*/
private void drawLevel(Canvas canvas, int index, int width,
int height_bootom, String level) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Style.FILL);
paint.setStrokeWidth(30f);
paint.setDither(true);
paint.setColor(getResources().getColor(colors[index]));
paint.setTextSize(30);
if(index ==5){
canvas.drawText(level , width+mRectWidth/4, height_bootom+60, paint);
}else if(index == 4 || index ==3 ){
canvas.drawText(level , width+mRectWidth/20, height_bootom+60, paint);
}else{
canvas.drawText(level , width+mRectWidth/6, height_bootom+60, paint);
}
}

public ArrayList<String> getPointList() {
return mPointList;
}

public void setPointList(ArrayList<String> mPointList) {
this.mPointList = mPointList;
}

}


/**
* 主页面
* @author DuGuang
* blog地址:http://blog.csdn.net/duguang77
*
*/
public class TestCourseReportActivity extends Activity {

private FrameLayout mFlMain;
private ArrayList<String> mPointList;
private CustomRect mCusRect;
private CustomTrigon mTrigon;
private TextView mTvHideOne, mTvLevel, mTvHideTwo,mTvHide;
private View mViewLine;

private int mWidth;
private int mHeight;

private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
//获取5个柱状体顶点中心坐标位置
mPointList.addAll(mCusRect.getPointList());
String[] split = mPointList.get(5).split("-");
mTrigon.setData(new PointBean(Integer.parseInt(split[0]), Integer
.parseInt(split[1])));
};
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_course_testcourse_report);

initView();
initData();
}

private void initView() {
mFlMain = (FrameLayout) findViewById(R.id.fl_mian);
mCusRect = (CustomRect) findViewById(R.id.cus_rect);

mTvHideOne = (TextView) findViewById(R.id.tv_hide_one);
mTvLevel = (TextView) findViewById(R.id.tv_level);
mTvHideTwo = (TextView) findViewById(R.id.tv_hide_two);
mViewLine = findViewById(R.id.view_line);
mTvHide = (TextView) findViewById(R.id.tv_hide);

}

private void initData() {

mPointList = new ArrayList<String>();
CustomCircle circle = new CustomCircle(this);
mTrigon = new CustomTrigon(this);

mFlMain.addView(mTrigon,2);
mFlMain.addView(circle,3);

new Thread() {
public void run() {
//这里启动线程是为了防止layout布局文件还没有完成,去获取柱状体顶部坐标的时候Null异常
SystemClock.sleep(200);
mHandler.sendEmptyMessage(0);

};
}.start();

// 获取屏幕宽高(方法1)
mWidth = getWindowManager().getDefaultDisplay().getWidth(); // 屏幕宽
mHeight = getWindowManager().getDefaultDisplay().getHeight(); // 屏幕高

int width = mWidth / 2 - mWidth /8 ;
int height = mHeight / 4 - mHeight/12;

//这里第一个TextView竟然显示不出来,不知道为什么,做个标记,以后修改
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
params.topMargin = height+40;
params.leftMargin = width;
mTvHideOne.setLayoutParams(params);

FrameLayout.LayoutParams params4 = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
params4.topMargin = height-10;
params4.leftMargin = width;
mTvHide.setLayoutParams(params4);

FrameLayout.LayoutParams params1 = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
params1.topMargin = height+40;
params1.leftMargin = width;
mTvLevel.setTextColor(getResources().getColor(R.color.text_hide));
mTvLevel.setLayoutParams(params1);

FrameLayout.LayoutParams params2 = new FrameLayout.LayoutParams(
300, 1);
params2.topMargin = height+140;
params2.leftMargin = width;
mViewLine.setBackgroundColor(getResources().getColor(R.color.view_backgroud));
mViewLine.setLayoutParams(params2);

FrameLayout.LayoutParams params3 = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
params3.topMargin = height+150;
params3.leftMargin = width;
mTvHideTwo.setTextColor(getResources().getColor(R.color.text_level));
mTvHideTwo.setLayoutParams(params3);

}

}


Demo下载地址:https://github.com/z56402344/Android_Graphics_Instance_One

项目这周周末会发到github上,大家等链接地址吧,如有什么疑问请留言

转载请注明:http://blog.csdn.net/duguang77/article/details/40869079
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息