【自定義控件系列四】android繪製實戰(一)通過Canvas+Path+Paint組合繪製圖表


轉載請註明: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




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章