1、定义

可以直接从内存或者DMA等硬件接口取得图像数据,是个非常重要的绘图容器。

它的特性是:可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。

2、实现

首先继承SurfaceView并实现SurfaceHolder.Callback接口
使用接口的原因:因为使用SurfaceView 有一个原则,所有的绘图工作必须得在Surface 被创建之后才能开始(Surface—表面,这个概念在 图形编程中常常被提到。基本上我们可以把它当作显存的一个映射,写入到Surface 的内容
                   
  可以被直接复制到显存从而显示出来,这使得显示速度会非常快),而在Surface 被销毁之前必须结束。所以Callback
中的surfaceCreated 和surfaceDestroyed 就成了绘图处理代码的边界。

需要重写的方法

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

     //在surface的大小发生改变时激发

 (2)public void surfaceCreated(SurfaceHolder holder){}

     //在创建时激发,一般在这里调用画图的线程。

 (3)public void surfaceDestroyed(SurfaceHolder holder) {}

     //销毁时激发,一般在这里将画图的线程停止、释放。

整个过程:继承SurfaceView并实现SurfaceHolder.Callback接口
----> SurfaceView.getHolder()获得SurfaceHolder对象
---->SurfaceHolder.addCallback(callback)添加回调函数---->SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布---->
Canvas绘画 ---->SurfaceHolder.unlockCanvasAndPost(Canvas
canvas)结束锁定画图,并提交改变,将图形显示。

3、SurfaceHolder
这里用到了一个类SurfaceHolder,可以把它当成surface的控制器,用来操纵surface。处理它的Canvas上画的效果和动画,控制表面,大小,像素等。
几个需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);
// 给SurfaceView当前的持有者一个回调对象。
(2)、abstract Canvas lockCanvas();
// 锁定画布,一般在锁定后就可以通过其返回的画布对象Canvas,在其上面画图等操作了。
(3)、abstract Canvas lockCanvas(Rect dirty);
// 锁定画布的某个区域进行画图等..因为画完图后,会调用下面的unlockCanvasAndPost来改变显示内容。
// 相对部分内存要求比较高的游戏来说,可以不用重画dirty外的其它区域的像素,可以提高速度。
(4)、abstract void unlockCanvasAndPost(Canvas canvas);
// 结束锁定画图,并提交改变。
4、实例:

主布局:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="net.bwie.surfaceview.MainActivity"> <Button
android:id="@+id/buffer_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SurfaceVie使用缓冲刷新UI几面"/> <Button
android:id="@+id/video_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SurfaceView播放视频"/> </LinearLayout>

主布局的Activity:

 public class MainActivity extends AppCompatActivity implements View.OnClickListener {

     protected Button mBufferBtn;
protected Button mVideoBtn; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_main);
initView();
} @Override
public void onClick(View view) {
Intent intent = new Intent();
if (view.getId() == R.id.buffer_btn) {
intent.setClass(this, BufferActivity.class);
} else if (view.getId() == R.id.video_btn) {
intent.setClass(this, VideoActivity.class);
}
startActivity(intent);
} private void initView() {
mBufferBtn = (Button) findViewById(R.id.buffer_btn);
mBufferBtn.setOnClickListener(MainActivity.this);
mVideoBtn = (Button) findViewById(R.id.video_btn);
mVideoBtn.setOnClickListener(MainActivity.this);
}
}
SurfaceVie使用缓冲刷新UI几面:
布局
 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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="net.bwie.surfaceview.activity.BufferActivity"> <SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/> </RelativeLayout>

Activity

 // SurfaceView配合子线程,使用双缓冲刷新UI界面
// SurfaceHolder:用于管理缓冲区Surface的类(生命周期)
public class BufferActivity extends AppCompatActivity implements SurfaceHolder.Callback { protected SurfaceView mSurfaceView;
private SurfaceHolder mHolder; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_buffer);
initView();
initSurfaceHolder();
} // 初始化Surface的管理者
private void initSurfaceHolder() {
mHolder = mSurfaceView.getHolder();
// 添加管理生命周期的接口回调
mHolder.addCallback(this);
} private void initView() {
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
} // 缓冲区创建
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d("1507", "surfaceCreated");
new DrawThread(this).start();
} // 缓冲区内容改变(子线程渲染UI的过程)
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d("1507", "surfaceChanged");
} // 缓冲区销毁
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d("1507", "surfaceDestroyed");
} // 绘制UI的子线程
private static class DrawThread extends Thread { // private BufferActivity mActivity; // 使用弱引用持有Activity的实例,
// 当Activity销毁时,当前线程会释放Activity,避免Activity无法释放导致的内存泄漏
private WeakReference<BufferActivity> mWeakReference; public DrawThread(BufferActivity activity) {
// mActivity = activity;
mWeakReference = new WeakReference<BufferActivity>(activity);
} @Override
public void run() {
super.run(); // 通过弱引用获取持有的Activity实例
BufferActivity activity = mWeakReference.get(); if (activity == null) {
return;
} // 获取SurfaceView的盖度
int height = activity.mSurfaceView.getHeight(); // 创建画笔
Paint paint = new Paint();
paint.setColor(Color.GREEN);// 画笔颜色
paint.setStrokeWidth(10);// 画笔粗细。注意:Java中设置的尺寸单位都是px
paint.setStyle(Paint.Style.FILL_AND_STROKE);// 设置实心
paint.setAntiAlias(true);// 设置是否抗锯齿 Canvas canvas = null;
for (int i = 0; i < height; i += 5) { // 获取Surface中的画布
canvas = activity.mHolder.lockCanvas();// 锁定画布 // 当SurfaceView随着Activity销毁时,缓冲区Surface也会随着销毁,无法获取缓冲区的画布
if (canvas == null) {
return;
} canvas.drawColor(Color.RED);// 设置画布背景覆盖之前的残影
// 使用画笔在画布上绘制指定形状
canvas.drawCircle(100, i + 50, 50, paint);// 圆心x坐标,圆心y坐标,半径,画笔 // 缓冲区的画布绘制完毕,需要解锁并提交给窗口展示
activity.mHolder.unlockCanvasAndPost(canvas); } }
} }
SurfaceView播放视频:
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
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="net.bwie.surfaceview.activity.VideoActivity"> <Button
android:id="@+id/play_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="播放"/> <net.bwie.surfaceview.widget.MyVideoSurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/> </LinearLayout>

Activity

/**
* 1、获取播放源
* 2、准备SurfaceView
* 3、多媒体交给MediaPlayer处理
*/
public class VideoActivity extends AppCompatActivity implements View.OnClickListener { protected MyVideoSurfaceView mSurfaceView;
protected Button mPlayBtn; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_video);
initView(); } // 运行、可见
@Override
protected void onStart() {
super.onStart();
} // 可交互
@Override
protected void onResume() {
super.onResume();
} private void play() {
String videoPath = Environment.getExternalStorageDirectory().getPath() +
"/VID_20171117_144736.3gp";// 外部存储根路径 mSurfaceView.playVideo(videoPath);
} private void initView() {
mSurfaceView = (MyVideoSurfaceView) findViewById(R.id.surface_view);
mPlayBtn = (Button) findViewById(R.id.play_btn);
mPlayBtn.setOnClickListener(VideoActivity.this);
} @Override
public void onClick(View view) {
if (view.getId() == R.id.play_btn) {
play();
}
}
}

MyVideoSurfaceView类:

 public class MyVideoSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

     private SurfaceHolder mHolder;
private MediaPlayer mMediaPlayer; public MyVideoSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs); init();
} private void init() {
// 获取Surface换朝哪个区的持有者
mHolder = getHolder();
mHolder.addCallback(this);
} // 设置播放源
public void playVideo(String path) {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
} try {
// 设置播放源
mMediaPlayer.setDataSource(path);
// 设置多媒体的显示部分:使用SurfaceHolder渲染画面
mMediaPlayer.setDisplay(mHolder);
mMediaPlayer.prepare();
mMediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
} } @Override
public void surfaceCreated(SurfaceHolder holder) { } @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override
public void surfaceDestroyed(SurfaceHolder holder) {
mMediaPlayer.release();
mMediaPlayer = null;
} }

别忘了加权限:

  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 

 
 

SurfaceView获取本地视频播放的更多相关文章

  1. VideoView获取本地视频播放

    主布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ...

  2. Win10《芒果TV》更新v3.5.0夏至版:会员尊享蓝光画质,关联本地视频播放

    在Win10秋季创意者更新前夕,Win10版<芒果TV>全平台同步更新夏至版v3.5.0,新增会员蓝光画质,关联本地视频播放,进一步提升使用体验. Win10版<芒果TV>V3 ...

  3. android获取本地图片并显示图片

    import java.io.FileNotFoundException; import android.content.ContentResolver; import android.content ...

  4. Linux编程获取本地IP

    #include <stdio.h> #include <sys/types.h> #include <ifaddrs.h> #include <netine ...

  5. Java获取本地IP地址

    import java.net.InetAddress; import java.net.UnknownHostException; public class IpTest { public stat ...

  6. Android开发之获取本地视频和获取自拍视频

    1.获取本地所有视频 public void getLoadMedia() { Cursor cursor = UILApplication.instance.getApplicationContex ...

  7. 获取本地IP地址信息

    2012-06-05    /// <summary>         /// 获取本地IP地址信息         /// </summary>         void G ...

  8. 用angular实时获取本地localStorage数据,实现一个模拟后台数据登入的效果

    研究了一上午,终于做出了,实时获取本地localStorage来模拟注册登入~~~ <!DOCTYPE html><html><head lang="en&qu ...

  9. Android之获取本地图片并压缩方法

    这两天在做项目时,做到上传图片功能一块时,碰到两个问题,一个是如何获取所选图片的路径,一个是如何压缩图片,在查了一些资料和看了别人写的后总算折腾出来了,在此记录一下. 首先既然要选择图片,我们就先要获 ...

随机推荐

  1. Shell 相互调用

    Shell 文件包含 和其他语言一样,Shell 也可以包含外部脚本.这样可以很方便的封装一些公用的代码作为一个独立的文件. Shell 文件包含的语法格式如下: . filename # 注意点号( ...

  2. Openstack单元测试工具简单说明

    一.Openstack 的单元测试工具介绍 1.unittest unittest: 是 Python 的标准库,提供了最基本的单元测试功能,包括 单元测试运行器(简称runner) 和 单元测试框架 ...

  3. 【JAVA】序列化

    好处有2: 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里). 利用序列化实现远程通信,即在网络上传送对象的字节序列. 序列化ID的作用: 简单来说,Java的序列化机制 ...

  4. 《Java多线程编程核心技术》——多线程与同步

    Java多线程 线程可以理解为是在进程中独立运行的子任务. Java多线程 使用方法 Java中实现多线程主要有以下两种方法: 继承Thread,而后实例化该对象调用start()即启动了新线程; 实 ...

  5. java学习--高效的除模取余运算(n-1)&hash

    没有测试过使用取余运算符和位运算符都做同一件事时的时间效率! 取余运算符% 如3除以2取余数 a = a%; 结果为1 上面是传统的方式进行求余运算. 需要先将10进制转成2进制到内存中进行计算,然后 ...

  6. j2ee高级开发技术课程第十四周

    RPC(Remote Procedure Call Protocol) RPC使用C/S方式,采用http协议,发送请求到服务器,等待服务器返回结果.这个请求包括一个参数集和一个文本集,通常形成“cl ...

  7. Docker Spring-boot

    docker 1.使用 sudo 或 root 权限登录 Centos. 2.确保 yum 包更新到最新. $ sudo yum update 3.执行 Docker 安装脚本. $ curl -fs ...

  8. Android so文件进阶 <一>

    0x00  前言   最近一段时间在弄android方面的东西,今天有人发了张截图,问:在要dump多大的内存? 一时之间我竟然想不起来ELF文件的哪个字段表示的是文件大小,虽然最后给出了解决方法,I ...

  9. Spring事务内方法调用自身事务 增强的三种方式

    ServiceA.java文件: 查看Spring Tx的相关日志:  可以看到只创建了一个事物ServiceA.service方法的事务,但是callSelf方法却没有被事务增强; 分析原因:Spr ...

  10. UVA 1605 Building for UN(思维)

    题目链接: https://cn.vjudge.net/problem/UVA-1605#author=0 /* 问题 设计一个包含若干层的联合国大厦,其中每一层都是等大的网格,每个格子分配给一个国家 ...