第三章 Android繪圖機制與處理技巧


1.屏幕尺寸信息

屏幕大小:屏幕對角線長度,單位“寸”;
分辨率:手機屏幕像素點個數,例如720x1280分辨率;
PPI(Pixels Per Inch):即DPI(Dots Per Inch),它是對角線的像素點數除以屏幕大小得到的;
系統屏幕密度:android系統定義了幾個標准的DPI值作為手機的固定DPI。(注,最后一個有筆誤,正確的是1080x1920)
img
獨立像素密度(DP):android系統使用mdpi屏幕作為標准,在這個屏幕上1dp=1px,其他屏幕可以通過比例進行換算。在hdpi中,1dp=1.5px。在xhdpi中,1dp=2px。在xxhdpi中,1dp=3px。
單位轉換:常用的單位轉換的輔助類DisplayUtil

/**
* 常用單位轉換的輔助類
*/
public class DisplayUtil {

/**
* dp轉px
*/
public static int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}

/**
* sp轉px
*/
public static int sp2px(Context context, float spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, context.getResources().getDisplayMetrics());
}

/**
* px轉dp
*/
public static float px2dp(Context context, float pxVal) {
final float scale = context.getResources().getDisplayMetrics().density;
return (pxVal / scale);
}

/**
* px轉sp
*/
public static float px2sp(Context context, float pxVal) {
return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);
}
}

 

2.2D繪圖基礎
(1)Canvas對象
drawPointdrawLinedrawRectdrawRoundRectdrawCircledrawArcdrawOvaldrawTextdrawPosText(在指定位置繪制文本),drawPath(繪制路徑)

(2)Paint對象
setAntiAlias:設置畫筆的鋸齒效果
setColor:設置畫筆的顏色
setARGB:設置畫筆的A、R、G、B值
setAlpha:設置畫筆的透明度值
setTextSize:設置字體大小
setStyle:設置畫筆的效果(空心STROKE或者實心FILL)
setStrokeWidth:設置空心邊框的寬度

3.Android XML繪圖
(1)Bitmap
在XML中定義Bitmap的語法

<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@[package:]drawable/drawable_resource"
android:antialias=["true" | "false"]
android:dither=["true" | "false"]
android:filter=["true" | "false"]
android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"]
android:tileMode=["disabled" | "clamp" | "repeat" | "mirror"] />

 

(2)Shape
在XML中定義Shape的語法

<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape=["rectangle" | "oval" | "line" | "ring"] >
<corners //當shape為rectangle時使用
android:radius="integer" //半徑值會被后面的單個半徑屬性覆蓋,默認為1dp
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
<gradient //漸變
android:angle="integer"
android:centerX="integer"
android:centerY="integer"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:useLevel=["true" | "false"] />
<padding //內邊距
android:left="integer"
android:top="integer"
android:right="integer"
android:bottom="integer" />
<size //指定大小,一般用在imageview配合scaleType屬性使用
android:width="integer"
android:height="integer" />
<solid //填充顏色
android:color="color" />
<stroke //邊框
android:width="integer"
android:color="color"
android:dashWidth="integer"
android:dashGap="integer" />
</shape>

 

(3)Layer
在XML中定義Layer的語法,layer類似PS中圖層的概念,語法如下

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@[package:]drawable/drawable_resource" />
<item android:drawable="@[package:]drawable/drawable_resource" />
......
</layer-list>

 

(4)Selector
selector的用法很多,一般是定義控件在不同狀態下的顯示形態,可以是圖片drawable,也可以是形狀shape,還可以只是顏色color!

<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 默認時的背景圖片-->
<item android:drawable="@drawable/pic1"/>
<!-- 沒有焦點時的背景圖片 -->
<item android:drawable="@drawable/pic1" android:state_window_focused="false"/>
<!-- 非觸摸模式下獲得焦點並單擊時的背景圖片 -->
<item android:drawable="@drawable/pic2" android:state_focused="true" android:state_pressed="true"/>
<!-- 觸摸模式下單擊時的背景圖片-->
<item android:drawable="@drawable/pic3" android:state_focused="false" android:state_pressed="true"/>
<!--選中時的圖片背景-->
<item android:drawable="@drawable/pic4" android:state_selected="true"/>
<!--獲得焦點時的圖片背景-->
<item android:drawable="@drawable/pic5" android:state_focused="true"/>
</selector>

 

selector與shape結合的例子

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定義當button處於pressed狀態時的狀態-->
<item android:state_pressed="true">
<shape>
<gradient android:startColor="#8600ff"/>
<stroke android:width="2dp" android:color="#000000"/>
<corners android:radius="5dp"/>
<padding android:bottom="10dp" android:left="10dp"
android:right="10dp" android:top="10dp"/>
</shape>
</item>
<!-- 定義當button獲得焦點時的狀態-->
<item android:state_focused="true">
<shape>
<gradient android:startColor="#eac100"/>
<stroke android:width="2dp" android:color="#333333"/>
<corners android:radius="8dp"/>
<padding android:bottom="10dp" android:left="10dp"
android:right="10dp" android:top="10dp"/>
</shape>
</item>
</selector>

 

selector可以用來指定不同狀態下文本的顏色,例如按鈕上的文本的顏色

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#999" android:state_selected="true"/>
<item android:color="#666" android:state_focused="true"/>
<item android:color="#333" android:state_pressed="true"/>
<item android:color="#000"/>
</selector>

 

結合這篇博文Android開發:shape和selector和layer-list以及博主的實現的圓角鏤空按鈕例子(綜合使用了Shape、Layer和Selector實現了圓角鏤空按鈕)一起看還是挺不錯的。

4.Android繪圖技巧
(1)Canvas 畫布
四個主要方法:
save:保存畫布,將之前繪制的內容保存起來;
restore:合並畫布,將save方法之后繪制的內容與之前繪制的內容合並起來;
translate:移動畫布,其實是畫布所在的坐標系的移動;
rotate:旋轉畫布,其實是畫布所在的坐標系的旋轉。
后面兩個方法主要是用來方便在某些特殊情況下的繪制,例如書中介紹的儀表盤的繪制

@Override
protected void onDraw(Canvas canvas) {
// 獲取寬高參數
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
// 畫外圓
Paint paintCircle = new Paint();
paintCircle.setStyle(Paint.Style.STROKE);
paintCircle.setAntiAlias(true);
paintCircle.setStrokeWidth(5);
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, paintCircle);
// 畫刻度
Paint painDegree = new Paint();
paintCircle.setStrokeWidth(3);
for (int i = 0; i < 24; i++) {
// 區分整點與非整點
if (i == 0 || i == 6 || i == 12 || i == 18) {
painDegree.setStrokeWidth(5);
painDegree.setTextSize(30);
canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2,
mWidth / 2, mHeight / 2 - mWidth / 2 + 60,
painDegree);
String degree = String.valueOf(i);
canvas.drawText(degree,
mWidth / 2 - painDegree.measureText(degree) / 2,
mHeight / 2 - mWidth / 2 + 90,
painDegree);
} else {
painDegree.setStrokeWidth(3);
painDegree.setTextSize(15);
canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2,
mWidth / 2, mHeight / 2 - mWidth / 2 + 30,
painDegree);
String degree = String.valueOf(i);
canvas.drawText(degree,
mWidth / 2 - painDegree.measureText(degree) / 2,
mHeight / 2 - mWidth / 2 + 60,
painDegree);
}
// 通過旋轉畫布簡化坐標運算
canvas.rotate(15, mWidth / 2, mHeight / 2);
}
// 畫圓心
Paint paintPointer = new Paint();
paintPointer.setStrokeWidth(30);
canvas.drawPoint(mWidth / 2, mHeight / 2, paintPointer);
// 畫指針
Paint paintHour = new Paint();
paintHour.setStrokeWidth(20);
Paint paintMinute = new Paint();
paintMinute.setStrokeWidth(10);
canvas.save();
canvas.translate(mWidth / 2, mHeight / 2);
canvas.drawLine(0, 0, 100, 100, paintHour);
canvas.drawLine(0, 0, 100, 200, paintMinute);
canvas.restore();
}

 

(2)Layer 圖層
在Android中圖層是基於棧的結構來管理的,通過調用saveLayersaveLayerAlpha方法來創建圖層,使用restorerestoreToCount方法將一個圖層入棧。入棧的時候,后面所有的操作都發生在這個圖層上,而出棧的時候則會把圖像繪制在上層Canvas上。

@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
mPaint.setColor(Color.BLUE);
canvas.drawCircle(150, 150, 100, mPaint);

canvas.saveLayerAlpha(0, 0, 400, 400, 127, LAYER_FLAGS);
mPaint.setColor(Color.RED);
canvas.drawCircle(200, 200, 100, mPaint);
canvas.restore();
}

 

儀表盤和Layer圖層效果如下:
    

5.Android圖像處理 [TODO:該部分略過了,自己暫時用的比較少,等需要用的時候學習下再補充]
色彩特效處理、圖形特效處理、畫筆特效處理

6.SurfaceView
SurfaceView與View的區別
(1)View主要適用於主動更新的情況下,而SurfaceView主要適用於被動更新,例如頻繁地刷新;
(2)View在主線程中對畫面進行刷新,而SurfaceView通常會通過一個子線程來進行頁面刷新;
(3)View在繪圖時沒有使用雙緩沖機制,而SurfaceView在底層實現機制中就已經實現了雙緩沖機制。

SurfaceView的使用
(1)創建SurfaceView,一般繼承自SurfaceView,並實現接口SurfaceHolderCallback。
SurfaceHolderCallback接口的三個回調方法

@Override
public void surfaceCreated(SurfaceHolder holder) {
//做一些初始化操作,例如開啟子線程通過循環來實現不停地繪制
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}

 

(2)初始化SurfaceView
初始化SurfaceHolder對象,並設置Callback

private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
......
}

 

(3)使用SurfaceView
通過lockCanvas方法獲取Canvas對象進行繪制,並通過unlockCanvasAndPost方法對畫布內容進行提交
需要注意的是每次調用lockCanvas拿到的Canvas都是同一個Canvas對象,所以之前的操作都會保留,如果需要擦除,可以在繪制之前調用drawColor方法來進行清屏。

private void draw() {
try {
mCanvas = mHolder.lockCanvas();
//mCanvas draw something
} catch (Exception e) {
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}

 

書中使用SurfaceView實現了簡易畫板

public class SimpleDraw extends SurfaceView implements SurfaceHolder.Callback, Runnable {

private SurfaceHolder mHolder;
private Canvas mCanvas;
private boolean mIsDrawing;
private Path mPath;
private Paint mPaint;

public SimpleDraw(Context context) {
super(context);
initView();
}

public SimpleDraw(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}

public SimpleDraw(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initView();
}

private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(40);
}

@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() {
long start = System.currentTimeMillis();
while (mIsDrawing) {
draw();
}
long end = System.currentTimeMillis();
// 50 - 100ms,經驗值
if (end - start < 100) {
try {
Thread.sleep(100 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

private void draw() {
try {
mCanvas = mHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e) {
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}

 

 

Android動畫機制與使用技巧

1.View動畫 (視圖動畫)
視圖動畫(Animation)框架定義了透明度(AlphaAnimation)、旋轉(RotateAnimation)、縮放(ScaleAnimation)和位移(TranslateAnimation)幾種常見的動畫,控制的是整個View,所以視圖動畫的缺陷就在於當某個元素發生視圖動畫后,其響應事件的位置還依然停留在原來的地方!
實現原理是每次繪制視圖時View所在的ViewGroup中的drawChild方法獲取該View的Animation的Transformation值,然后調用canvas.concat(transformationToApply.getMatrix()),通過矩陣運算完成動畫幀。如果動畫沒有完成,就繼續調用invalidate方法,啟動下次繪制來驅動動畫,從而完成整個動畫的繪制。
動畫集合(AnimationSet):將多個視圖動畫組合起來
動畫監聽器(AnimationListener):提供動畫的監聽回調方法

2.屬性動畫
Android 3.0之后添加了屬性動畫(Animator)框架,其中核心類ObjectAnimator能夠自動驅動,在不影響動畫效果的情況下減少CPU資源消耗。

ObjectAnimator
創建ObjectAnimator只需通過它的靜態工廠方法直接返回一個ObjectAnimator對象,參數包括view對象,以及view的屬性名字,這個屬性必須要有get/set方法,因為ObjectAnimator內部會通過反射機制來修改屬性值。常用的可以直接使用屬性動畫的屬性包括:
(1)translationXtranslationY:控制view從它布局容器左上角坐標偏移的位置;
(2)rotationrotationXrotationY:控制view圍繞支點進行2D和3D旋轉;
(3)scaleXscaleY:控制view圍繞着它的支點進行2D縮放;
(4)pivotXpivotY:控制支點位置,圍繞這個支點進行旋轉和縮放處理。默認情況下,支點是view的中心點;
(5)xy:控制view在它的容器中的最終位置,它是最初的左上角坐標和translationX、translationY的累計和;
(6)alpha:控制透明度,默認是1(不透明)。

ObjectAnimator的常見使用方式如下:

ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 300);
animator.setDuration(1000);
animator.start();

 

屬性動畫集合AnimatorSet:控制多個動畫的協同工作方式,常用方法animatorSet.play().with().before().after()playTogetherplaySequentially等方法來精確控制動畫播放順序。使用PropertyValueHolder也可以實現簡單的動畫集合效果。

動畫監聽器:監聽動畫事件可以使用AnimatorListener或者簡易的適配器AnimatorListenerAdapter

如果一個屬性沒有get/set方法怎么辦?
(1)自定義包裝類,間接地給屬性提供get/set方法,下面就是一個包裝類的例子,為width屬性提供了get/set方法

public class WrapperView {

private View mView;

public WrapperView(View mView){
this.mView = mView;
}

public int getWidth(){
return mView.getLayoutParams().width;
}

public void setWidth(int width){
mView.getLayoutParams().width = width;
mView.requestLayout();
}
}

 

(2)使用ValueAnimator
ObjectAnimator就是繼承自ValueAnimator的,它是屬性動畫的核心,ValueAnimator不提供任何動畫效果,它就是一個數值產生器,用來產生具有一定規律的數字,從而讓調用者來控制動畫的實現過程,控制的方式是使用AnimatorUpdateListener來監聽數值的變換。

ValueAnimator animator = ValueAnimator.ofFloat(0,100);
animator.setTarget(view);
animator.setDuration(1000);
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float value = (Float) animation.getAnimatedValue();
//do the animation!
}
});

 

在XML中使用屬性動畫
下面是一個簡單例子:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="4000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360" />

 

在代碼中使用方式如下: [注:測試該代碼的時候,上面的xml定義應該放在res的animator目錄下,放在anim目錄下不行]

Animator animator = AnimatorInflater.loadAnimator(this, R.animator.animator_rotation);
animator.setTarget(view);
animator.start();

 

View的animate方法
Android 3.0之后View新增了animate方法直接驅動屬性動畫,它其實是屬性動畫的一種簡寫方式

imageView.animate().alpha(0).y(100).setDuration(1000)
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}

@Override
public void onAnimationEnd(Animator animation) {
}

@Override
public void onAnimationCancel(Animator animation) {
}

@Override
public void onAnimationRepeat(Animator animation) {
}
});

 

3.布局動畫
布局動畫是作用在ViewGroup上的,給ViewGroup添加view時添加動畫過渡效果。
(1)簡易方式(但是沒有什么效果):在xml中添加如下屬性 android:animateLayoutChanges="true
(2)通過LayoutAnimationController來自定義子view的過渡效果,下面是一個常見的使用例子:

LinearLayout linearLayout = (LinearLayout) findViewById(R.id.ll);
ScaleAnimation scaleAnimation = new ScaleAnimation(0,1,0,1);
scaleAnimation.setDuration(2000);
LayoutAnimationController controller = new LayoutAnimationController(scaleAnimation, 0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);//NORMAL 順序 RANDOM 隨機 REVERSE 反序
linearLayout.setLayoutAnimation(controller);

 

4.自定義動畫
創建自定義動畫就是要實現它的applyTransformation的邏輯,不過通常還需要覆蓋父類的initialize方法來實現初始化工作。
下面是一個模擬電視機關閉的動畫,

public class CustomTV extends Animation {

private int mCenterWidth;
private int mCenterHeight;

@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
setDuration(1000);// 設置默認時長
setFillAfter(true);// 動畫結束后保留狀態
setInterpolator(new AccelerateInterpolator());// 設置默認插值器
mCenterWidth = width / 2;
mCenterHeight = height / 2;
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final Matrix matrix = t.getMatrix();
matrix.preScale(1, 1 - interpolatedTime, mCenterWidth, mCenterHeight);
}
}

 

applyTransformation方法的第一個參數interpolatedTime是插值器的時間因子,取值在0到1之間;第二個參數Transformation是矩陣的封裝類,一般使用它來獲得當前的矩陣Matrix對象,然后對矩陣進行操作,就可以實現動畫效果了。

如何實現3D動畫效果呢?
使用android.graphics.Camera中的Camera類,它封裝了OpenGL的3D動畫。可以把Camera想象成一個真實的攝像機,當物體固定在某處時,只要移動攝像機就能拍攝到具有立體感的圖像,因此通過它可以實現各種3D效果。
下面是一個3D動畫效果的例子

public class CustomAnim extends Animation {

private int mCenterWidth;
private int mCenterHeight;
private Camera mCamera = new Camera();
private float mRotateY = 0.0f;

@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
setDuration(2000);// 設置默認時長
setFillAfter(true);// 動畫結束后保留狀態
setInterpolator(new BounceInterpolator());// 設置默認插值器
mCenterWidth = width / 2;
mCenterHeight = height / 2;
}

// 暴露接口-設置旋轉角度
public void setRotateY(float rotateY) {
mRotateY = rotateY;
}

@Override
protected void applyTransformation( float interpolatedTime, Transformation t) {
final Matrix matrix = t.getMatrix();
mCamera.save();
mCamera.rotateY(mRotateY * interpolatedTime);// 使用Camera設置旋轉的角度
mCamera.getMatrix(matrix);// 將旋轉變換作用到matrix上
mCamera.restore();
// 通過pre方法設置矩陣作用前的偏移量來改變旋轉中心
matrix.preTranslate(mCenterWidth, mCenterHeight);
matrix.postTranslate(-mCenterWidth, -mCenterHeight);
}
}

 

5.Android 5.X SVG矢量動畫機制 [TODO:該部分略過了,自己暫時用的比較少,等需要用的時候學習下再補充]
本章最后還有幾個很常用的動畫實例,感興趣可以看下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM