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

时间:2023-02-13 18:26:50


转载请注明:http://blog.csdn.net/duguang77/article/details/40869079

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

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

效果图:

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


动态效果图:

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


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

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

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

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

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

效果拆分层

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

简化图


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

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

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