Android APP完整基础教程(17)图形系统-SurfaceView

49 篇文章 15 订阅

1 SurfaceView的绘图机制

@1 理解SurfaceView

为什么要使用SurfaceView,而不是直接使用View?

这里要考虑到动态场景和静态场景的差异,相对于动态场景:

  • View组件缺少双缓冲机制。存在图像撕裂/显示不全的情况。
  • View组件无法局部更新。当程序需要更新图片时,程序必须重新绘制View上的整张图片
  • View的更新受限:新线程无法直接更新View。

而以上View不具备的这些SurfaceView均具备。因此可以理解为:静态绘图用View更合适,但动态绘图SurfaceView更适合。

@2 SurfaceView的使用步骤

  • View继承SurfaceView,实现SurfaceHolder.Callback接口并重写方法:surfaceChanged   (surface发生变化时触发)、surfaceCreated(Surface创建时触发,注意:新线程不要再这个线程中绘制Surface)、surfaceDestroyed(销毁时触发)
  • 使用getHolder()获取SurfaceHolder对象,使用SurfaceHolder.addCallback添加回调。
  • 调用SurfaceHolder方法:lockCanvas(获取Canvas对象并锁定画布,调用Canvas绘图)和unlockCanvasAndPost(结束锁定画布,提交改变)

@3 SurfaceView官方文档

SurfaceView官方文档:Android SurfaceView类详解

2 SurfaceView实战

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

2.2 SurfaceView实战-示波器(显示正弦/余弦曲线)

实现功能:按键响应 开始绘制正弦/余弦曲线。效果如下所示:

在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 布局参考文件如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:visibility="visible">
        <Button
            android:id="@+id/btn_sin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/sin" />
        <Button
            android:id="@+id/btn_cos"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/cos" />
    </LinearLayout>
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

Android 中,可以使用 SurfaceView 进行图形绘制,SurfaceView 继承自 View 类,但是与普通的 View 不同,它可以在新的线程中进行绘制操作,因此可以避免在主线程中进行图形绘制导致的卡顿现象。 下面是基本的 SurfaceView 实现流程: 1. 创建一个 SurfaceView 对象,并将其添加到布局中。 2. 实现 SurfaceHolder.Callback 接口,该接口包括三个方法:surfaceCreated、surfaceDestroyed、surfaceChanged。 3. 在 surfaceCreated 回调方法中获取 SurfaceHolder 对象,并通过该对象获取 Canvas 对象进行图形绘制。 4. 在 surfaceChanged 回调方法中实现屏幕旋转、大小变化等操作。 5. 在 surfaceDestroyed 回调方法中释放资源,停止绘制线程等操作。 下面是一个简单的 SurfaceView 实现示例: ```java public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private Paint mPaint; private Thread mThread; private boolean mRunning; public MySurfaceView(Context context) { super(context); init(); } public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mHolder = getHolder(); mHolder.addCallback(this); mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); } @Override public void surfaceCreated(SurfaceHolder holder) { mRunning = true; mThread = new Thread(new Runnable() { @Override public void run() { while (mRunning) { draw(); } } }); mThread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mRunning = false; try { mThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } private void draw() { Canvas canvas = mHolder.lockCanvas(); if (canvas != null) { canvas.drawColor(Color.WHITE); canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); mHolder.unlockCanvasAndPost(canvas); } } } ``` 在该示例中,我们在 surfaceCreated 回调方法中启动了一个新的线程进行图形绘制,每次循环都会调用 draw 方法,该方法中获取 Canvas 对象进行绘制,最后通过 unlockCanvasAndPost 方法提交绘制结果。在 surfaceDestroyed 回调方法中停止绘制线程,释放资源。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值