android進階4step1:Android動畫處理與自定義View——SurfaceView
阿新 • • 發佈:2019-01-04
SurfaceView簡介
- 1、SurfaceView與View的區別
- 2、SurfaceView的具體使用場景
- 3、如何使用SurfaceView
一、SurfaceView與View的區別
- 1、不使用onDraw
- 2、非UI執行緒繪製
- 3、獨立的Surface
二、SurfaceView的具體使用場景
- 1、視訊播放
- 2、一些炫酷的動畫效果 (一直在動的動畫)
- 3、小遊戲
三、如何使用SurfaceView
- 1、獲取SurfaceHolder物件
- 2、監聽Surface的建立
- 3、開啟非同步執行緒進行while迴圈(子執行緒可以不停地繪製)
- 4、通過SurfaceHolder獲取Canvas進行繪製
固定模板:
import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; /** * <p>檔案描述:<p> * <p>作者:Mr-Donkey<p> * <p>建立時間:2018/12/10 21:09<p> * <p>更改時間:2018/12/10 21:09<p> * <p>版本號:1<p> */ public class SurfaceViewTemplate extends SurfaceView implements Runnable { private Thread mThread; //多執行緒的同步 private volatile boolean isRunning; public SurfaceViewTemplate(Context context, AttributeSet attrs) { super(context, attrs); /** * 如何使用SurfaceView 1、獲取SurfaceHolder物件 2、監聽Surface的建立 3、開啟非同步執行緒進行while迴圈(子執行緒可以不停地繪製) 4、通過SurfaceHolder獲取Canvas進行繪製 */ //1.獲取SurfaceHolder物件 SurfaceHolder holder = getHolder(); //2.監聽Surface建立 holder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { //監聽Surface建立完畢 mThread = new Thread(SurfaceViewTemplate.this); //執行run方法 mThread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { isRunning = false; } }); } @Override public void run() { while (isRunning) { drawSelf(); } } /** * 3、開啟非同步執行緒進行while迴圈(子執行緒可以不停地繪製) * 4、通過SurfaceHolder獲取Canvas進行繪製 */ private void drawSelf() { Canvas canvas = null; try { canvas = getHolder().lockCanvas(); if (canvas != null) { //draw } } catch (Exception e) { e.printStackTrace(); } finally { if (canvas != null) { //釋放canvas getHolder().unlockCanvasAndPost(canvas); } } } }
surface例項:
在子執行緒開啟while迴圈 保證子執行緒一直執行
(一直在動態繪製圓(動態改變半徑)( 1.先繪製白色背景(覆蓋之前繪製的圓) 2.再繪製圓)) 重複1、2步驟
完整程式碼:
public class SurfaceViewTemplate extends SurfaceView implements Runnable { private Thread mThread; //多執行緒的同步 private volatile boolean isRunning; //畫筆 private Paint mPaint; //定義圓的屬性 private int mMinRadius; private int mMaxRadius; private int mRadius; private int mFlag; public SurfaceViewTemplate(Context context, AttributeSet attrs) { super(context, attrs); /** * 三、如何使用SurfaceView 1、獲取SurfaceHolder物件 2、監聽Surface的建立 3、開啟非同步執行緒進行while迴圈(子執行緒可以不停地繪製) 4、通過SurfaceHolder獲取Canvas進行繪製 */ //1.獲取SurfaceHolder物件 SurfaceHolder holder = getHolder(); //2.監聽Surface建立 holder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { //讓執行緒開啟 isRunning = true; //監聽Surface建立完畢 mThread = new Thread(SurfaceViewTemplate.this); //執行run方法 mThread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { isRunning = false; } }); //SurfaceView設定屬性 setFocusable(true); setFocusableInTouchMode(true); setKeepScreenOn(true); //初始化畫筆 initPaint(); } /** * 初始化畫筆 */ private void initPaint() { mPaint = new Paint(); mPaint.setDither(true); //抗鋸齒 mPaint.setAntiAlias(true); //空心圓 mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(6); mPaint.setColor(Color.GREEN); } /** * 控制元件大小發生改變時呼叫 系統呼叫 * * @param w * @param h * @param oldw * @param oldh */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //-20 隨意寫的 mMaxRadius = Math.min(w, h) / 2 - 20; mRadius = mMinRadius = 30; } @Override public void run() { while (isRunning) { //不斷的繪製 drawSelf(); } } /** * 3、開啟非同步執行緒進行while迴圈(子執行緒可以不停地繪製) * 4、通過SurfaceHolder獲取Canvas進行繪製 */ private void drawSelf() { Canvas canvas = null; try { canvas = getHolder().lockCanvas(); if (canvas != null) { drawCircle(canvas); } } catch (Exception e) { e.printStackTrace(); } finally { if (canvas != null) { //釋放canvas getHolder().unlockCanvasAndPost(canvas); } } } /** * 畫圓的方法 * 因為放到while裡要不斷迴圈繪製檢視 * 1.第一次先畫個白色背景 * 2.背景之上畫個圓 * 迴圈1.2 動態改變 圓的半徑 * * @param canvas */ private void drawCircle(Canvas canvas) { //繪製背景(覆蓋之前的圓) canvas.drawColor(Color.WHITE); //繪製圓 canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, mPaint); //動態改變 圓的半徑 if (mRadius >= mMaxRadius) { mFlag = -1; } else if (mRadius <= mMinRadius) { mFlag = 1; } //達到最大值遞減 2個畫素 達到最小值遞減2個畫素 mRadius += mFlag * 2; } }
實現飛享的小鳥的案例
先了解一下canvas畫矩形的方法:
public voiddrawRect(float left, float top, float right, float bottom,Paint paint)
說明:繪製一個矩型。需要注明的是繪製矩形的引數和Java中的方法不一樣。
該方法的引數圖解說明如下:
各位看官請注意:圖中X、Y軸方向標記錯誤。 自己也懶得重新修正了。
那麼,矩形的高 height = bottom - right
矩形的寬 width = right – left
PS :假如drawRect的引數有誤,比如right < left ,Android是不會給我們檢查的,也不會提示相應的錯誤資訊,
但它會繪畫出一個高或寬很小的矩形,可能不是你希望的。
佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--讓自定義的surfaceview 佔滿 並使用NoActionbar主題 xml中設定-->
<com.demo.surfaceviewdemo.game.FlappyBird
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
FlappyBird.java 主要邏輯 迴圈繪製動畫 和動態改變鳥的高度 和 地板的x方向的值
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.support.annotation.DrawableRes;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.demo.surfaceviewdemo.R;
/**
* <p>檔案描述:<p>
* <p>作者:Mr-Donkey<p>
* <p>建立時間:2018/12/10 21:09<p>
* <p>更改時間:2018/12/10 21:09<p>
* <p>版本號:1<p>
*/
public class FlappyBird extends SurfaceView implements Runnable {
private Thread mThread;
//多執行緒的同步
private volatile boolean isRunning;
//繪製的矩形區域
private RectF mDestRect;
//res
private Bitmap mBg;
private Bitmap mBirdbm;
private Bitmap mFloorbm;
//定義Floor物件
private Floor mFloor;
private Bird mBrid;
//定義 底部x的移動速度
private int mSpeed;
//點選螢幕鳥上升的距離
private static final int TOUCH_UP_SIZE = -16;
private int mBirdUpDis;
//鳥自由下落的大小
private static final int SIZE_AUTO_DOWN = 2;
private int mAutoDownDis;
//中間值
private int mTempBirdDis;
public FlappyBird(Context context, AttributeSet attrs) {
super(context, attrs);
/**
* 三、如何使用SurfaceView
1、獲取SurfaceHolder物件
2、監聽Surface的建立
3、開啟非同步執行緒進行while迴圈(子執行緒可以不停地繪製)
4、通過SurfaceHolder獲取Canvas進行繪製
*/
//1.獲取SurfaceHolder物件
SurfaceHolder holder = getHolder();
//2.監聽Surface建立
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
//讓執行緒開啟
isRunning = true;
//監聽Surface建立完畢
mThread = new Thread(FlappyBird.this);
//執行run方法
mThread.start();
//當用戶點選home的時候,會重新繪製 定位鳥的位置
//讓鳥重寫在螢幕中央
mBrid.reset();
mTempBirdDis = 0;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isRunning = false;
}
});
//SurfaceView設定屬性
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
//初始化資源
initRes();
//設定底部x的移動速度
mSpeed = Util.dp2px(getContext(), 2);
//每次點選上升的距離
mBirdUpDis = Util.dp2px(getContext(), TOUCH_UP_SIZE);
//每次自由下落的大小
mAutoDownDis = Util.dp2px(getContext(), SIZE_AUTO_DOWN);
}
/**
* 初始化資源
*/
private void initRes() {
mBg = loadBitmapByResId(R.drawable.bg1);
mBirdbm = loadBitmapByResId(R.drawable.b1);
mFloorbm = loadBitmapByResId(R.drawable.floor_bg);
}
//封裝初始化資源
//@DrawableRes 註解 來增強程式碼的健壯性 必須要傳入一個 DrawableRes值
private Bitmap loadBitmapByResId(@DrawableRes int resId) {
return BitmapFactory.decodeResource(getResources(), resId);
}
/**
* 控制元件大小發生改變時呼叫 系統呼叫
*
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//矩形區域
mDestRect = new RectF(0, 0, w, h);
mFloor = new Floor(getContext(), w, h, mFloorbm);
mBrid = new Bird(getContext(), w, h, mBirdbm);
}
@Override
public void run() {
while (isRunning) {
long start = System.currentTimeMillis();
drawSelf();
long end = System.currentTimeMillis();
//為了避免浪費資源
//限制每隔50ms繪製一次 自己定義
if (end - start < 50) {
try {
Thread.sleep(50 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 3、開啟非同步執行緒進行while迴圈(子執行緒可以不停地繪製)
* 4、通過SurfaceHolder獲取Canvas進行繪製
*/
private void drawSelf() {
Canvas canvas = null;
try {
canvas = getHolder().lockCanvas();
if (canvas != null) {
//進行繪製工作
logic();
drawBg(canvas);
drawBird(canvas);
drawFloor(canvas);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null) {
//釋放canvas
getHolder().unlockCanvasAndPost(canvas);
}
}
}
/***
*執行一定的邏輯
*
*/
private void logic() {
//動態改變Floor地板 x的位置讓它有動起來的效果
mFloor.setX(mFloor.getX() - mSpeed);
//動態改變鳥的下降的速度y越大 越靠近底部
mTempBirdDis += mAutoDownDis;
mBrid.set(mBrid.getY() + mTempBirdDis);
}
//點選螢幕的世界
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
//賦值給中間變數 讓 y減小 即上升
mTempBirdDis = mBirdUpDis;
}
return true;
}
//繪製背景
private void drawBg(Canvas canvas) {
//引數1 bitmap,matrix,繪製矩形區域
canvas.drawBitmap(mBg, null, mDestRect, null);
}
/**
* 對於下面有具體邏輯的繪製
* 自定義類實現具體邏輯方便些
*
* @param canvas
*/
//繪製地板
private void drawFloor(Canvas canvas) {
mFloor.draw(canvas);
}
//繪製小鳥
private void drawBird(Canvas canvas) {
mBrid.draw(canvas);
}
}
工具類 Util.java 將dp轉換成px
public class Util {
//將dp轉成 px
public static int dp2px(Context context, int dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal,
context.getResources().getDisplayMetrics());
}
}
Bird和Floor的基類 抽取的公共方法
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
/**
* <p>檔案描述:<p>
* <p>作者:Mr-Donkey<p>
* <p>建立時間:2018/12/10 23:09<p>
* <p>更改時間:2018/12/10 23:09<p>
* <p>版本號:1<p>
*/
/**
* 地板和鳥的公共類
*/
public abstract class DrawablePart {
protected Context mContext;
protected int mGameWidth;
protected int mGameHeight;
protected Bitmap mBitmap;
public DrawablePart(Context context, int gameW, int gameH, Bitmap bitmap) {
mContext = context;
mGameWidth = gameW;
mGameHeight = gameH;
mBitmap = bitmap;
}
public abstract void draw(Canvas canvas);
}
Bird.java 實現鳥的繪製
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RectF;
/**
* <p>檔案描述:<p>
* <p>作者:Mr-Donkey<p>
* <p>建立時間:2018/12/10 23:12<p>
* <p>更改時間:2018/12/10 23:12<p>
* <p>版本號:1<p>
*/
public class Bird extends DrawablePart {
private int x;
private int y;
private static final float RADIO_Y_POS = 1 / 2F;
// 30dp
private static final int WIDTH_BIRD = 30;
private int mWidth;
private int mHeight;
//繪製的矩形範圍
private RectF mRect = new RectF();
public Bird(Context context, int gameW, int gameH, Bitmap bitmap) {
super(context, gameW, gameH, bitmap);
//鳥所在的y的位置
y = (int) (gameH * RADIO_Y_POS);
//設定鳥的寬度為30dp
mWidth = Util.dp2px(context, WIDTH_BIRD);
//在和寬度等比例下 鳥的高度
mHeight = (int) (mWidth * 1.0f / bitmap.getWidth() * bitmap.getHeight());
//繪製鳥的初始左邊x位置 螢幕的一半減去鳥本身的寬度的一半
x = gameW / 2 - mWidth / 2;
}
@Override
public void draw(Canvas canvas) {
mRect.set(x, y, x + mWidth, y + mHeight);
canvas.drawBitmap(mBitmap, null, mRect, null);
}
/**
* 重置bird位置
* 改變高度
*/
public void reset() {
y = (int) (mHeight * RADIO_Y_POS);
}
/**
* 暴露方法點選時動態改變y的值
*
* @return
*/
public int getY() {
return y;
}
public void set(int y) {
this.y = y;
}
}
Floor.java 實現地板的繪製
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader;
/**
* <p>檔案描述:<p>
* <p>作者:Mr-Donkey<p>
* <p>建立時間:2018/12/10 23:08<p>
* <p>更改時間:2018/12/10 23:08<p>
* <p>版本號:1<p>
*/
public class Floor extends DrawablePart {
//設定動畫的位置
private int x;
private int y;
private static final float RADIO_Y_POS = 4 / 5F;
private Paint mPaint;
private BitmapShader mBitmapShader;
public Floor(Context context, int gameW, int gameH, Bitmap bitmap) {
super(context, gameW, gameH, bitmap);
//讓高度位於檢視的4/5左右
y = (int) (gameH * RADIO_Y_POS);
//初始化畫筆
mPaint = new Paint();
//設定抗鋸齒
mPaint.setAntiAlias(true);
mPaint.setDither(true);
//引數1 bitmap 引數2:橫向的模式(設定為重複) 引數3:縱向的模式(設定為拉伸)
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
}
@Override
public void draw(Canvas canvas) {
canvas.save();
//將畫步canvas移動到指定的位置
canvas.translate(x, y);
mPaint.setShader(mBitmapShader);
canvas.drawRect(x, 0, mGameWidth - x, mGameHeight - y, mPaint);
canvas.restore();
//繪製完成之後 回收shader
mPaint.setShader(null);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
//如果x的大於螢幕寬度 則取餘
if (-x > mGameWidth) {
this.x = x % mGameWidth;
}
}
}