Android应用开发(35)SufaceView基本用法

Android应用开发学习笔记——目录索引

 参考Android官网:https://developer.android.com/reference/android/view/SurfaceView

一、SurfaceView简介

SurfaceView派生自View,提供嵌入视图层次结构内部的专用绘图表面,SurfaceView可以在主线程之外的线程中向屏幕绘图,这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。

通过 SurfaceHolder 接口提供对Surface的访问,调用getHolder()方法获取SurfaceHolder。

实现SurfaceHolder.Callback回调接口,回调是通过 SurfaceHolder.addCallback方法设置。

SurfaceHolder.Callback中定义了三个接口方法:

SurfaceHolder.Callback#surfaceCreated  // 当首次创建surface后立即调用。
SurfaceHolder.Callback#surfaceChanged  //当surface进行任何更改立即调用此方法。
​SurfaceHolder.Callback#surfaceDestroyed  //当surface即将被破坏之前调用的。

//当surface对象创建后,该方法就会被立即调用。 
public voidsurfaceCreated(SurfaceHolder holder)   {  
             
}  

//当surface发生任何结构性的变化时(格式或者大小),该方法就会被立即调用。
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, intarg3) {  

}  
      
//当surface对象在将要销毁前,该方法会被立即调用。   
public voidsurfaceDestroyed(SurfaceHolder holder)  {  

}  

什么是Surface?简单的说Surface对应了一块屏幕缓冲区,每个window对应一个Surface,任何View都要画在Surface的Canvas(画布)上。传统的view共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行。
在SDK的文档中,对Surface的描述是这样的:“Handle onto a raw buffer that is being managed by the screen compositor”,翻译成中文就是“由屏幕显示内容合成器(screen compositor)所管理的原始缓冲区的句柄”,这句话包括下面两个意思:
1、通过Surface就可以获得原生缓冲器以及其中的内容(因为Surface是句柄)。就像在C++语言中,可以通过一个文件的句柄,就可以获得文件的内容一样。
2、原始缓冲区(a raw buffer)是用于保存当前窗口的像素数据的。引伸地,可以认为Android中的Surface就是一个用来画图形(graphics)或图像(image)的涂鸦场所。由此,可以推知一个Surface对象中应该包含有一个Canvas(画布)对象。
因此,在前面提及的两个意思的基础上,可以再加上一条:
3、Surface中有一个Canvas成员,专门用于画图的。
由以上的概括,我们总结如下:Surface中的Canvas成员,是专门用于供程序员画图的场所,就像黑板一样;其中的原始缓冲区是用来保存数据的地方;Surface本身的作用类似一个句柄,得到了这个句柄就可以得到其中的Canvas、原始缓冲区以及其它方面的内容。
Surface是用来管理数据的(句柄),在这里“数据”指的就是画板的内容。。

SurfaceView 的 Public 方法

void applyTransactionToFrame(SurfaceControl.Transaction transaction)

添加一个事务,该事务将与显示 SurfaceView 的下一帧同步应用。

void draw(Canvas canvas)

手动将此视图(及其所有子视图)渲染到给定的画布。

boolean gatherTransparentRegion(Region region)

当视图层次结构包含一个或多个 SurfaceView 时,ViewRoot 使用它来执行优化。

SurfaceHolder getHolder()

返回 SurfaceHolder,提供对此 SurfaceView 底层表面的访问和控制。

IBinder getHostToken()

用于构造 的令牌SurfaceControlViewHost

int getImportantForAccessibility()

获取用于确定此 View 对于可访问性是否重要的​​模式。

SurfaceControl getSurfaceControl()

返回一个 SurfaceControl,可用于将 Surface 设为此 SurfaceView 的父级。

boolean hasOverlappingRendering()

返回此视图是否有重叠的内容。

void setAlpha(float alpha)

将视图的不透明度设置为 0 到 1 之间的值,其中 0 表示视图完全透明,1 表示视图完全不透明。

void setChildSurfacePackage(SurfaceControlViewHost.SurfacePackage p)

显示嵌入此 SurfaceView 中的视图层次结构SurfaceControlViewHost.SurfacePackage 。

void setClipBounds(Rect clipBounds)

在此视图上设置一个矩形区域,绘制视图时将剪裁到该区域。

void setSecure(boolean isSecure)

控制是否应将表面视图的内容视为安全,以防止其出现在屏幕截图中或在非安全显示器上查看。

void setSurfaceLifecycle(int lifecycleStrategy)

控制此 SurfaceView 拥有的 Surface 的生命周期。

void setVisibility(int visibility)

设置此视图的可见性状态。

void setZOrderMediaOverlay(boolean isMediaOverlay)

控制表面视图的表面是否放置在窗口中另一个常规表面视图的顶部(但仍在窗口本身的后面)。

void setZOrderOnTop(boolean onTop)

控制表面视图的表面是否放置在其窗口的顶部。

SurfaceView 的 Protected 方法

void dispatchDraw(Canvas canvas)

由draw调用来绘制子视图。

void onAttachedToWindow()

当视图附加到窗口时调用此方法。

void onDetachedFromWindow()

当视图与窗口分离时调用此方法。

void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)

当该视图的焦点状态发生变化时,由视图系统调用。

void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

测量视图及其内容以确定测量的宽度和测量的高度。

boolean onSetAlpha(int alpha)

如果存在涉及 alpha 的 Transform,则调用。

void onWindowVisibilityChanged(int visibility)

当包含的窗口的可见性发生更改(在GONEINVISIBLE和 之间VISIBLE)时调用。

二、SurfaceView和View的不同之处

Android提供了View来进行绘图处理,在大部分情况下View都能满足绘图需求。大家都知道View是通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的间隔时间为16ms。如果在16ms内View完成了你所需要执行的所有操作,那么用户在视觉上,就不会产生卡顿的感觉;反之,如果操作的逻辑过多时,处理时间超过了一帧的时间周期,就会掉帧,从而使得用户感觉到卡顿。特别的需要频繁刷新的界面上,如游戏(60FPS以上)、高帧率视频等,就会不断阻塞主线程,从而导致界面卡顿。基于此Android提供SurfaceView来解决这种情况。

View SurfaceView
共享Surface 独立Surface
在主线程中进行画面更新 通常通过一个子线程来进行画面更新

SurfaceView和View一大不同:View通过invalidate方法通知系统来主动刷新界面的,但View的刷新是依赖于系统的VSYSC信号的,而且因为UI线程中的其他一些操作会导致掉帧卡顿。而对于SurfaceView而言,SurfaceView是在子线程中绘制图形,根据这一特性即可控制其显示帧率,通过简单地设置休眠时间,即可,并且由于在子线程中,一般不会引起UI卡顿。

Thread.sleep(50);即可以控制1s内刷新20次

三、SurfaceView的基本操作

1. 测试程序一:下面通过画圆来介绍SurfaceView的基本操作

public class MainActivity extends AppCompatActivity implements View.OnClickListener, SurfaceHolder.Callback {
    private final static String TAG = "lzl-test";
    private Button mButton;
    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private Paint mPaint = new Paint();
    private int mCircleRadius = 10;
    private boolean isRunning = false;
    private boolean isStart = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "onCreate...");

        // 获取 SurfaceView
        mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        // 调用getHolder()方法获取SurfaceHolder
        mSurfaceHolder = mSurfaceView.getHolder();
        // 通过 SurfaceHolder.addCallback方法设置:实现SurfaceHolder.Callback回调接口
        mSurfaceHolder.addCallback(this);

        // 绘图 启动/ 停止 按键
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(this);
        mButton.setText("启动");
    }

    @Override
    public void onClick(View v) {
        // 绘图 启动/ 停止 按键
        if (v.getId() == R.id.button) {
            isStart = !isStart;
            if (isStart) {
                mButton.setText("停止");
                Log.d(TAG, "按键按下:开始绘制");
                start();
            } else {
                mButton.setText("启动");
                Log.d(TAG, "按键按下:停止绘制");
                stop();
            }
        }
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
        Log.d(TAG, "surfaceCreated...");
        if (mSurfaceHolder == null) {
            // 调用getHolder()方法获取SurfaceHolder
            mSurfaceHolder = mSurfaceView.getHolder();
            // 通过 SurfaceHolder.addCallback方法设置:实现SurfaceHolder.Callback回调接口
            mSurfaceHolder.addCallback(this);
        }
        mPaint.setAntiAlias(true); // 设置画笔为无锯齿
        mPaint.setColor(Color.RED); // 设置画笔的颜色
        mPaint.setStrokeWidth(10); // 设置画笔的线宽
        mPaint.setStyle(Paint.Style.FILL); // 设置画笔的类型。STROK表示空心,FILL表示实心
        mPaint.setTextSize(30);
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        Log.d(TAG, "surfaceChanged...");
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
        Log.d(TAG, "surfaceDestroyed...");
        Log.d(TAG, "surfaceDestroyed:停止绘制");
        mSurfaceHolder = null;
        isStart = false;
        mButton.setText("启动");
        stop();
    }

    // 开始绘制
    public void start() {
        isRunning = true;
        new Thread() {
            @Override
            public void run() {
                while (isRunning) {
                    drawCircle();
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    // 停止绘制
    public void stop() {
        isRunning = false;
    }

    // 绘制图形
    private void drawCircle() {
        long now = System.currentTimeMillis();
        if (mSurfaceHolder != null) {
            Canvas canvas = mSurfaceHolder.lockCanvas();
            if (canvas != null) {
                // 设置画布为灰色背景色
                canvas.drawARGB(255, 55, 55, 55);
                // 画圆
                canvas.drawCircle(canvas.getWidth() / 2, canvas.getWidth() / 2, mCircleRadius, mPaint);

                if (mCircleRadius < canvas.getWidth() / 2) {
                    mCircleRadius++;
                } else {
                    mCircleRadius = 10;
                }
                if (canvas != null && mSurfaceHolder != null)
                    mSurfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }
}

layout XML:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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
        android:id="@+id/surfaceView"
        android:layout_width="350dp"
        android:layout_height="350dp"
        android:layout_marginTop="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="启动"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/surfaceView" />
</androidx.constraintlayout.widget.ConstraintLayout>

2. 测试程序二:下面通过touch划线来介绍SurfaceView的基本操作

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
    private final static String TAG = "lzl-test";
    private SurfaceView mSurfaceView;
    //SurfaceHolder
    private SurfaceHolder mHolder;
    //用于绘图的Canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;
    //画笔
    private Paint mPaint;
    //路径
    private Path mPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "onCreate...");

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); //设置屏幕不随手机旋转
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); //设置屏幕直向显示

        mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        mHolder = mSurfaceView.getHolder();
        mHolder.addCallback(this);

        /* 清屏 */
        findViewById(R.id.button_clear).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (v.getId() == R.id.button_clear) {
                    mPath.reset();
                }
            }
        });
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        Log.d(TAG, "surfaceCreated...");
        if (mHolder == null) {
            mHolder = mSurfaceView.getHolder();
            mHolder.addCallback(this);
        }

        mPath = new Path();
        //初始化画笔
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(6);
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        mSurfaceView.setFocusable(true);
        mSurfaceView.setFocusableInTouchMode(true);
        mSurfaceView.setKeepScreenOn(true);

        mIsDrawing = true;
        new Thread(runnable).start();
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        Log.d(TAG, "surfaceChanged...");
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        Log.d(TAG, "surfaceDestroyed...");
        mIsDrawing = false;
        mHolder = null;
    }

    private void draw() {
        if (mHolder != null) {
            Canvas canvas = null;
            try{
                //用于绘图的Canvas, 锁定画布并返回画布对象
                canvas = mHolder.lockCanvas();
                //接下去就是在画布上进行一下draw
                canvas.drawColor(Color.WHITE);
                canvas.drawPath(mPath,mPaint);
            } catch (Exception e){

            } finally {
                //当画布内容不为空时,才post,避免出现黑屏的情况。
                if(canvas !=null && mHolder != null)
                    mHolder.unlockCanvasAndPost(canvas);
            }
        }
    }

    private  Runnable runnable = new Runnable() {
        @Override
        public void run() {
            long start =System.currentTimeMillis();
            while(mIsDrawing){
                draw();
                long end = System.currentTimeMillis();
                if(end-start<100){
                    try{
                        Thread.sleep(100-end+start);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x=(int) event.getX();
        int y= (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "onTouchEvent: down");
                mPath.moveTo(x,y);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onTouchEvent: move");
                mPath.lineTo(x,y);
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onTouchEvent: up");
                break;
        }
        return true;
    }
}

四、测试程序

完整源码

百度网盘链接:百度网盘 请输入提取码 提取码:test

SurfaceViewTest和SufaceViewTest(android-studio-2022.3.1.18)目录

运行效果

备注:

参考:

SurfaceView和普通view的区别及简单使用

Android系统view与SurfaceView的基本使用及区别分析

点此查看Android应用开发学习笔记的完整目录

猜你喜欢

转载自blog.csdn.net/zsyf33078/article/details/132228674