若非專業設置遊戲界面或者從事美工人員,對於2D圖形繪製工具Canvas,只需懂得基本用法就好,技術人員主要是爲了實現功能,Canvas也主要是在自定義view及其子類時,繪製界面所用。
canvas繪圖被人形象的描述爲“畫布”,這樣理解也無可厚非,畫筆“paint”工作的地方就是畫布。
有時候需要製作一些圖形,現有控件是無法辦到的,這時需要我們新建view類,然後將內容畫到視圖中,最常用的就是重寫draw方法,這個方法就是顯示界面時候你指定要”畫“的東西。
class DrawView extends View {
public DrawView(Context context) {
super(context);
}
@Override
public void draw(Canvas canvas){
super.draw(canvas);//參數傳入的畫布,明顯還有其他地方需要引用,因此不要自己去新建canvas了。
int width =canvas.getWidth();//獲取畫布的寬;
int height = canvas.getHeight();//獲取畫布的高;
Paint paint = new Paint();//新建畫筆類;
paint.setStyle(Paint.Style.STROKE);//設置畫筆類型爲stroke,表示只描邊
paint.setAlpha(250);//設置——不——透明度,爲0表示透明。
paint.setAntiAlias(true);//抗鋸齒,爲了讓畫出來的線更平滑。
paint.setStrokeWidth(5);//設置畫筆線的粗細,是以像素爲單位的,例如紅米手機像素爲720*1280
paint.setColor(Color.BLUE);//設置畫筆顏色
canvas.drawPoint(100,100,paint);//在100,100的座標畫一個點,點的大小可以通過設置畫筆線的粗細來調節。
}
}
如果在linearLayout中橫向排列同時設置了一個控件的width=“wrap_content”和weight,那麼會優先設置控件的寬度大於等於wrap_content;
基本的canvas和paint用法,網上可以自己找,很多人都有介紹,這裏只是畫一個點示範,要注意的是,座標軸是以你自定義的控件的左上角爲座標原點的,如果你控件不是充滿整個屏幕,那麼canvas的width和height就不是爲屏幕的像素了,另外如果你重寫了ontouch方法,會發現座標會有負值出現。
爲了學習canvas,我自己製作了一個塗鴉板,手指接觸屏幕或者滑動時,會在上面留下軌跡,然後可以將畫板以圖片文件的形式保存下來,且如果再次打開畫板,可以將保存的圖片文件再顯示到畫板上,功能大概就是如此,且看代碼。
class DrawView extends View {
public DrawView(Context context) {
super(context);
}
@Override
public void draw(Canvas canvas){
super.draw(canvas);
int width =canvas.getWidth();
int height = canvas.getHeight();
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setAlpha(250);
paint.setAntiAlias(true);
paint.setStrokeWidth(5);
paint.setColor(Color.BLUE);
//從這裏往下看->
File file=new File(Environment.getExternalStorageDirectory() + "/" + "bitmap.jpg");
if(file.exists()) {
try {
Bitmap b=BitmapFactory.decodeFile(file.getPath());
canvas.setBitmap(b);
} catch (Exception e) {
Toast.makeText(DrawBitmap.this,e.toString(),Toast.LENGTH_LONG).show();
}
}
}
}
爲了省事我把很多的代碼刪除了,所以有些變量看起來跟沒有定義一樣,沒關係,我們直接看。
當有文件bitmap存在時,就通過BitmapFactory.decodeFile()生成bitmap位圖,然後直接通過canvas.setBitmap()方法,把位圖設爲畫布的背景,這時會有異常:
IllegalStateException
查看源碼發現是因爲setBitmap()方法參數必須是可以改變的位圖:
public void setBitmap(@Nullable Bitmap bitmap) {
if (!bitmap.isMutable()) {
throw new IllegalStateException();
}
}
}
//注意這代碼都是截取的,因此發現跟源碼不同不要驚異。
然後我改變源碼,先把bitmap變爲isMutable的,再傳入:
canvas.setBitmap(b.copy(Bitmap.Config.ARGB_8888, true));
嗯,它又拋出異常:UnsupportOperationException
沒辦法,我只能再上百度,有大神說這是因爲開啓了硬件加速,所以導致異常,於是我再然後在Manifest.xml的application中加上android:hardwareAccelerated=”false”,這是關閉硬件加速,結果沒有錯誤了,界面卻又無法顯示了,手指接觸屏幕也不會有痕跡出現,.還得去查看硬件加速的問題……>>>>>>>>>
<application
android:hardwareAccelerated="false"
還有的童鞋發現在copy之後,因爲內存中會存在多張圖片,會導致內存溢出OOM,嗯,好吧,如果以後想用setBitmap方法,還是省省吧,改用drawBitmap比較好一點,不會有這麼多的麻煩。
不過需要注意的是,使用drawbitmap時,要不能使用過大的圖片,否則會導致OOM,一般300Kb的就可以,再大就有可能導致畫面卡頓甚至卡死,不過我發現一個奇怪的現象,在我的塗鴉界面中,如果把傳入的
copy之後再drawBitmap,雖然界面還是會卡頓,但不會直接退出。
bitmap大小=圖片長度(px)*圖片寬度(px)*單位像素佔用的字節數
單位像素所佔字節數就是圖片的色彩模式,安卓默認爲四個字節。
在BitmapFactory.Options.inPreferredConfig這裏可以找到,一共有4種, ARGB代表:A 透明度 , R 紅色, G 綠色, B 藍色。
圖片的壓縮方法可以參考以下博客:
android圖片壓縮的三種算法
這是質量壓縮算法的實現:
File file=new File(Environment.getExternalStorageDirectory() + "/" + "bitmap.jpg");
if (file.exists()) {
int p=90;
Bitmap b=BitmapFactory.decodeFile(file.getPath());
while(b.getByteCount()>1024*300&&p>20){
//這裏爲了明顯對比,進行的是破壞性實驗,把圖片最低壓縮爲30%,可以自己實驗看下效果。
try {
b.compress(Bitmap.CompressFormat.JPEG,p--,new FileOutputStream(file));
b=BitmapFactory.decodeFile(file.getPath());
} catch (FileNotFoundException e) {
Toast.makeText(MainActicity.this,"bitmap can't compress",Toast.LENGTH_SHORT).show();
break;
}
}
}
按比例壓縮方法:
private Bitmap compressPicture(String pathName,int height,int width){//按比例壓縮
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds = true;
//設置options只讀取圖片的大小。
Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/"+pathName,options);//此時返回bm爲空,但必須有這一步,這是獲取圖片options的。
options.inJustDecodeBounds=false;
int h=options.outHeight;
int w=options.outWidth;
//得到圖片的寬高,
int zip= h/height>w/width?h/height:w/width;
options.inSampleSize=zip;//設置縮放的比例,此時改變了像素大小,那麼bitmap大小就會改變。
bitmap=BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/"+pathName,options);
return bitmap;