Android之SurfaceView
本来这个SurfaceView没什么好写的。仅仅是发现网络上很多SurfaceView的教程代码不全,入门者可能会感到困惑,因为不知道谁应该放在哪里。所以这里力求无论新手熟手,都能从源码中得到全部信息,权当是一个补充。
第一步
首先自己先建一个类,我这里叫MySurfaceView,完整的源码如下(参考了【1】,但修复了其中退出时会引发黑屏的一个小bug,另外多说一句,里面采用新建线程的方法不要学,线程能不建就尽量不要建;对于while循环,一般在有外来信息时加入判断条件,及时中止即可,不过这与本文主旨无关)。
package com.spacesoftwares.ssapplication;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* Created by Administrator on 2018/7/19 0019.
*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
// SurfaceHolder
private SurfaceHolder mSurfaceHolder;
// 画布
private Canvas mCanvas;
// 子线程标志位
private boolean isDrawing;
// 画笔
Paint mPaint;
// 路径
Path mPath;
private float mLastX, mLastY;//上次的坐标
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* 初始化
*/
private void init() {
//初始化 SurfaceHolder mSurfaceHolder
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
//画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setStrokeWidth(10f);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
//路径
mPath = new Path();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {//创建
isDrawing = true;
Log.e("surfaceCreated","--"+isDrawing);
//绘制线程
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改变
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {//销毁
isDrawing = false;
Log.e("surfaceDestroyed","--"+isDrawing);
}
@Override
public void run() {
while (isDrawing){
drawing();
}
}
/**
* 绘制
*/
private void drawing() {
try {
mCanvas = mSurfaceHolder.lockCanvas();
if(null == mCanvas){
return;
}
mCanvas.drawColor(Color.YELLOW);
mCanvas.drawPath(mPath,mPaint);
} finally {
if (mCanvas != null) {
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
mPath.moveTo(mLastX, mLastY);
break;
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(x - mLastX);
float dy = Math.abs(y - mLastY);
if (dx >= 3 || dy >= 3) {
mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2);
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(300, 300);
} else if (wSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(300, hSpecSize);
} else if (hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(wSpecSize, 300);
}
}
}
其中drawing函数中 if(null == mCanvas){ return; }这句是必须的,否则退出时程序可能会崩溃,因此此时从surfaceHolder中得到的canvas一定是空的。报错内容大概和这个差不多:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Canvas.drawColor(int)' on a null object reference....bla bla bla...。
第二步 SurfaceViewActivity
SurfaceViewAcitivity源码如下
package com.spacesoftwares.ssapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceView;
public class SurfaceViewActivity extends AppCompatActivity {
private MySurfaceView surface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_surface_view);
surface = findViewById(R.id.sv_main);
}
}
对应的layout/activity_surface_view.xml如下
<?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="com.spacesoftwares.ssapplication.SurfaceViewActivity">
<com.spacesoftwares.ssapplication.MySurfaceView
android:id="@+id/sv_main"
android:layout_width="match_parent"
android:layout_height="400dp"
/>
</android.support.constraint.ConstraintLayout>
这里你可以看到,布局文件com.spacesoftwares.ssapplication.MySurfaceView就是我们自己定义的那个MySurfaceView。
当然,不要忘记检查AndroidManifest.xml中有这么一句(一般Android Studio会自动添加)
com.spacesoftwares.ssapplication.SurfaceViewActivity