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
版权声明:本文为博主原创文章,转载请附上博文链接!

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

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