【如果你想了解這個點讀筆寫字App的背景,請移步這里
http://www.lxweimin.com/p/ee2a1bb99280 】
直到這篇文章的時候,我并不知道在android App運行的過程中需要使用到的圖片文件應該放到何處比較合適,一點可以肯定的是不能在app安裝時直接寫到安裝路徑包的文件夾下面,因為這樣會發生誤刪,從而引起app運行時獲取不到圖片的錯誤。如果不應該這么做,麻煩告訴我應該放到哪吧。
正如題目所說的,我把他放到了Drawable中,運行中就可以方便的取得圖片了,不過放到drawable文件中方便的同時也給我帶來一些困擾,下面聽我慢慢道來吧。
在app中,我去取得圖片來作為筆記本的頁背景(就是普通的筆記本上一行一行的行文線的樣子),在上面書寫,實際也就是用特定的像素點去取代之前背景圖片的像素點了。當然最終筆記本的寫上內容的圖片是需要保存的,因為我希望下次打開筆記本的時候,我在那一頁頁上寫下的小心情還是存在的。當然我沒法再把它們存在drawable中了,我選擇把這些記錄保存到本地。
本篇的內容并不涉及我如何從本地獲取圖片來初始化我筆記本的頁,也不涉及我如何從內存中把我的圖片寫到本地。這里提到這些是因為打開筆記本的時候,取出寫過的頁和從drawable中去會有些不同。而在之前寫過的頁上繼續我寫字是很正常的事,我應該有這樣的功能。(你做為程序員不應阻止我勤儉節約的本質吧。這里順便給你們舉個例子吧:我有個大學舍友,考研那會,他在草稿上演算的時候都是先用黑色的筆寫滿整張草稿,然后再換紅色的筆寫滿,然后才扔掉。怎樣,節約吧?其實那時這么做的他大概是想通過這樣的方式向自己展示努力的成果吧。畢竟不是每次演算都是有結果的,在那段迷茫空虛的日子,這些努力的痕跡卻可以證明未辜負青春正好)。當然,初始化書寫過的內容,保存書寫的內容到本地,會單獨寫一篇,請移步這里,《初始化本子上已經記錄的文字》。
下面開始吧:
獲取bitmap對象的方法
- getResources().getDrawable.getBimap
- 通過BitmapFactory功能累來獲取
我們需要在一個圖片上畫圖,過程是:獲取該圖的bitmap對象->建立畫布->在對應的像素處作畫。
在項目的過程中,我發現使用BitmapFactory來操作bitmap對象十分的方便,
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inMutable = true;
baseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.page_background, opts);
BitmapFactory.Options是BitmapFactory一個功能參數設置,指示該如何去處理對象,如果處理實際上也決定了我們得到什么(雖然生活比這個復雜,但也是這個理)。如這里的設置inMutable為true,mutable是可變的意思,即為獲取的圖片我們是可以
canvas = new Canvas(baseBitmap);
這樣建立一個畫布作畫的。默認獲取得的bitmap是不支持改變bitmap的內容的,即使你把這塊2進制的數據取到內存中,這里這么做的原因不知道是不是為了保護數據完整性(我感覺不是很必要,畢竟你去new canvas的時候就是表示要對其操作了,在VC++中就是未保護的)。注意這里不是不是mutable的時候建立畫布會報錯。如果采取是第一種方法獲取的bitmap的話,當然也是非mutable的了,這里的辦法就是
//其中copy還需要傳進參數
baseBitmap =((BitmapDrawable)getResources().getDrawable(R.drawable.page_background))
.getBitmap().copy();
由于第二種要簡潔明了,我推薦第二種方式啦,所以copy中的參數我就不說了,有興趣的自己看去api的文檔吧。
按比例縮放圖片
這介紹兩種縮放方式
- BitmapFactory按比例縮放
- CreateBitmap按長寬比例縮放
BitmapFactory按比例縮放
這里我們設定一個情景,假定我們需要獲取一個bitmap對象其長寬為原來圖片的大小,即一個800*800(px)的圖片,我希望獲取的bitmap對象就是800*800。可能有點了解android的朋友會知道,在drawable文件夾中的圖片會根據他文件夾的后綴(drawable-ldpi、-mdpi、-hdpi、-xhdpi、xxhdpi)去自動的進行一些拉伸,android這么的機制呢是為了保證不同顯示分辨率上顯示效果的一致性。這到底是個什么情況呢,我會在文章的最后來解釋一下。這也就是我在之前說的,從本地取出寫過的頁圖片和從drawable中去會有些不同,因為drawable會自動去做圖片的縮放啊,從drawable中獲取的可能已經是縮放過的了,所以我們這里要想取得原始圖片了。是不是感覺有點亂,其實沒有啊,我去取得原始圖片是因為我提前知道drawable會縮放圖片啊(其實哪有什么提前,之前并沒有人告訴我,我也是先掉到坑里從這個坑中爬出來的)。
至于我為什么要獲得原始圖,因為我只有一個原始圖的像素矩陣我才能“隨心所欲”地去到對應像素點位置作畫。如800*800的圖,我希望把它分成8*8的格子,然后我把左上角的格子涂黑:
canvas.drawRect(0, 0, 100, 100);
好了,我們還是來看看如果使用BitmapFactory來進行按長寬原始比例縮放:
//firstly
BitmapFactory.Options opts = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//此參數意思是不建立bitmap對象,只取圖片原來大小,這么做的好處就是節約內存
BitmapFactory.decodeResource(getResources(), R.drawable.page_background, opts);//這里無需返回bitmap
int be = (int)(options.outHeight / (float)200);//200為希望的高縮放到200,只要設置這個就好,因為寬和高是等比例的嘛
//then
options.inJustDecodeBounds = false;//默認為false。意思建立bitmap
option.inSampleSize = be ;//注意上面計算be時int那個強制轉換,因為inSample只接受int
baseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.page_background
, opts);
上面就是把圖片從800*800縮放到200*200的例子了,我們做個簡單的計算,相當于把option.inSampleSize賦值為4,則獲取到的圖為原來1/4。inSampleSize只能為整數,所以我們獲得的圖片只能為1,1/2,1/3,...(這就有問題啦,2/3這樣的是不允許存在的,哪怕你算術運算算出的結果是整數,這個在上面說的最后部分給個例子)。其實知道BitmapFactory的inSampleSize設置發明出來是為了快速的獲取縮略圖用的,你也就釋然了。
按照上面的來,我要獲取原圖,那我是不是應該
be = (int)(options.outHeight / (float)options.outHeight);//be = 1
設option.inSampleSize=1,遺憾的是獲取的圖還是被縮放了。就是說我沒有用BitmapFactory從drawable中取出未做縮放的圖了。其實如果你這樣就可以取得未做縮放圖片了:
BitmapFactory.decodeFile("/sdcard/test.jpg", options);//注意為本地讀取,這就是和drawable的區別了。options不做任何設置,就是取原圖
CreateBitmap按長寬比例縮放
matrix.postScale(scaleWidth, scaleHeight);
resizeBitmap = Bitmap.createBitmap(bitmap, 0, 0, bmpWith, bmpHeight, matrix, false);
scaleWidth和scaleHeight的計算是類似這樣 scaleWidth=200/800=1/4其中200為其想要縮放到的尺寸,當然這里的這里的scaleWidth和scaleHeight是可以分別指定到不同的尺寸的,也沒有必須int型的限制。
Bitmap.createBitmap函數中的參數bitmap為指定的縮放對象;后面的0, 0, bmpWith, bmpHeight即為所取的bitmap對象的區域,我們這里bmpwith,bmpHeight為bitmap這個指定對象的對應值啦,意思取整個圖進行縮放。matrix就是給個縮放的比例尺了。
認識Drawable(hdpi,mdpi,ldpi等等)作用
首先系統是根據發布的目標設備的PPI(pixels per inch)去對應的文夾中搜索所添加資源圖片的,搜索到即取出來。dpi和ppi的區別 搜索順序是:對應文件夾->有標識文件夾(hdpi,mdpi,ldpi等,就近原則,至于是先搜大的還是小的,深究這個沒有意義,畢竟如果你取實現該功能實現者就隨你了)->drawable(默認文件夾)。而這個dpi每個設備是不同的,android并不可能為每種dpi設置一個drawable,所以這些dpi都是有個就近取特定值的策略。
各個文件夾對應的dpi
文件夾種類 | dpi值 |
---|---|
drawable | 160dpi |
drawable-ldpi | 120dpi |
drawable-mdpi | 160dpi |
drawable-hdpi | 240dpi |
drawable-xhdpi | 320dpi |
drawable-xxhdpi | 480dpi |
其實最終我想獲取圖片原始尺寸并沒有采用第二種方式(第一種直接取不是原始尺寸啦,前面說了),畢竟那種拉伸是可以能引起圖片的變形的(height和width可以按不同的比例拉伸啊)。主要還是我喜歡BitmapFactory那樣的操作啦,封裝得很好,要是我們自己開發的過程中可以把工具類做成這個樣子那就更好啦,想的時候看看源碼吧(其實我也沒看呢,哈哈)。那么我是怎么做的呢?
BitmapFactory.Options opts = new BitmapFactory.Options();
baseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.page_background
, opts);
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
float density = dm.density;
nNibSize = (int)(100 * (160 * density / 240));//hdpi represent for 240dp;density depend on 160;
前面一段我們知道是怎么回事了,我想取一個原始尺寸的圖片嘛(當然通過url到本地取就是原始尺寸了),它給我一個拉伸之后的嘛。然后我過DisplayMetrics取獲取目標設備的PPI給我畫筆的像素寬度做一個相應的放大。即我之前800*800的圖片,畫一個點是100*100的,每個100*100的框都是一個點。現在800*800的圖片放大了,那么我相應的點的大小也要放大(縮小也縮小嘛,我這么做也是為了兼容不是PPI設備嘛,我也很牛逼)。其中,nNibSize就是我所謂一個點的像素(100*100)。舉個例子會更清晰點:
我的設備為5.7inch,分辨率為1920*1080(px),計算該設備PPI
這就是求得我設備PPI了。而我的設備就近歸檔到那個drawable呢?我debug我的代碼,我發現800*800圖片放到了-hdpi下,獲得的圖的大小為1467*1467,為什么會是這個值。 在上述的代碼中,我獲得了一個就近取的像素密度,發現dm.density取得的值為2.75,也就是我們386的像素密度就近取了440=2.75*160,結果表明440并沒有在我們上表所列的文件夾中的,說明android有自己一套歸檔策略,至于為什么會有440這檔,沒必要去深究,那只是實現者想去處理事情的方式而已。
而1467=800*(2.75*160/240)這個值就是根據這個來縮放的。首先我們的圖片在-hdpi中,這個像素密度是比我們設備的對應像素密度要低的。也就是說在hdpi對應的像素密度中,我們的圖應該顯示成原圖,而到高的PPI中時如果再顯示原圖的話,那么圖片相對于屏幕要小了,要達到相同的顯示效果就是要把圖片按照這個PPI的比例放大,這個比例就是2.75*160/240。所以nNibSize = (int)(100 * (160 * density / 240));這行代碼我只是按照他的一個比例把我100的像素變大以適應變大后的圖而已。效果是可以在如下圖中,不管PPI是多少,總是可以一次只涂黑一個格子,不會出現涂到別的像素或者涂不滿的情況。