Android-繪圖機制總結
前言
早在今年年初就發過關於Android繪圖機制的博文,只不過不是在簡書,是在CSDN。而且已經過去很久了有的地方也忘記了,最重要的是當時剛剛開始寫部落格,很多地方不規範,自己查閱起來也比較麻煩,所以重新在簡書上整理一篇繪圖機制的總結,內容是整合了android群英傳和android開發藝術探索兩本書中的內容。覺得不錯的話可以點選一個“喜歡”。
這裡你將瞭解到以下內容:
- Android螢幕相關知識
- Android繪圖技巧
- Android影象處理技巧
- SurfaceView的使用
1、螢幕尺寸資訊
1.螢幕引數
一塊螢幕通常具備以下的幾個引數
螢幕大小:
指螢幕對角線的長度,通常用寸來表示,例如4.7寸,5.5寸
解析度:
解析度是指實際螢幕的畫素點個數,例如720X1280就是指螢幕的解析度,寬有720個畫素點,高有1280個畫素點
PPI
每英寸畫素又稱為DPI,他是由對角線的的畫素點數除以螢幕的大小所得,通常有400PPI就已經很6了
2.系統螢幕密度
3.獨立畫素密度dp
這是由於各種螢幕密度的不同,導致同樣畫素大小的長度,在不同密度的螢幕上顯示長度不同,因此相同長度的螢幕,高密度的螢幕包含更多的畫素點,在安卓系統中使用mdpi密度值為160的螢幕作為標準,在這個螢幕上,1px = 1dp,其他螢幕則可以通過比例進行換算,例如同樣是100dp的長度,mdpi中為100px,而在hdpi中為150,我們也可以得出在各個密度值中的換算公式,在mdpi中 1dp = 1px, 在hdpi中, 1dp = 1.5px,在xhdpi中,1dp = 2px,在xxhdpi中1dp = 3px,由此可見,我們換算公式l:m:h:xh:xxh = 3:4:6:8:12
4.換算公式
這裡就不貼出來了,點選跳轉到我歷史部落格即可;
系統提供TypedValue幫助我們轉換
/**
* dp2px
* @param dp
* @return
*/
protected int dp2px(int dp){
return (int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResources()
.getDisplayMetrics());
}
/**
* sp2px
* @param dp
* @return
*/
protected int sp2px(int sp){
return (int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp
,getResources().getDisplayMetrics());
}
二、繪圖基礎
1.Paint
作為一個非常重要的元素,功能也是很強大的以下是一些基礎的屬性和對應的功能
- setAntiAlias(); //設定畫筆的鋸齒效果
- setColor(); //設定畫筆的顏色
- setARGB(); //設定畫筆的A、R、G、B值
- setAlpha(); //設定畫筆的Alpha值
- setTextSize(); //設定字型的尺寸
- setStyle(); //設定畫筆的風格(空心或實心)
- setStrokeWidth(); //設定空心邊框的寬度
- getColor(); //獲取畫筆的顏色
接下來實現一個Demo
程式碼:
paint1 = new Paint();
paint1.setColor(Color.BLUE);
paint1.setAntiAlias(true);
//空心
paint1.setStyle(Paint.Style.STROKE);
paint2 = new Paint();
paint2.setColor(Color.GREEN);
//實心
paint2.setStyle(Paint.Style.FILL);
效果圖:
2.Canvas
四種常用的方法
canvas.save(); //儲存畫布
canvas.restore(); //合併圖層
canvas.translate(x,y); //畫布平移到x,y處
canvas.rotate(翻轉的角度,圓心的X座標,圓心的Y座標);//畫布翻轉
DrawPoint 繪製點
canvas.drawPoint(x,y,paint);
DrawLine繪製直線
canvas.drawLine(startX,startY,endX,endY,paint);
繪製多條直線:
float[] pts1={startX1,startY1,endX1,endY1...startXn,startYnendXn,endYn};
canvas.drawLines(pts,paint);
DrawRect繪製矩形
canvas.drawRect(left,top,right,button,paint);
DrawRoundRect繪製圓角矩形
canvas.drawRect(left,top,right,button,radiusX,radiusY,paint);
DrawCircle繪製圓
canvas.drawCircle(圓心X座標,Y座標,半徑,paint1);
DrawArc繪製弧形/扇形
兩種寫法:
第一種:canvas.drawArc(left,top,right,button, startAngle, sweepAngle, useCenter, paint2);
第二種:
RectF rectF = new RectF(left,top,right,button);
canvas.drawArc(rectF, 0, 100, true, paint2);
在這裡rectF代表的是圓弧外輪擴矩形區域
startAngle表示起始的角度 (以X軸正方向為0度順時針開始算)
sweepAngle為圓弧掃過的角度
useCenter設定為true的時候顯示圓心
DrawPath繪製路徑
繪製多邊形
//例項化路徑
Path path = new Path();
path.moveTo(80, 200);// 此點為多邊形的起點
path.lineTo(120, 250);
path.lineTo(80, 250);
path.lineTo(300, 300);
//path.close(); // 使這些點構成封閉的多邊形
canvas.drawPath(path, paint1);
繪製曲線:
Path path=new Path();
path.moveTo(100, 320); //設定Path的起點
path.quadTo(160,320,180,410); //設定路徑點和終點
canvas.drawPath(path, paint1);
DrawOval繪製橢圓
DrawOval(left,top,right,button,paint);
RectF rectF = new RectF(210,100,250,130) ;
canvas.drawOval(rectF, paint2) ;
DrawText繪製文字
canvas.drawText("自定義文字",X座標,Y座標,paint);
繪製不同位置的文字資訊
float [] pts=new float[]{100,100,200,200,300,300,400,400,500,500,600,600};
canvas.drawPosText("text",pts,paint1);
效果圖:
三、XML繪圖
1、Bitmap
在XML中使用Bitmap很簡單
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/ic_launcher">
</bitmap>
通過這樣引用圖片就可以將圖片直接轉化成Bitmap讓我們在程式中使用
2、Shape
通過Shape可以繪製各種圖形,下面展示一下shape的引數
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!--預設是rectangle-->
<!--當shape= rectangle的時候使用-->
<corners
android:bottomLeftRadius="1dp"
android:bottomRightRadius="1dp"
android:radius="1dp"
android:topLeftRadius="1dp"
android:topRightRadius="1dp" />
<!--半徑,會被後面的單個半徑屬性覆蓋,預設是1dp-->
<!--漸變-->
<gradient
android:angle="1dp"
android:centerColor="@color/colorAccent"
android:centerX="1dp"
android:centerY="1dp"
android:gradientRadius="1dp"
android:startColor="@color/colorAccent"
android:type="linear"
android:useLevel="true" />
<!--內間距-->
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<!--大小,主要用於imageview用於scaletype-->
<size
android:width="1dp"
android:height="1dp" />
<!--填充顏色-->
<solid android:color="@color/colorAccent" />
<!--指定邊框-->
<stroke
android:width="1dp"
android:color="@color/colorAccent" />
<!--虛線寬度-->
android:dashWidth= "1dp"
<!--虛線間隔寬度-->
android:dashGap= "1dp"
</shape>
3.Layer
Layer是在PhotoShop中是非常常用的功能,在Android中,我們同樣可以實現圖層的效果
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--圖片1-->
<item android:drawable="@mipmap/ic_launcher"/>
<!--圖片2-->
<item
android:bottom="10dp"
android:top="10dp"
android:right="10dp"
android:left="10dp"
android:drawable="@mipmap/ic_launcher"
/>
</layer-list>
4.Selector
Selector的作用是幫助開發者實現靜態View的反饋,通過設定不同的屬性呈現不同的效果
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 預設時候的背景-->
<item android:drawable="@mipmap/ic_launcher" />
<!-- 沒有焦點時候的背景-->
<item android:drawable="@mipmap/ic_launcher" android:state_window_focused="false" />
<!-- 非觸控模式下獲得焦點並點選時的背景圖片-->
<item android:drawable="@mipmap/ic_launcher" android:state_pressed="true" android:state_window_focused="true" />
<!-- 觸控模式下獲得焦點並點選時的背景圖片-->
<item android:drawable="@mipmap/ic_launcher" android:state_focused="false" android:state_pressed="true" />
<!--選中時的圖片背景-->
<item android:drawable="@mipmap/ic_launcher" android:state_selected="true" />
<!--獲得焦點時的圖片背景-->
<item android:drawable="@mipmap/ic_launcher" android:state_focused="true" />
</selector>
這一方法可以幫助開發者迅速製作View的反饋,通過配置不同的觸發事件,selector會自動選中不同的圖片,特別是自定義button的時候,而我們不再使用原生單調的背景,而是使用selector特別製作的背景,就能完美實現觸控反饋了
通常情況下,上面提到的這些方法都可以共同實現,下面這個例子就展示了在一個selector中使用shape作為他的item的例子,實現一個具體點選反饋效果的,圓角矩形的selector,程式碼如下
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<!--填充顏色-->
<solid android:color="#33444444" />
<!--設定按鈕的四個角為弧形-->
<corners android:radius="5dp" />
<!--間距-->
<padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<!--填充顏色-->
<solid android:color="#FFFFFF" />
<!--設定按鈕的四個角為弧形-->
<corners android:radius="5dp" />
<!--間距-->
<padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" />
</shape>
</item>
</selector>
四、Android繪圖技巧
在學完Android的基本繪圖之後我們來講解一下常用的繪圖技巧
1、Canvas
- Canvas.save()
- Canvas.restore()
- Canvas.translate()
- Canvas.roate()
首先,我們來看一下前面兩個方法
在講解這兩個方法之前,首先來了解一下Android繪圖的座標體系,這個其實這個前面已經講了,這裡不贅述,而Canvas.save()這個方法,從字面上的意思可以理解為儲存畫布,他的作用就是講之前的影象儲存起來,讓後續的操作能像在新的畫布一樣操作,這跟PS的圖層基本差不多
而Canvas.restore()這個方法,則可以理解為合併圖層,,就是講之前儲存下來的東西合併.
8
而後面兩個方法尼?從字母上理解畫布平移或者旋轉,但是把他理解為座標旋轉更加形象,前面說了,我們繪製的時候預設座標點事左上角的起始點,那麼我們呼叫translate(x,y)之後,則將原點(0,0)移動到(x,y)之後的所有繪圖都是在這一點上執行的
2.Layer圖層
Android中的繪圖API,很大程度上都來自繪圖的API,特別是借鑑了很多PS的原理,比如圖層的概念,相信看過圖層的也都知道是個什麼樣的,我們畫個圖來分析一下
Android通過saveLayer()方法,saveLayerAlpha()將一個圖層入棧,使用restore()方法,restoreToCount()方法將一個圖層出棧,入棧的時候,後面的所有才做都是發生在這個圖層上的,而出棧的時候,則會把圖層繪製在上層Canvas上,我們仿照API Demo來
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
mPaint.setColor(Color.BLUE);
canvas.drawCircle(150, 150, 100, mPaint);
canvas.saveLayerAlpha(0, 0,400,400,127,LAYER_TYPE_NONE);
mPaint.setColor(Color.RED);
canvas.drawCircle(200, 200, 100, mPaint);
canvas.restore();
}
當繪製兩個相交的圓時,就是圖層
接下來將圖層後面的透明度設定成0-255不同值
我們分別演示 127 255 0三個
五.Android影象處理之畫筆特效處理
不管是在我們的世界裡,還是在Android的世界裡,要想畫出好的圖片,就必須賬務畫筆的特效,今天我們就來玩玩這個Paint的特殊Get
1.PorterDuffXfermode
途中列舉了16種PorterDuffXfermode,有點像數學中的集合,主要是一個混合顯示模式
這裡注意了,PorterDuffXfermode設定兩個圖層交集區域的顯示方法des是先畫的影象,src是後畫的
當然,這些模式也不是經常的用到,用到最多的事,使用一張圖片作為另一張圖片的遮罩,通過控制遮罩層的圖形,來控制下面被遮罩的顯示效果,其中最常用的就是通過DST_IN.SRC_IN模式來實現將一個矩形變成圓角圖片的效果,我們這裡來實現一下
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.nice);
mOut = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mOut);
mPaint = new Paint();
mPaint.setAntiAlias(true);
canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), 80, 80, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mBitmap,0,0,mPaint);
下面我們來做一個比較類似於刮刮卡的效果,其實效果就是兩張圖片,上面那張一刮就顯示下面的那張看,這個效果同樣我們可以使用PorterDuffXfermode去實現,我們首先要做的就是初始化一些資料
public class PlayView extends View {
private Bitmap mBgBitmap, mFgBitmap;
private Paint mPaint;
private Canvas mCanvas;
private Path mPath;
/**
* 構造方法
*
* @param context
* @param attrs
*/
public PlayView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* 初始化
*/
private void init() {
mPaint = new Paint();
mPaint.setAlpha(0);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);//讓筆更加圓滑一些
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.ROUND);//讓筆更加圓滑一些
mPath = new Path();
mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.nice);
mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mFgBitmap);
mCanvas.drawColor(Color.GRAY);
}
/**
* 觸控事件
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_UP:
mPath.lineTo(event.getX(), event.getY());
break;
}
mCanvas.drawPath(mPath, mPaint);
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBgBitmap, 0, 0, null);
canvas.drawBitmap(mFgBitmap, 0, 0, null);
}
}
2,Shader
Shader又被稱為著色器。渲染器,它可以實現渲染,漸變等效果,Android中的Shader包括以下幾種
BitmapShader:點陣圖Shader
LinearGradient:線性Shader
RadialGradient:光束Shader
SweepGradient:梯形Shader
ComposeShader:混合Shader
除了第一個之外,其他的都是比較正常的,實現了名副其實的漸變,渲染效果,而與其他Shader的效果不同的是,BitmapShader所產生的是一個影象,有點類似PS裡面的影象填充,他的作用是通過Paint對畫布進行制定bitmap的填充,填充式有一下幾個模式供選擇。
- CLAMP拉伸——拉伸的是圖片最後的哪一個畫素,不斷重複
- REPEAT重複——橫向,縱向不斷重複
- MIRROR映象——橫向不斷翻轉重複,縱向不斷翻轉重複
這幾種模式的含義都非常好理解的,與字面意識基本相同,這裡最常用的是CLAMP拉伸模式,雖然他會拉伸最後一個畫素,但是隻要將突破設定成一個固定的畫素,是可以避免的,
六.View之孿生兄弟---SurfaceView
Android系統提供了VieW進行繪圖處理, vieW可以滿足大部分的繪圖需求,但在某些時卻也有些心有餘而力不足,特別是在進行一些開發的時候。 我們知道,VieW通過重新整理來檢視, Android系統通過發出VSYNC訊號來進行螢幕的重繪, 重新整理的間隔時間為I6ms。在16ms內View完成了你所需要執行的所有操作,那麼使用者在視覺上, 就不會產生卡頓的感覺:而如果執行的操作邏輯太多,特別是需要頻繁重新整理的介面上,例如遊戲介面,那麼就塞主執行緒,從而導致畫面卡頓,很多時候,在自定義VieW的Log中經常會看見如下示的警告
Skipped 47 frames! The application may be doing too much work on its main thread
這些警告的產生,很多情況下就是因為在繪製過程中, 處理邏輯太多造成的為了避免這一問題的產生一 Android系統提供了surfacevicW元件來解決這個問題,可以說是VieW的孿生兄弟,但它與View還是有所不同的,它們的區別主要體現
- View主要用於自動更新的情況下,而surfaceVicw主要適用於被動更新,例如頻繁重新整理
- View在主執行緒中重新整理,而surfaceView通常會通過一 個子執行緒來進行頁面重新整理。
- View在繪製的時候沒有雙緩衝機制,而surfaceVicw在底層實現機制中就已經實現了雙緩衝機制;
總結起來,如果你的自定義View需要頻繁重新整理,或者重新整理時資料處理量比較大,那麼你就可以考慮使用surfaceVicw取代View了
1.surfaceView的使用
一套模板程式碼:
public class SurfaView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
//SurfaceHolder
private SurfaceHolder mHolder;
//用於繪製的Canvas
private Canvas mCanvas;
//子執行緒標誌位
private boolean mIsDrawing;
/**
* 構造方法
*
* @param context
* @param attrs
*/
public SurfaView(Context context, AttributeSet attrs) {
super(context, attrs);
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}
@Override
public void run() {
while (mIsDrawing) {
draw();
//在這裡不斷繪製要繪製的內容
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
//這裡要不斷地用mCanvas方法繪製
} catch (Exception e) {
} finally {
if (mCanvas != null) {
//提交
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
注意
@Override
public void run() {
long start = System.currentTimeMillis();
while (mIsDrawing) {
draw();
}
long end = System.currentTimeMillis();
// 50 - 100
if (end - start < 100) {
try {
Thread.sleep(100 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
由於不斷的呼叫draw()來進行繪製,有時候也不用那麼頻繁。可以設定Sleep操作。儘可能的省資源
100ms是一個經驗值,通常可以設定成50-100ms.
相關文章
- Android中View的量算、佈局及繪圖機制AndroidView繪圖
- slub機制偽總結
- java機制總結 一Java
- mysql鎖機制總結MySql
- android-錯誤提示說明彙總Android
- 類載入機制總結
- Spring SPI 機制總結Spring
- 二進位制小總結
- slab機制總結篇
- Javascript繼承機制總結JavaScript繼承
- Java類載入機制總結Java
- PHP 的錯誤機制總結PHP
- PHP的錯誤機制總結PHP
- JVM之類載入機制總結JVM
- JS的執行機制的總結!JS
- kubernetes驅逐機制總結
- C#反射機制學習總結C#反射
- Oracle的鎖機制歸納總結Oracle
- mysql 二進位制日誌總結MySql
- 【執行機制】 JavaScript的事件迴圈機制總結 eventLoopJavaScript事件OOP
- Java反射機制開發經驗總結Java反射
- 響應式程式設計機制總結程式設計
- mysql鎖機制總結,以及優化建議MySql優化
- 響應者鏈及相關機制總結
- 二級指標做形參機制總結指標
- 轉貼:Oracle的鎖機制歸納總結Oracle
- PHP-FPM 與 Nginx 的通訊機制總結PHPNginx
- 你需要理解的 Java 反射機制知識總結Java反射
- Android-認識BitmapAndroid
- Android-使用FindBugsAndroid
- JS基礎總結(5)—— JS執行機制與EventLoopJSOOP
- Android自定義View之事件分發機制總結AndroidView事件
- Redis中的事務處理機制分析與總結Redis
- 電商系統架構總結3(webapi授權機制)架構WebAPI
- C++異常處理機制核心觀點總結C++
- Android-藍芽聊天demoAndroid藍芽
- Android-返回桌面?退出程式?Android
- Android-實現Animation everywhereAndroid