Android二維圖形繪制
Android提供了一系列用於二維繪制的APIs,當繪制2D圖形時,通常有兩種選擇:
1.在一個View對象中繪制。繼承View類,在子類的 onDraw()方法中寫入自己定義的繪制代碼。
2.直接在畫布(Canvas)上繪制。
Canvas是一個管理繪制操作的類。
Canvas的底層有一個Bitmap,你的繪制實際上是在這個位圖對象上。
當你需要繪制某些東西的時候,你實際上需要四個基本組件:
1.一個Bitmap來存放像素。
2.一個Canvas來調用繪制函數,向位圖中寫入內容。
3.要繪制的基本圖元,如矩形(Rect), 路徑(Path), 位圖(BitMap),或者文字等。
4.一個畫筆(Paint),指定了繪制時所用的顏色和樣式等。
程序實例:
這個程序實際是ApiDemos中FingerPaint程序的一小部分,實現了用戶在控件中的自由繪制。
程序自定義的MyPaintView類繼承了View類,用Path記錄用戶點擊的軌跡,在onDraw()方法中將軌跡畫出。當然這個程序中最重要的是各種觸摸事件的處理。
其中invalidate()方法的作用是及時調用onDraw()方法進行繪制。
程序更新:2013/2/28
首先,糾正之前犯的一個錯誤,就是在自定義View的子類時,應該將其基類的三個構造函數全都覆寫,並在其中調用基類構造函數后進行初始化。
之前的做法在整個布局中只有一個控件時沒有問題,但是要使用xml布局文件則會出錯,因為必要的構造方法沒有提供,或沒有在其中調用初始化方法。
其次,使用了布局文件進行布局,加入了一個TextView顯示歡迎語句,底部加入Clear按鈕可以進行畫面清除。
關於畫面清除我想到了兩種方法:
1.重新生成位圖對象;2.Bitmap類中有一個eraseColor函數,利用它把位圖繪制為白色。
應該是第二種方法比較好吧,畢竟總是重新生成對象,應該會有一些內存清理方面的問題。
附上代碼(2013/2/18版本):
自定義View:
MyPaintView
package com.mengexample.hellofreepaint; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class MyPaintView extends View { private Resources myResources; // 畫筆,定義繪制屬性 private Paint myPaint; private Paint mBitmapPaint; // 繪制路徑 private Path myPath; // 畫布及其底層位圖 private Bitmap myBitmap; private Canvas myCanvas; private float mX, mY; private static final float TOUCH_TOLERANCE = 4; // 記錄寬度和高度 private int mWidth; private int mHeight; public MyPaintView(Context context) { super(context); initialize(); } public MyPaintView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } public MyPaintView(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } /** * 初始化工作 */ private void initialize() { // Get a reference to our resource table. myResources = getResources(); // 繪制自由曲線用的畫筆 myPaint = new Paint(); myPaint.setAntiAlias(true); myPaint.setDither(true); myPaint.setColor(myResources.getColor(R.color.purple_dark)); myPaint.setStyle(Paint.Style.STROKE); myPaint.setStrokeJoin(Paint.Join.ROUND); myPaint.setStrokeCap(Paint.Cap.ROUND); myPaint.setStrokeWidth(12); myPath = new Path(); mBitmapPaint = new Paint(Paint.DITHER_FLAG); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; myBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); myCanvas = new Canvas(myBitmap); } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touch_start(x, y); invalidate(); break; case MotionEvent.ACTION_MOVE: touch_move(x, y); invalidate(); break; case MotionEvent.ACTION_UP: touch_up(); invalidate(); break; } return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 背景顏色 // canvas.drawColor(getResources().getColor(R.color.blue_dark)); // 如果不調用這個方法,繪制結束后畫布將清空 canvas.drawBitmap(myBitmap, 0, 0, mBitmapPaint); // 繪制路徑 canvas.drawPath(myPath, myPaint); } private void touch_start(float x, float y) { myPath.reset(); myPath.moveTo(x, y); mX = x; mY = y; } private void touch_move(float x, float y) { float dx = Math.abs(x - mX); float dy = Math.abs(y - mY); if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { myPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); mX = x; mY = y; } } private void touch_up() { myPath.lineTo(mX, mY); // commit the path to our offscreen // 如果少了這一句,筆觸抬起時myPath重置,那么繪制的線將消失 myCanvas.drawPath(myPath, myPaint); // kill this so we don't double draw myPath.reset(); } /** * 清除整個圖像 */ public void clear() { // 清除方法1:重新生成位圖 // myBitmap = Bitmap // .createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); // myCanvas = new Canvas(myBitmap); // 清除方法2:將位圖清除為白色 myBitmap.eraseColor(myResources.getColor(R.color.white)); // 兩種清除方法都必須加上后面這兩步: // 路徑重置 myPath.reset(); // 刷新繪制 invalidate(); } }
Activity:
Activity
package com.mengexample.hellofreepaint; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View; import android.widget.Button; public class PaintActivity extends Activity { Button clearBtn; MyPaintView paintView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 將自定義的控件類作為整個布局 //setContentView(new MyPaintView(this)); //使用布局文件 setContentView(R.layout.activity_paint); paintView = (MyPaintView) findViewById(R.id.view_paint); clearBtn = (Button)findViewById(R.id.btn_clear); clearBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { paintView.clear(); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_paint, menu); return true; } }
布局文件:
activity_paint.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="10" > <com.mengexample.hellofreepaint.MyPaintView android:id="@+id/view_paint" android:layout_width="fill_parent" android:layout_height="fill_parent" > </com.mengexample.hellofreepaint.MyPaintView> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/text_welcome" /> </FrameLayout> <Button android:id="@+id/btn_clear" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="2" android:text="@string/btn_clear" /> </LinearLayout>
附上一些顏色值,這里雖然沒怎么用到,但以后可以復用:
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red">#f00</color>
<color name="green">#0f0</color>
<color name="blue">#0000ff</color>
<color name="black">#000</color>
<color name="blue_light">#33b5e5</color>
<color name="blue_dark">#0099cc</color>
<color name="purple_light">#aa66cc</color>
<color name="purple_dark">#9933cc</color>
<color name="green_light">#99cc00</color>
<color name="green_dark">#669900</color>
<color name="yellow_light">#ffbb33</color>
<color name="yellow_dark">#ff8800</color>
<color name="red_light">#ff4444</color>
<color name="red_dark">#cc0000</color>
</resources>
程序運行如圖(我自己手寫的Hello Wind,比較幼稚,哈~):

程序還可進一步改進,加入調色板、橡皮擦、清除、形狀選擇與繪制、線型選擇等功能,變成一個完善的畫圖程序。
參考資料:
API DEMOS: FingerPaint
API Guides: Canvas and Drawables
http://developer.android.com/guide/topics/graphics/2d-graphics.html
