【自訂控制項系列四】android繪製實戰(一)通過Canvas+Path+Paint組合繪製圖表,androidcanvas
轉載請註明: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)]}//求出座標點Pdouble 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;//圓心點座標Xprivate float mCenterY;//圓心點座標Yprivate int mWidth;//畫布的寬 == 手機螢幕的寬private int mHeight;//畫布的高 == 手機螢幕的高 - ActionBar - 頂部titlepublic 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")@Overrideprotected 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)]}//求出座標點Pdouble 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 - 頂部titlepublic 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")@Overrideprotected 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 - 頂部titleprivate 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) {}@Overrideprotected 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])));};};@Overrideprotected 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
android自訂控制項canvas畫圖問題
我是在activity裡面對控制項所在的relativelayout進行了scrollBy,這樣就會出現繪製不出螢幕外的那部分,是因為控制項限制了大小嗎?該怎麼寫比較好,這是我的拖動代碼@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: currentX = (int) event.getRawX(); currentY = (int) event.getRawY(); break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_MOVE: int x2 = (int) event.getRawX(); int y2 = (int) event.getRawY(); container.scrollBy(currentX-x2 , currentY-y2 ); currentX=x2; currentY=y2; metroView.invalidate(); default: break; } return super.onTouchEvent(event); } 查看原帖>>
怎用android 畫出三角形
。支支吾吾也沒說出來,哎,回家一想太可氣 了,就找到方法。原來是android.graphics.Path這個類,實現的。Api裡的描述如下:The Path class encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves. It can be drawn with canvas.drawPath(path, paint), either filled or stroked (based on the paint's Style), or it can be used for clipping or to draw text on a path. 它大概的意思是說path類包含了多種組件,比如直線段,二次曲線,三次曲線,它可以在畫布上繪製,無論是填充或筆畫。下面舉例說明:package cn.lgl.draw; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.graphics.Shader; import android.view.View; public class MyView extends View { public MyView(Context context) { super(context); // TODO Auto-generated constructor stub } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); /*設定背景為白色*/ canvas.drawColor(Color.WHITE); Paint paint=new Paint(); /*去鋸齒*/ paint.setAntiAlias(true); /*設定paint的顏色*/ paint.setColor(Color.RED); /*設定paint的 style 為STROKE:空心*/ paint.setStyle(Paint.Style.STROKE); /*設定paint的外框寬度*/ paint.setStrokeWidth(3); /*畫一個空心三角形*/ Path path=new Path(); path.moveTo(10,330); path.lineTo(70,330); path.lineTo(40,100); path.close(); canvas.drawPath(path, paint); /*設定paint 的style為 FILL:實心*/ paint.setStyle(Pain......餘下全文>>