SurfaceView详解和使用

SurfaceView详解和使用双缓冲机制不管是什么操作系统,都有个“图像数据缓冲区”,存放颜色数据,每隔一段时间,把这些颜色数据投射到显示器上,我们就看到了各种各样的画面。对于应用程序来说,只需要把想要展示的内容存放到“图像数据缓

大家好,又见面了,我是你们的朋友全栈君。

双缓冲机制

不管是什么操作系统,都有个“图像数据缓冲区”,存放颜色数据,每隔一段时间,把这些颜色数据投射到显示器上,我们就看到了各种各样的画面。

对于应用程序来说,只需要把想要展示的内容存放到“图像数据缓冲区”就可以了,这个操作也基本是系统帮我们做了。这样的模式有个问题就是:如果系统每16ms投射一次图像数据,而我们的UI显示此时还没绘制完成,就只能显示一部分,剩下的一部分就是上一次是图像。

对于这种情况,系统一般会用背景色来填充未完成的部分,保证不会出现图像残影,但是这样又会导致闪屏:一个完整的图像经过多次投射才显示完,每次都有一部分从背景色变为图像,这样的体验就是闪屏。

解决方法就是双缓冲机制,对于绘制复杂的图像,比如游戏、视频等,会创建第二个“图像数据缓冲区”。绘制完成前,图像数据都是放在第二个缓冲区,绘制完成后,才会一次性复制到另一个缓冲区,这样就完美解决了闪屏的问题。
SurfaceView简介

一、Android图层关系
Android中,每个Activity对应一个显示图层,这个Activity里的所有控件都是显示在这个图层中。而SurfaceView跟其它控件不一样,它单独对应一个图层,跟Activity平级。但是在层次排列上,SurfaceView的图层在Activity图层的下面,所以需要在Activity图层上显示一块透明的区域,用于显示下面的SurfaceView图层。

那么,Activity怎么知道这块透明区域位置在哪、宽高多少呢?上面说的是显示图层不一样,但是布局的时候,SurfaceView依然是Activity的RootView的Child,所有的measure、layout等流程都是和其它控件一样的。

二、SurfaceView和普通View的区别
普通的View,onDraw()方法是在主线程中调用的,有两种情况下会被调用。一种是画面有变化(滑动、转场等)时,系统通知调用,另一种是主动调用invalidate()告诉系统,然后再等待系统的通知。总的来说,是一种被动等待刷新,不能控制刷新频率。

而SurfaceView不在Activity那个图层,绘制、刷新完全不受约束,想什么时候绘制、刷新都可以自由控制。

总的来说,SurfaceView主要就是绘制、刷新和显示过程跟普通View不一样而已,其他的都一样。缓存图像数据、显示等等这些事都是系统底层做好的,我们只需要关注绘制、刷新过程就行,SurfaceView开放的几个接口也基本都是绘制、刷新的。
SurfaceView相关接口

一、SurfaceView的接口

    public SurfaceHolder getHolder()
    SurfaceView只有一个常用接口getHolder(),返回一个SurfaceHolder对象,通过这个对象管理图层的绘制、刷新等操作。

二、SurfaceHolder的接口

    void addCallback(SurfaceHolder.Callback callback)
    通过这个方法添加一个callback,监听Surface图层的生命周期

    void setFixedSize(int width, int height)
    通过这个方法,设置Surface图层的大小,设置之后是不可变的。这个方法需要在UI线程调用。

    void setSizeFromLayout()
    设置Surface图层的大小根据容器(即Activity)的改变而改变,通过callback可以监听Surface的改变。这个方法也需要在UI线程调用。

    Canvas lockCanvas() 和 void unlockCanvasAndPost(Canvas canvas)
    通过lockCanvas()获取Surface对应的Canvas,进行绘制操作,绘制完成后,通过unlockCanvasAndPost()提交,提交完成后绘制的内容就会显示在屏幕上。注意,下次再次获取Canvas时,上一次绘制的内容可能丢失了,需要重绘。

    Rect getSurfaceFrame()
    返回Surface对应的Rect,但是坐标left、top的值总是0。注意:不要修改这个Rect。

    Surface getSurface()
    直接访问Surface,如有有需要的话,可以用这个方法获取Surface,直接进行操作。

SurfaceView自定义绘制demo

public class SurfaceViewDraw extends SurfaceView implements SurfaceHolder.Callback,Runnable{
    private static final String TAG = “SurfaceViewDraw”;
    private SurfaceHolder mHolder;
    private boolean surfaceAvailable;

    private Canvas mCanvas;
    private Paint mPaint;
    private Path mPath;

    private Thread mDrawThread;

    public SurfaceViewDraw(Context context) {
        super(context);
        initView();
    }

    public SurfaceViewDraw(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    private void initView() {
        mHolder = 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);

        setFocusable(true);
        setFocusableInTouchMode(true);
        setKeepScreenOn(true);
    }

    private void draw() {
        try {
            //锁定画布并返回画布对象
            mCanvas = mHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath, mPaint);
        } catch (Exception e) {
        } finally {
            //当画布内容不为空时才提交显示
            if (mCanvas != null)
                mHolder.unlockCanvasAndPost(mCanvas);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 在主线程中接收用户的触摸事件,记录下来。在子线程中,每隔100毫秒执行一次draw()方法,根据主线程中的触摸绘制图像
        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;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        surfaceAvailable = true;

        // surface创建完成后,就启动子线程,循环进行绘制操作。
        if(null == mDrawThread){
            mDrawThread = new Thread(this);
        }
        mDrawThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        surfaceAvailable = false;
    }

    @Override
    public void run() {
        // 每隔100毫秒,执行一次draw()方法,即刷新频率为100毫秒
        while (surfaceAvailable) {
            draw();

            // 严格一点的话应该把绘制时间算进去,这里主要演示用法,就不搞那么复杂了
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107

SurfaceView在播放视频中的使用

使用MediaPlayer播放音视频,通过setDisplay()方法设置一个Surface,让视频画面显示出来。这里面涉及到两个过程,一个是player本身的播放操作、控制,一个是Surface的初始化操作,这两个过程是相互独立的,互不影响,唯一的交集就是Surface创建完成后调用player的setDisplay()方法将Surface设置进去而已。

下面使用的player是简单封装过的,下面也会贴出主要代码,了解MediaPlayer详情可以看 – MediaPlaer详解和使用

SurfaceView在播放视频中的使用如下:

public class PlayerActivity extends Activity {
    private static final String TAG = “PlayerActivity”;
    private static final String VIDEO_DIR_0 = “/storage/sdcard0/zzzccc/videotest/video00.mp4”;
    private MyPlayer mPlayer;

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

        // 播放视频。MyPlayer的代码后面贴出
        mPlayer = new MyPlayer();
        mPlayer.play(this, Uri.parse(VIDEO_DIR_0));

        // 初始化Surface,创建成功后,调用player的setDisplay()方法设置Surface
        SurfaceView surfaceView = findViewById(R.id.sv_player);
        SurfaceHolder holder = surfaceView.getHolder();
        holder.setFormat(PixelFormat.RGB_888);
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                mPlayer.setDisplay(holder);
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                // 这里可以实时监听视频界面的变化
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                // 界面不可见时就会销毁,比如Activity的onStop()方法。这里不需要做额外的操作,player会自动处理
            }
        });
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

MyPlayer代码:

public class MyPlayer implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
    private MediaPlayer mPlayer;
    private boolean hasPrepared;

    private void initIfNecessary() {
        if (null == mPlayer) {
            mPlayer = new MediaPlayer();
            mPlayer.setOnErrorListener(this);
            mPlayer.setOnCompletionListener(this);
            mPlayer.setOnPreparedListener(this);
        }
    }

    public void play(Context context, Uri dataSource) {
        hasPrepared = false; // 开始播放前讲Flag置为不可操作
        initIfNecessary(); // 如果是第一次播放/player已经释放了,就会重新创建、初始化
        try {
            mPlayer.reset(); // 如果不是第一次播放,最好调一下reset()
            mPlayer.setDataSource(context, dataSource); // 设置曲目资源
            mPlayer.prepareAsync(); // 异步的准备方法
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        // release()会释放player、将player置空,所以这里需要判断一下
        if (null != mPlayer && hasPrepared) {
            mPlayer.start();
        }
    }

    public void pause() {
        if (null != mPlayer && hasPrepared) {
            mPlayer.pause();
        }
    }

    public void seekTo(int position) {
        if (null != mPlayer && hasPrepared) {
            mPlayer.seekTo(position);
        }
    }

    public void setDisplay(SurfaceHolder holder) {
        if (null != mPlayer) {
            mPlayer.setDisplay(holder);
        }
    }

    public void release() {
        hasPrepared = false;
        mPlayer.stop();
        mPlayer.release();
        mPlayer = null;
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        hasPrepared = true; // 准备完成后回调到这里
        start();
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        hasPrepared = false;
        // 通知调用处,调用play()方法进行下一个曲目的播放
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        hasPrepared = false;
        return false;
    }
}
———————
作者:thinkreduce
来源:CSDN
原文:https://blog.csdn.net/u014606081/article/details/79963733
版权声明:本文为博主原创文章,转载请附上博文链接!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/155058.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 抖音昵称html,抖音个性网名带特殊符号 带漂亮符号的抖音昵称[通俗易懂]

    抖音昵称html,抖音个性网名带特殊符号 带漂亮符号的抖音昵称[通俗易懂]抖音个性网名带特殊符号带漂亮符号的抖音昵称发布时间:2020-08-2019:16编辑:丽姐点击:次一、雾里有你二、离瑰ⅠThekhoi三、畏光四、刚刚好五、非洲小白脸六、纵容所有你七、涐の尐熊還恠嗎八、心盲°九、风吹旧夏十、爱成空@十一、烟祭smoke十二、回忆是束缚我的枷锁﹌十三、星期五╮的爱恋十四、拨打寂寞专线十五、祢.硪锝辛福呢?十六、╭那抹忧伤ソ属于谁十七、西…

    2022年5月29日
    41
  • 大数据架构之– Lambda架构「建议收藏」

    大数据架构之– Lambda架构「建议收藏」一、什么是Lambda架构Lambda架构由Storm的作者[NathanMarz]提出,根据维基百科的定义,Lambda架构的设计是为了在处理大规模数据时,同时发挥流处理和批处理的优势。通过批处理提供全面、准确的数据,通过流处理提供低延迟的数据,从而达到平衡延迟、吞吐量和容错性的目的。为了满足下游的即席查询,批处理和流处理的结果会进行合并。二、Lambda架构组成Lambda架构包含三层,BatchLayer、SpeedLayer和ServingLayer。架.

    2022年6月25日
    33
  • pycharm使用gpu运行_降低python程序cpu占用高

    pycharm使用gpu运行_降低python程序cpu占用高本人频繁在pycharm下run程序,经常终止,可能其后台运行的Python程序没有关闭,所以耗尽GPU资源。现象是占用GPU的进场ID为空,即nvidia-smi后,没有进程使用GPU,但每块GPU的内存确被使用很多。。。。。fuser-v/dev/nvidia*会发现很多Python在运行,故粗暴地kill这些进程ID就可以了。。。。。。。ID乍一看很多,杀死一两个就不剩几个了。。。。本方…

    2022年8月29日
    1
  • 论文心得:BatchNorm及其变体

    论文心得:BatchNorm及其变体本文记录BatchNormalization及其四个拓展,分别是BatchRenormalization、AdaBN、WeightNormalization、NormalizationPropagation

    2022年5月30日
    34
  • springboot mysql事物_SpringBoot事务详细简介[通俗易懂]

    springboot mysql事物_SpringBoot事务详细简介[通俗易懂]重要概念自动提交模式对于mysql数据库,默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式,下面是查看方式:查看是否自动提交命令(ON表示开启自动提交,值为1,OFF表示关闭自动提交,值为0):showvari…

    2022年6月5日
    88
  • 如何使用eclipse软件创建一个Java项目?[通俗易懂]

    如何使用eclipse软件创建一个Java项目?[通俗易懂]同学们在参加Java的时候老师肯定会教给你们如何去创建一个项目,这里怕有些同学没记住,所以单独为大家分享一篇如何使用eclipse软件创建一个Java项目教程,感觉有用的话收藏转发一下~eclipse创建Java项目教程1.首先我们需要打开eclipse软件,之后找到左上角的file选项卡,点击一下依次选择new-Javaproject选项,如图所示。2.随后会打开一个新建页面,在里面我们找到箭头所示的projectname处,在里面填写我们的Java项目名称,直接选择finish即可完成创建。

    2022年7月9日
    21

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号