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;
}
}
}