@1 理解SurfaceView
为什么要使用SurfaceView,而不是直接使用View?
这里要考虑到动态场景和静态场景的差异,相对于动态场景:
而以上View不具备的这些SurfaceView均具备。因此可以理解为:静态绘图用View更合适,但动态绘图SurfaceView更适合。
@2 SurfaceView的使用步骤
@3 SurfaceView官方文档
SurfaceView官方文档:Android SurfaceView类详解
实现功能:一张背景图上显示一条在游动的鱼(动态鱼时由10张Bitmap构成的组图)。效果如下所示:
说明:鱼是沿着红线箭头的方向游,游的时候会不断切换图片来构建动画。
关于该程序,自定义SurfaceView的关键代码如下所示:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "MySurfaceView";
private Bitmap bitmapBackground = null;
private Bitmap[] bitmapFish = new Bitmap[10];
private UpdateSurfaceViewThread drawThread = null;
public MySurfaceView(Context context) {
super(context);
init(context);
}
private void init(Context context){
//SurfaceHolder初始化
getHolder().addCallback(this);
//Bitmap init(fish & fish background)
String[] images = new String[11];
try {
images = context.getAssets().list("");
for (int i = 0,j=0; i < images.length; i++){
if(images[i].endsWith("png")){
//fish pic
InputStream isImage = context.getAssets().open(images[i]);
bitmapFish[j] = BitmapFactory.decodeStream(isImage);
j++;
}else if(images[i].endsWith("jpg")){
//fish background pic
InputStream isImage = context.getAssets().open(images[i]);
bitmapBackground = BitmapFactory.decodeStream(isImage);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
//drawThread线程启动
if(drawThread==null){
drawThread = new UpdateSurfaceViewThread(this);
drawThread.setResource(bitmapFish,bitmapBackground);
drawThread.start();
}
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
//drawThread停止与释放资源
if(drawThread !=null){
drawThread.requestExitAndWait();
drawThread = null;
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
//do nothing
}
}
绘制线程UpdateSurfaceViewThread的代码实现为:
public class UpdateSurfaceViewThread extends Thread{
private boolean done = false;
private SurfaceView surfaceView;
//bitmap fish资源
private Bitmap bitmapBackground = null;
private Bitmap[] bitmapFish = new Bitmap[10];
//关于fish绘制相关的变量设置
private int fishIndex;//fish图片索引
private float initX=0.0f,initY=500.0f; //fish 初始化坐标
private float currentX=0.0f,currentY=0.0f;//fish 当前坐标
private float fishSpeed = 10f; //fish速度
private float fishAngle = (float) (Math.random()*60.f);//fish进入角度
private Matrix matrix = new Matrix(); //fish专属变换矩阵matrix
UpdateSurfaceViewThread(SurfaceView surfaceView){
this.surfaceView = surfaceView;
//小鱼绘制 初始化坐标位置
initX = surfaceView.getWidth()-400f;
initY = surfaceView.getHeight()-800f;
currentX = initX;
currentY = initY;
}
void setResource(Bitmap[] bitmaps,Bitmap bitmapground){
bitmapFish = bitmaps;
bitmapBackground = bitmapground;
}
@Override
public void run() {
SurfaceHolder surfaceHolder = surfaceView.getHolder();
while(!done){
//关键步骤1 锁定SurfaceView对象,获取canvas
Canvas canvas = surfaceHolder.lockCanvas();
//关键步骤2 在canvas上绘制内容
canvas.drawColor(Color.BLACK);
//draw background
float centerStartX = (canvas.getWidth()-bitmapBackground.getWidth())*1.0f/2;
float centerStartY = (canvas.getHeight()-bitmapBackground.getHeight())*1.0f/2;
canvas.translate(centerStartX,centerStartY);
canvas.drawBitmap(bitmapBackground,0f,0f,null);
//draw a fish
if(currentX<0 || currentY<0){
currentX = initX;
currentY = initY;
fishAngle = (float) (Math.random()*60.f);
}
matrix.reset();
matrix.setRotate(fishAngle);
currentX-=fishSpeed*Math.cos(Math.toRadians(fishAngle));
currentY-=fishSpeed*Math.sin(Math.toRadians(fishAngle));
matrix.setTranslate(currentX,currentY);
canvas.drawBitmap(bitmapFish[fishIndex++%bitmapFish.length],matrix,null);
//关键步骤3 提交绘制图形
surfaceHolder.unlockCanvasAndPost(canvas);
try {
//注意:这里可以根据需要调整动画频率
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void requestExitAndWait() {
done = true;
try {
join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在MainActivity中实现代码为:
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MySurfaceView(this));
}
}
实现功能:按键响应 开始绘制正弦/余弦曲线。效果如下所示:
在MainActivity中实现代码为:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static String TAG = "MainActivity";
private Button btn_sin,btn_cos;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder = null;
private Paint paint = new Paint();
private static final int HEIGHT = 800;//x轴(+、- 各200)
private static final int X_OFFSET = 10;//间距调整参数
int screenWidth;
int cx = X_OFFSET;
Timer timer = new Timer();
TimerTask timerTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_surfaceview);
btn_sin = findViewById(R.id.btn_sin);
btn_cos = findViewById(R.id.btn_cos);
surfaceView = findViewById(R.id.surfaceView);
//获取屏幕宽度
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
screenWidth = metrics.widthPixels;
//获取holder
surfaceHolder = surfaceView.getHolder();
//注意:如果没有这段代码并不会报错,但首次显示不回触发drawBackground,显示会黑屏,start
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
drawBackground(surfaceHolder);
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
timer.cancel();
}
});
//注意:。。。,end
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(15f);
paint.setAntiAlias(true);
paint.setDither(true);
}
@Override
public void onClick(View v) {
drawBackground(surfaceHolder);
cx = X_OFFSET;
if(timerTask!=null){
timerTask.cancel();
}
//逐点绘制,使用TimerTask来更新正弦/余弦图像内容
timerTask = new TimerTask() {
@Override
public void run() {
paint.setColor(Color.GREEN);
int cy = 0;
if(v.getId() == R.id.btn_sin){
cy = HEIGHT/2-(int)(HEIGHT/4*Math.sin((cx-5)*Math.PI/150));
}else if(v.getId() == R.id.btn_cos){
cy = HEIGHT/2-(int)(HEIGHT/4*Math.cos((cx-5)*Math.PI/150));
}
//局部刷新
Canvas canvas = surfaceHolder.lockCanvas(new Rect(cx,cy,cx+(int)paint.getStrokeWidth(),cy+(int)paint.getStrokeWidth()));
canvas.drawPoint(cx,cy,paint);
cx+=1;
if(cx>screenWidth){
timerTask.cancel();
timerTask = null;
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
};
timer.schedule(timerTask,0,10);
}
void drawBackground(SurfaceHolder holder){
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.WHITE);
paint.setColor(Color.BLACK);
canvas.drawLine(X_OFFSET,HEIGHT/2,screenWidth,HEIGHT/2,paint);
canvas.drawLine(X_OFFSET,40f,X_OFFSET,HEIGHT,paint);
holder.unlockCanvasAndPost(canvas);
}
}
关于该程序,sample_surfaceview.xml 布局参考文件如下所示: