編輯:關於Android編程
那麼今天,我們繼續來看一篇關於Android中的UI篇,如何自定義視圖View的進階篇,關於前奏篇之前已經寫過了,在這篇文章中我主要介紹了自定義View的一些基礎知識,講解了Paint,Canvas,Path,漸變色等技術。那麼隔了半年的時間,我們今天再次看一下如何在上一個層次去使用Canvas自定義更多的我們想要的特效呢?說道這裡,我還想多講兩句,就是Android中UI知識點還是很多很高深的,如果你對UI技術了解的非常透徹,那麼待遇也是很不錯的,現在可以看到一些公司在找Android高級UI工程師,就是叫你去做一些酷炫的UI,同時對性能方面也是要了解很多的,因為我們知道酷炫的UI動畫什麼的,其實如果處理不好會給整個程序造成很大的性能問題。所以本人對Android中的UI和逆向領域非常感興趣的。
下面我們就來看看,如何使用Canvas做一些我們經常要用到的效果:
1、修改圖片的透明度
2、圖層的疊加效果(遮罩層)
3、畫布的保存和恢復操作
4、使用矩陣實現平移,旋轉,縮放動畫
5、通過旋轉,平移,縮放畫布來實現動畫
6、裁剪畫布
總共六個知識點,講完這些知識點,現在市面上的View大部分都可以知道原理,以及自己可以動手去繪制了,後面會再寫一篇關於自定義ViewGroup系列(LinearLayout,RelativeLayout等)的相關文章。
我們下面就用例子一一介紹這上面的六個功能點,首先我們先回顧一下,如何自定義一個View,其實很簡單,我們只需要集成View類,然後在onDraw方法中,得到一個畫布Canvas對象,然後就可以繪制各種我們想要的效果了。
public void onDraw(Canvas canvas)
下面我們先來看看第一個知識點:
1、修改圖片的透明度
/** * 修改圖片的argb值 * @param sourceImg * @param number * @return */ public static Bitmap getTransparentBitmap(Bitmap sourceImg, int number){ int[] argb = new int[sourceImg.getWidth() * sourceImg.getHeight()]; sourceImg.getPixels(argb, 0, sourceImg.getWidth(), 0, 0, sourceImg .getWidth(), sourceImg.getHeight());// 獲得圖片的ARGB值 number = number * 255 / 100; for (int i = 0; i < argb.length; i++) { argb[i] = (number << 24) | (argb[i] & 0x00FFFFFF); } sourceImg = Bitmap.createBitmap(argb, sourceImg.getWidth(), sourceImg .getHeight(), Bitmap.Config.ARGB_8888); return sourceImg; }
我們在onDraw方法繪制出修改之後的圖片:
BitmapDrawable drawable = (BitmapDrawable)ctx.getResources().getDrawable(R.drawable.cm_whatsapp_ico_audio); Bitmap bitmap = drawable.getBitmap(); //修改圖片的argb值的使用 bitmap = getTransparentBitmap(bitmap, 10); canvas.drawBitmap(bitmap, 0, 0, paint);代碼其實很簡單,就是拿到原圖Bitmap,然後在從新構造一張圖片,只是這時候我們會看到這裡的技術點:
首先我們可以獲取到一張圖片的ARGB數組值,數組大小就是圖片的像素值,那麼每個像素都是一個int類型值:
A代表透明度:8位
RGB代表三基色的顏色值:各8位
那麼加起來是32位,正好一個int類型長度。
這個也是在構造圖片的時候我們可以看到Bitmap.Config.ARGB_8888的含義,當然這個Config還有其他值,比如:
Bitmap.Config.ALPHA_8:只有透明度的值,8位,那麼我們可以使用byte類型就可以了。這張圖我們可以想象沒有顏色的圖片。
Bitmap.Config.ARGB_4444:和ARGB.8888類似,就是占用的位數不一樣,這裡是4*4=16位,用short類型即可,由此可見ARGB.8888雖然能夠顯示更高清的圖片,但是占用內存會高點。
Bitmap.Config.RGB_565:只有顏色值,沒有透明度,占用位數:5+6+5=16位,用short類型即可。
就是在構造一張Bitmap的時候,可以傳遞新的argb值了,那麼意味著我們可以隨意的改變圖片的透明度,顏色值啥的,我們看一下效果:
下面我們在隨便改一下他的顏色值:
for (int i = 0; i < argb.length; i++) { argb[i] = (number << 24) | (argb[i] & 0x00FFFFFF); argb[i] = 400 + (argb[i] & 0xFF00FFFF); }
看一下效果:
知識點:
1>、我們可以使用Bitmap的getPixel方法獲取圖片的像素值
2>、了解到了圖片像素值的表示含義和各個配置之間的差異
3>、修改圖片的透明度和顏色值
2、圖層的疊加效果(遮罩層)
這個知識點我們可能在前面已經介紹過了,比如如何制作圓角圖片,就是用遮罩層來做的,其實遮罩層說白了,就是兩張圖片之間如何進行疊加,類似於ppt中我們制作圖片的時候,可以選擇至於上層,至於底層等效果。但是這裡的遮罩層的效果會更多,下面來看一下代碼實現:
//獲得圓角圖片的方法 /** * android.graphics.PorterDuff.Mode.SRC:只繪制源圖像 android.graphics.PorterDuff.Mode.DST:只繪制目標圖像 android.graphics.PorterDuff.Mode.DST_OVER:在源圖像的頂部繪制目標圖像 android.graphics.PorterDuff.Mode.DST_IN:只在源圖像和目標圖像相交的地方繪制目標圖像 android.graphics.PorterDuff.Mode.DST_OUT:只在源圖像和目標圖像不相交的地方繪制目標圖像 android.graphics.PorterDuff.Mode.DST_ATOP:在源圖像和目標圖像相交的地方繪制目標圖像,在不相交的地方繪制源圖像 android.graphics.PorterDuff.Mode.SRC_OVER:在目標圖像的頂部繪制源圖像 android.graphics.PorterDuff.Mode.SRC_IN:只在源圖像和目標圖像相交的地方繪制源圖像 android.graphics.PorterDuff.Mode.SRC_OUT:只在源圖像和目標圖像不相交的地方繪制源圖像 android.graphics.PorterDuff.Mode.SRC_ATOP:在源圖像和目標圖像相交的地方繪制源圖像,在不相交的地方繪制目標圖像 android.graphics.PorterDuff.Mode.XOR:在源圖像和目標圖像重疊之外的任何地方繪制他們,而在不重疊的地方不繪制任何內容 android.graphics.PorterDuff.Mode.LIGHTEN:獲得每個位置上兩幅圖像中最亮的像素並顯示 android.graphics.PorterDuff.Mode.DARKEN:獲得每個位置上兩幅圖像中最暗的像素並顯示 android.graphics.PorterDuff.Mode.MULTIPLY:將每個位置的兩個像素相乘,除以255,然後使用該值創建一個新的像素進行顯示。結果顏色=頂部顏色*底部顏色/255 android.graphics.PorterDuff.Mode.SCREEN:反轉每個顏色,執行相同的操作(將他們相乘並除以255),然後再次反轉。結果顏色=255-(((255-頂部顏色)*(255-底部顏色))/255) * @param bitmap * @param roundPx * @return */ public static Bitmap getRoundedCornerBitmap(Bitmap bitmap,float roundPx){ Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap .getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); final RectF rectF = new RectF(rect); paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); canvas.drawRoundRect(rectF, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); canvas.drawBitmap(bitmap, rect, rect, paint); return output; }這裡我們看到,我們首先可以創造一個畫布,但是畫布不能為空,需要一個打底圖,我們直接使用配置Bitmap.Config.ARGB_8888配置創造一個和原圖一樣大小的白色圖片,當然我們通過上面說的方法,可以創造一個有透明度和顏色值的圖片,然後貼在畫布上。這裡我們看到也可以直接使用畫布來設置ARGB的值,這裡全是0,這時候我們可以作畫了,我們繪制一個圓角矩形,繪制完成之後,我們需要設置畫筆的遮罩層效果,這裡是關鍵點,關於遮罩層有很多種選項:
android.graphics.PorterDuff.Mode.SRC:只繪制源圖像 android.graphics.PorterDuff.Mode.DST:只繪制目標圖像 android.graphics.PorterDuff.Mode.DST_OVER:在源圖像的頂部繪制目標圖像 android.graphics.PorterDuff.Mode.DST_IN:只在源圖像和目標圖像相交的地方繪制目標圖像 android.graphics.PorterDuff.Mode.DST_OUT:只在源圖像和目標圖像不相交的地方繪制目標圖像 android.graphics.PorterDuff.Mode.DST_ATOP:在源圖像和目標圖像相交的地方繪制目標圖像,在不相交的地方繪制源圖像 android.graphics.PorterDuff.Mode.SRC_OVER:在目標圖像的頂部繪制源圖像 android.graphics.PorterDuff.Mode.SRC_IN:只在源圖像和目標圖像相交的地方繪制源圖像 android.graphics.PorterDuff.Mode.SRC_OUT:只在源圖像和目標圖像不相交的地方繪制源圖像 android.graphics.PorterDuff.Mode.SRC_ATOP:在源圖像和目標圖像相交的地方繪制源圖像,在不相交的地方繪制目標圖像 android.graphics.PorterDuff.Mode.XOR:在源圖像和目標圖像重疊之外的任何地方繪制他們,而在不重疊的地方不繪制任何內容 android.graphics.PorterDuff.Mode.LIGHTEN:獲得每個位置上兩幅圖像中最亮的像素並顯示 android.graphics.PorterDuff.Mode.DARKEN:獲得每個位置上兩幅圖像中最暗的像素並顯示 android.graphics.PorterDuff.Mode.MULTIPLY:將每個位置的兩個像素相乘,除以255,然後使用該值創建一個新的像素進行顯示。結果顏色=頂部顏色*底部顏色/255 android.graphics.PorterDuff.Mode.SCREEN:反轉每個顏色,執行相同的操作(將他們相乘並除以255),然後再次反轉。結果顏色=255-(((255-頂部顏色)*(255-底部顏色))/255)
這些選項很全的,可以實現兩張圖片之間疊加的任何效果了。設置完打底圖片的畫筆遮罩層之後,那麼就來繪制第二張圖片,也就是原始圖片,繪制完成之後,直接返回打底圖片即可。
看一下效果:
看到了,這裡我們選擇的遮罩層選項是:Mode.XOR。就是智慧值他們兩交集以外的地方,挺有意思的吧,下面我們在用Mode.SCR_IN來看看,和Mode.XOR相反,只繪制兩張圖片相交的地方:
好吧,這就實現了圖片的圓角效果了。哈哈哈,當然還有其他遮罩的效果,大家都可以去嘗試一下,也很好玩的。
知識點:
1>、手動創建一個Bitmap和Canvas
2>、了解遮罩層技術改變兩張圖片的疊加效果
3、畫布的保存和恢復操作
這個技術,我們在自定義View的時候,用到的最多的地方,也是非常重要的一個點,他說白了,就是先在畫布上繪制一個圖形,然後保存一下,在繪制一個圖形,然後在恢復一下,這樣就可以在一個畫布上繪制出不同的樣式了,下面我們來看一下代碼:
/** * 保留圖層技術 * @param canvas */ public static void saveCanvas(Canvas canvas){ //首先繪制一個線條 Paint paint = new Paint(); paint.setColor(Color.BLUE); paint.setStrokeWidth(10); canvas.drawLine(0,0, 100,100,paint); //保存畫布,同時設置區域l,t,r,b canvas.saveLayerAlpha(10, 10, 300, 300, 0x88, Canvas.ALL_SAVE_FLAG); //繪制圓圈 Paint paint1 = new Paint(); paint1.setColor(Color.RED); paint1.setStrokeWidth(10); paint1.setStyle(Paint.Style.FILL); canvas.drawCircle(140, 140, 100, paint1); canvas.restore(); }我們看到首先,我們繪制出一個藍色的線條,然後我們保存畫布狀態,我們在保存的時候,同時設置一下畫布的區域和透明度,然後在繪制一個紅色的圓圈,最後恢復畫布,我們看一下效果:
看到了,首先紅色的圓圈和藍色的線條重疊的地方,紅色圖片在上面,其次是我們看到紅色的圓圈有透明度的,不是全紅的。這樣我們就學會了保存畫布和恢復畫布狀態操作,但是在實際的過程中我們不可能這麼簡單的,我們改一下代碼,讓畫布平移一下:
這裡我們使用矩陣來操作一下畫布平移,然後我們看一下效果:
好了,看到了嗎?圓圈移動了,其實實際上不是圓圈移動了,是畫布移動了,所以要理解好畫布的保存和恢復的真正含義:
每個畫板Canvas創建的時候都有一個默認的圖層,我們在使用save方法的時候,會在創建一個圖層,然後繪制,在調用restore方法的時候,相當於兩個圖層的疊加操作,所以看上去是畫布移動了,其實不是的,是新建了一個圖層,畫布不可能移動的。在說的通俗點就是:畫布相當於畫板,圖層相當於畫紙。
當然這裡我們還看到了圓圈沒顯示全,這個就是我們上面在保存畫布的時候,設置了區域,其實這個區域和透明度就是新建圖層的大小和透明度,這個圓圈是繪制在新建的圖層上的所以會被裁剪了。
還有一個地方需要注意的是:
我們每次如果要操作畫布的時候,一定要記住一個准則:
先操作畫布,在繪制圖形
如果順序反了,那麼操作是沒有效果的,比如上面的平移效果如果放到繪制圓圈的後面,那麼就沒有任何效果的。
知識點:
1>、使用畫布保存技術來繪制想要的特殊效果
2>、理解了畫布的平移,縮放等效果不是真正意義上的操作畫布,而是新建一個圖層,然後進行圖層的疊加操作。
3>、使用矩陣來操作畫布
4、使用矩陣實現平移,旋轉,縮放效果
我們在實現View的簡單的平移,旋轉,縮放等功能的時候,Android中提供了很多種選擇,可以使用傳統動畫,屬性動畫等。但是這裡我們今天來看一下,在繪制圖片的時候我們使用矩陣來實現這些效果,說道矩陣,在學習線性數學的時候,我們知道兩個矩陣相乘能夠實現各種變化效果,同時A*B=C,A矩陣我們理解為變化前的矩陣,B矩陣理解為變化矩陣,C矩陣理解為變化後的矩陣。當然Android中操作舉證的Api不需要我們去手動的計算,而是幫我們已經封裝了平移,旋轉,縮放這三種功能的效果矩陣,同樣我們也可以實現矩陣的手動計算,但是這裡就不介紹了,因為矩陣操作還是很復雜的,感興趣的同學可以去網上搜一下。下面我們來看一下代碼:
/** * 使用Matrix來實現旋轉圖片 * @param bitmap * @return */ public static Bitmap operateBitmap(Bitmap bitmap){ Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap .getHeight(), Bitmap.Config.RGB_565); Canvas canvas = new Canvas(output); Matrix matrix = new Matrix(); matrix.postRotate(300); matrix.postScale(0.4f, 0.5f); matrix.postTranslate(100, 100); canvas.drawBitmap(bitmap, matrix, new Paint()); return output; }這裡我們依然是,先創建一個畫布和打底圖,然後就用矩陣來操作圖片了,這裡先旋轉,縮放,平移,然後我們看一下效果:
看到了,圖片變成這個樣子了,但是這裡我們看到怎麼有一塊是黑色的,原因是我們在創建打底圖的時候設置的圖片配置是:
Bitmap.Config.RGB_565
這個樣式是沒有透明度的,默認是黑色的。
關於Matrix矩陣操作有一個前乘和後乘的區別,因為矩陣的乘法是不支持交換律的,post開頭的方法都是後乘,就是我們正常的操作效果,其中pre開頭的方法是前乘,改一下代碼:
看看效果:
我擦,不知道變成什麼鬼了,我這裡為什麼要提一下前乘和後乘的區別呢?其實想說明一個問題就是:
一定不要把前乘和後乘理解為是相反的操作
如果我們想操作相反的動作,只需要把數值寫成負數值即可。
知識點:
1>、學習到了矩陣操作
2>、理解了矩陣的前乘和後乘的區別
5、通過旋轉,平移,縮放畫布來實現動畫
關於這個知識點,我們在前面自定義視圖View的基礎篇說到了。但是這裡我們來使用選擇畫布的方式來實現動畫效果,下面來看一下代碼:
/** * 通過旋轉畫布來實現圖片的旋轉 * @param canvas * @param bitmap * @param argee */ public void recyleRotateBitmap(Canvas canvas, Bitmap bitmap, int argee){ //這裡需要注意的是一定是先操作完畫布,才能繪制,不然沒效果的 canvas.rotate(argee); Rect rect1 = new Rect(0,0,bitmap.getWidth(),bitmap.getWidth()); Rect rect2 = new Rect(0,0,bitmap.getWidth(),bitmap.getHeight()); canvas.drawBitmap(bitmap, rect1, rect2, new Paint()); postInvalidateDelayed(200); }這裡我們可以看到,Canvas本身也有平移,旋轉,縮放的方法,可以不需要矩陣就可以實現,這裡我們實現的原理很簡單:就是在onDraw方法中來改變旋轉角度,然後從新繪制圖片,然後調用postInvalidateDelayed方法來實現畫布的內容更新操作,在onDraw方法中我們的代碼:
我們看一下效果:
就是圖片沿著左上角的點開始旋轉。
知識點:
1>、使用Canvas本身的api來操作畫布
6、裁剪畫布
有時候我們可能需要裁剪畫布,就是把畫布上已經畫好的圖形,我們只想要其中的一部分,那麼這時候我們可以使用裁剪技術來實現,比如我們現在想把圖片裁剪成不規則形狀,我們看一下代碼:
/** * 裁剪畫布 * @param canvas * @param bitmap */ public static void clipCanvas(Canvas canvas, Bitmap bitmap){ canvas.drawColor(Color.GRAY); //先裁去完畫布,在繪制內容 //canvas.clipRect(0, 0, 200, 200); Path path = new Path(); path.lineTo(0, 0); path.lineTo(400, 0); path.lineTo(100, 300); path.lineTo(0, 0); canvas.clipPath(path); Rect rect1 = new Rect(0,0,bitmap.getWidth(),bitmap.getWidth()); Rect rect2 = new Rect(0,0,bitmap.getWidth(),bitmap.getHeight()); canvas.drawBitmap(bitmap, rect1, rect2, new Paint()); }這裡我們繪制不規則形狀使用Path來實現的,這裡有一點需要注意的是,裁剪畫布也算是操作畫布了,所以還是要遵從一個准則:
先操作畫布,在繪制圖形
我們可以看一下效果:
擦,被裁成這個吊樣了,我們這裡還看到了,可以使用path來實現繪制不規則圖形的,而且裁剪的api中也支持規則的圖形裁剪,比如是:圓形,矩形等裁剪形狀。
知識點:
1>、使用Path來繪制不規則圖形
2>、隨意裁剪圖片的形狀
到這裡我們就介紹完了關於畫布Canvas的一些操作,這些操作可以滿足我們在日常中想要實現的效果了,下面我們就再來整理我們所學習到的知識點:
1> 我們可以使用Bitmap的getPixel方法獲取圖片的像素值
2> 了解到了圖片像素值的表示含義和各個配置之間的差異
3> 修改圖片的透明度和顏色值
4> 手動創建一個Bitmap和Canvas
5> 了解遮罩層技術改變兩張圖片的疊加效果
6> 使用畫布保存技術來繪制想要的特殊效果
7> 理解了畫布的平移,縮放等效果不是真正意義上的操作畫布,而是新建一個圖層,然後進行圖層的疊加操作。
8> 使用矩陣來操作畫布
9> 學習到了矩陣操作
10> 理解了矩陣的前乘和後乘的區別
11> 使用Canvas本身的api來操作畫布
12> 使用Path來繪制不規則圖形
13> 隨意裁剪圖片的形狀
我們這篇文章主要介紹了如何操作畫布來實現我們在自定義視圖View的時候想要的效果,這些api我們可以不用記住,但是這些知識點可以記住,因為我們在看到一種UI效果的時候我們可以想到有這些功能就可以了,沒事多看看一些UI上的效果和特效,多想想可以怎麼去實現它,這樣對於我們繪制UI很有幫助,沒事的時候腦補一下還是很有用途的。
最近項目裡面需要支付功能,boos一致決定用微信支付,所以在網上查了很多資料,說的不全,完了就找以前的同事指教。算是成功集成上去了。在這裡做個總結記錄。1、在APP上集成
在PC上我們已經習慣了樹形控件,因為其可以清晰的展現各個節點之間的層次結果,但是在Android平台上,系統並沒有提供這樣一個控件,而是只有ListView。不過通過改寫
水波紋效果已經不是什麼稀罕的東西了,用過5.0新控件的小伙伴都知道這個效果,可是如果使用一個TextView或者Button或者其它普通控件的話,你是否知道如何給它設置水
實現功能:實現NetMusicListAdapter(網絡音樂列表適配器)實現SearchResult(搜索音樂對象)使用Jsoup組件請求網絡,並解析音樂數據,並,音樂