Android 的音视频入门学习,首先了解一下绘制图片。在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView,SurfaceView,自定义 View作绘制图片。下面我以SurfaceView作重点来讲,为什么不用其他的来作例子,分析完SurfaceView就是知道为什么要用SurfaceView作例子。

SurfaceView

我们以下面几个点来了解SurfaceView

  • SurfaceView 有那些相关类。
  • SurfaceView 有那些特点。
  • 如何使用SurfaceView呢。
  • SurfaceView的优缺。
  • SurfaceView 在视频开发中应用在那里。

SurfaceView 其实是继承了View ,但与View又有一些区别,View是通过 onDraw(Canvas canvas)方法中的Canvas去绘制自身显示有界面上,而SurfaceView则不需要onDraw方法,有人会有些疑问,如果SurfaceView不需要实现onDraw方法怎么去绘制自身呢?其实View是在UI线程中绘制的,SurfaceView是在子线程中绘制的(即在一个子线程中对自己进行绘制)。在子线程中绘制怎么拿到canvas呢?下面我们去了解SurfaceView 有那些相关类。

SurfaceView 有那些相关类。

有三个重要的类,分别如下:

  • Surface
  • SurfaceHolder
  • SurfaceView

Surface
我们看看Surface的源码

/*** Handle onto a raw buffer that is being managed by the screen compositor.** <p>A Surface is generally created by or from a consumer of image buffers (such as a* {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or* {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as* {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL},* {@link android.media.MediaPlayer#setSurface MediaPlayer}, or* {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw* into.</p>** <p><strong>Note:</strong> A Surface acts like a* {@link java.lang.ref.WeakReference weak reference} to the consumer it is associated with. By* itself it will not keep its parent consumer from being reclaimed.</p>*/
public class Surface implements Parcelable {private static final String TAG = "Surface";private static native long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)throws OutOfResourcesException;private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject);private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)throws OutOfResourcesException;private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);private static native void nativeRelease(long nativeObject);private static native boolean nativeIsValid(long nativeObject);......./*** Create Surface from a {@link SurfaceTexture}.** Images drawn to the Surface will be made available to the {@link* SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link* SurfaceTexture#updateTexImage}.** @param surfaceTexture The {@link SurfaceTexture} that is updated by this* Surface.* @throws OutOfResourcesException if the surface could not be created.*/public Surface(SurfaceTexture surfaceTexture) {if (surfaceTexture == null) {throw new IllegalArgumentException("surfaceTexture must not be null");}mIsSingleBuffered = surfaceTexture.isSingleBuffered();synchronized (mLock) {mName = surfaceTexture.toString();setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));}}/* called from android_view_Surface_createFromIGraphicBufferProducer() */private Surface(long nativeObject) {synchronized (mLock) {setNativeObjectLocked(nativeObject);}}........}

也不难看出,其实Surface就充当着Model层,也是一个原始数据的缓冲区,表面通常是由图像缓冲区的使用者创建的。

SurfaceHolder
看看SurfaceHolder的源码

/*** Abstract interface to someone holding a display surface.  Allows you to* control the surface size and format, edit the pixels in the surface, and* monitor changes to the surface.  This interface is typically available* through the {@link SurfaceView} class.* * <p>When using this interface from a thread other than the one running* its {@link SurfaceView}, you will want to carefully read the* methods* {@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated()}.*/
public interface SurfaceHolder {/** @deprecated this is ignored, this value is set automatically when needed. */@Deprecatedpublic static final int SURFACE_TYPE_NORMAL = 0;/** @deprecated this is ignored, this value is set automatically when needed. */@Deprecatedpublic static final int SURFACE_TYPE_HARDWARE = 1;/** @deprecated this is ignored, this value is set automatically when needed. */@Deprecatedpublic static final int SURFACE_TYPE_GPU = 2;/** @deprecated this is ignored, this value is set automatically when needed. */@Deprecatedpublic static final int SURFACE_TYPE_PUSH_BUFFERS = 3;/*** Exception that is thrown from {@link #lockCanvas} when called on a Surface* whose type is SURFACE_TYPE_PUSH_BUFFERS.*/public static class BadSurfaceTypeException extends RuntimeException {public BadSurfaceTypeException() {}public BadSurfaceTypeException(String name) {super(name);}}/*** A client may implement this interface to receive information about* changes to the surface.  When used with a {@link SurfaceView}, the* Surface being held is only available between calls to* {@link #surfaceCreated(SurfaceHolder)} and* {@link #surfaceDestroyed(SurfaceHolder)}.  The Callback is set with* {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.*/public interface Callback {/*** This is called immediately after the surface is first created.* Implementations of this should start up whatever rendering code* they desire.  Note that only one thread can ever draw into* a {@link Surface}, so you should not draw into the Surface here* if your normal rendering will be in another thread.* * @param holder The SurfaceHolder whose surface is being created.*/public void surfaceCreated(SurfaceHolder holder);/*** This is called immediately after any structural changes (format or* size) have been made to the surface.  You should at this point update* the imagery in the surface.  This method is always called at least* once, after {@link #surfaceCreated}.* * @param holder The SurfaceHolder whose surface has changed.* @param format The new PixelFormat of the surface.* @param width The new width of the surface.* @param height The new height of the surface.*/public void surfaceChanged(SurfaceHolder holder, int format, int width,int height);/*** This is called immediately before a surface is being destroyed. After* returning from this call, you should no longer try to access this* surface.  If you have a rendering thread that directly accesses* the surface, you must ensure that thread is no longer touching the * Surface before returning from this function.* * @param holder The SurfaceHolder whose surface is being destroyed.*/public void surfaceDestroyed(SurfaceHolder holder);}/*** Additional callbacks that can be received for {@link Callback}.*/public interface Callback2 extends Callback {/*** Called when the application needs to redraw the content of its* surface, after it is resized or for some other reason.  By not* returning from here until the redraw is complete, you can ensure that* the user will not see your surface in a bad state (at its new* size before it has been correctly drawn that way).  This will* typically be preceeded by a call to {@link #surfaceChanged}.** @param holder The SurfaceHolder whose surface has changed.*/public void surfaceRedrawNeeded(SurfaceHolder holder);}/*** Add a Callback interface for this holder.  There can several Callback* interfaces associated with a holder.* * @param callback The new Callback interface.*/public void addCallback(Callback callback);.........
}

从源码有可以看出,SurfaceHolder是以接口的形式给持有显示表面使用,允许你控制表面尺寸和格式,编辑表面的像素。监视对表面的更改。我们可以理解为SurfaceHolder充当控制层,管理Surface的生命周期,让SurfaceView来绘制Surface的数据。

SurfaceView

SurfaceView就是视图层,SurfaceView 中包含一个专门用于绘制的Surface ,Surface中包含了一个Canvas。如果细心的一点,也不难发现Surface、SurfaceHolder、SurfaceView其实就是一个MVC模式。

那么问题不了,那么如何获取到Canvas?
在SurfaceView中有一个getHolder() -> SurfaceHolder。那么Holder包含了Canvas(Canvas+管理SurfaceView的生命周期)。所以Canvas = holder.lockCanvas()。调用生命周期的holder.addCallback(Callback callback)。
SurfaceView的生命周期管理有三个方法:

  • SurfaceCreated
  • SurfaceChanged
  • SurfaceDestoryed

如何使用SurfaceView呢?

1、获取SurfaceHolder对象,其是SurfaceView的内部类。

  • 监听Surface生命周期。
  • 只有当native层的Surface创建完毕之后,才可以调用lockCanvas(),否则失败。
    holder.Callback。

2、调用holder.lockCanvas()。
3、绘制
4、调用SurfaceHolder.unlockCanvasAndPost,将绘制内容post到Surface中

注意:第3、4、5步是在子线程中执行的。

SurfaceView的特点有那些

具有独立的绘图表面Surface。

需要在宿主窗口上挖一个洞来显示自己,z轴比普通的window要小。

它的UI绘制可以在独立的线程中进行,这样就可以进行复杂的UI绘制,并且不会影响应用程序的主线程响应用户输入。

SurfaceView的优缺点

优点

  • 在一个子线程中对自己进行绘制,避免造成UI线程阻塞。
  • 高效复杂的UI效果。
  • 独立Surface,独立的Window。
  • 使用双缓冲机制,播放视频时画面更流畅。

缺点

  • 每次绘制都会优先绘制黑色背景,更新不及时会出现黑边现象。
  • Surface不在View hierachy中,它的显示也不受View的属性控制,平移,缩放等变换。

SurfaceView的基本知道了解得差不多了,那么我们写一个SurfaceView绘制图片的一个公共View的实现。

public class CommonSurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable {private SurfaceHolder mHolder;private Canvas mCanvas;//用于绘制的线程private Thread mThread;//线程状态的标记(线程的控制开关)private boolean isRunning;public CommonSurfaceView(Context context) {this(context,null);}public CommonSurfaceView(Context context, AttributeSet attrs) {this(context, attrs,0);}public CommonSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//初始化mHolder = getHolder();mHolder.addCallback(this);//管理生命周期//获取焦点setFocusable(true);setFocusableInTouchMode(true);//设置常量setKeepScreenOn(true);}@Overridepublic void surfaceCreated(SurfaceHolder holder) {isRunning = true;mThread = new Thread(this);mThread.start();//开启线程}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {isRunning = false;}@Overridepublic void run() {//不断地进行绘制while (isRunning){draw();}}private void draw() {//为什么要try catch 因为当view在主界面时有可能按Home键或Back键时回到界面,Surface被销毁了。//这时有可能已经进入到draw() ,这时候获取的mCanvas可能为null。// 还有一种可能,就是界面被销毁的,我们的线程还没有销毁,mCanvas可能为null。try{//获取CanvasmCanvas = mHolder.lockCanvas();if(mCanvas !=null){//do something}}catch (Exception e){e.printStackTrace();}finally {if(mCanvas !=null){//释放CanvasmHolder.unlockCanvasAndPost(mCanvas);}}}
}

总结一下有那些问题、疑虑

  • 不绘制任何东西,SurfaceView显示的是黑色?
  • SurfaceView 能绘制什么东西?
  • SurfaceVeiw双缓冲区
  • SurfaceView 和 SurfaceHolder 怎么交互?
  • SurfaceHolder与Surface的交互
  • SurfaceView 怎么进行旋转,透明操作的?
  • 一般视频播放器可以横竖屏切换,是如何实现的?
  • SurfaceView 和普通的View的区别?
  • SurfaceView 挖洞原理
  • SurfaceView 生命周期
  • 横屏录制横屏播放,竖屏录制竖屏播放

那么我们来解答一下上面的一些疑虑和问题,就浅析一下,有做得不好的请多多指出,谢谢。

不绘制任何东西,SurfaceView显示的是黑色?

每次更新视图时都会先将背景绘制成黑色。所以在移动或者缩放过程,会更新不及时时就会看黑边。

@Override
public void draw(Canvas canvas) {if (mDrawFinished && !isAboveParent()) {// draw() is not called when SKIP_DRAW is setif ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {// punch a whole in the view-hierarchy below uscanvas.drawColor(0, PorterDuff.Mode.CLEAR);}}super.draw(canvas);
}//这句话表示PorterDuff.Mode.CLEAR会将像素设置为0,也就是黑色
//Destination pixels covered by the source are cleared to 0.
public enum Mode {// these value must match their native equivalents. See SkXfermode.h/*** <p>*     <img src="{@docRoot}reference/android/images/graphics/composite_CLEAR.png" />*     <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption>* </p>* <p>\(\alpha_{out} = 0\)</p>* <p>\(C_{out} = 0\)</p>*/CLEAR       (0),
}

SurfaceView 能绘制什么东西?

从下面代码可以看到,SurfaceView 的绘制也是使用 Canvas 进行绘制的,绘制应该跟普通的 View 绘制差不多

/*** 绘制*/
private void draw() {if (radius > getWidth()) {return;}Canvas canvas = mHolder.lockCanvas();if (canvas != null) {canvas.drawCircle(300, 300, radius += 10, mPaint);mHolder.unlockCanvasAndPost(canvas);}
}

SurfaceVeiw双缓冲区

双缓冲:在运用时可以理解为:SurfaceView在更新视图时用到了两张 Canvas,一张 frontCanvas 和一张 backCanvas ,每次实际显示的是 frontCanvas ,backCanvas 存储的是上一次更改前的视图。当你在播放这一帧的时候,它已经提前帮你加载好后面一帧了,所以播放起视频很流畅。
当使用lockCanvas()获取画布时,得到的实际上是backCanvas 而不是正在显示的 frontCanvas ,之后你在获取到的 backCanvas 上绘制新视图,再 unlockCanvasAndPost(canvas)此视图,那么上传的这张 canvas 将替换原来的 frontCanvas 作为新的frontCanvas ,原来的 frontCanvas 将切换到后台作为 backCanvas 。例如,如果你已经先后两次绘制了视图A和B,那么你再调用 lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你将重绘的 A 视图上传,那么 A 将取代 B 作为新的 frontCanvas 显示在SurfaceView 上,原来的B则转换为backCanvas。

相当与多个线程,交替解析和渲染每一帧视频数据。

surfaceholder.lockCanvas--surfaceholder.unlockCanvasAndPost

SurfaceView 和 SurfaceHolder 怎么交互?

SurfaceHolder 是 SurfaceView 内部类,可以通过 SurfaceView.getHolder() 即可获取对应的 SurfaceHolder 对象。

通过getHolder() 就可以将SurfaceHolder,然后将其传递给MediaPlayer或者Camera 显示出来。实际上就是通过SurfaceHolder去控制SurfaceView的显示。

public SurfaceHolder getHolder() {return mSurfaceHolder;
}

SurfaceHolder与Surface的交互

SurfaceHolder 是一个接口,它具体的实现在 SurfaceView 中定义的一个内部类。对于 SurfaceHolder 的操作,实际上是操作Surface 的相关接口。

因为 Surface 会牵扯到 native 层的 Surface ,只有 Native 层的 Surface 创建成功之后,我们才能真正开始去绘制我们的视图。

那么如何去捕获到这个 Surface 的创建生命周期呢?

注册 SurfaceHolder.Callback 接口,监听这个接口的回调:

surfaceCreated
播放视频

surfaceDestroy
停止视频播放

Canvas canvas = mHolder.lockCanvas();
if (canvas != null) {canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher, null), 0, 0, mPaint);mHolder.unlockCanvasAndPost(canvas);
}

mHolder.lockCanvas(); 实际获取的是 Surface 中的 Canvas。

@Override
public Canvas lockCanvas() {return internalLockCanvas(null);
}private final Canvas internalLockCanvas(Rect dirty) {mSurfaceLock.lock();Canvas c = null;if (!mDrawingStopped && mWindow != null) {try {//实际调用的是 surface.lockCancas()c = mSurface.lockCanvas(dirty);} catch (Exception e) {Log.e(LOG_TAG, "Exception locking surface", e);}}...return null;
}

canvas.drawXXX();

  • 在 Canvas 中绘制内容。

mHolder.unlockCanvasAndPost(canvas);将 绘制在 Canvas 中的内容刷新到 Surface 中。

//将 backcanvas 中的内容刷新到 surface 中并且释放这个 canvas
@Override
public void unlockCanvasAndPost(Canvas canvas) {mSurface.unlockCanvasAndPost(canvas);mSurfaceLock.unlock();
}

SurfaceView 怎么进行旋转,透明操作的?

  • 普通View旋转后,View的内容也跟着同步做了旋转.
  • SurfaceView在旋转之后,其显示内容并没有跟着一起旋转.

比喻:这就好比在墙上开了一个窗(Surface),通过窗口可以看外面的花花世界,但窗口无论怎样变化,窗外面的世界是不会跟着窗口一同变化。

一般视频播放器可以横竖屏切换,是如何实现的?
在 Activity 中覆写 onConfigurationChanged 方法就可以。根据横竖屏切换,修改 SurfaceView 的 Parameter 的宽高参数即可。

android:configChanges="orientation|keyboardHidden|screenSize"
@Override
public void onConfigurationChanged(Configuration newConfig) {  super.onConfigurationChanged(newConfig);  if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {  //变成横屏了    }   else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {  //变成竖屏了 }
}

SurfaceView 挖洞原理

挖洞原理了解之后再补上吧

SurfaceView 和普通的View的区别?

  • surfaceView是在一个新起的单独线程中可以重新绘制画面。
  • View必须在UI的主线程中更新画面。
  • 那么在UI的主线程中更新画面可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。
  • 当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。

SurfaceView 生命周期

使用:双缓冲
导致:需要更多的内存开销
为了节约系统内存开销:

SurfaceView 可见时 -> 创建 SurfaceHolder
SurfaecView 不可见时 -> 摧毁 SurfaceHolder

1、程序打开
Activity 调用顺序:onCreate()->onStart()->onResume()
SurfaceView 调用顺序: surfaceCreated()->surfaceChanged()

2、程序关闭(按 BACK 键)
Activity 调用顺序:onPause()->onStop()->onDestory()
SurfaceView 调用顺序: surfaceDestroyed()

3、程序切到后台(按 HOME 键)
Activity 调用顺序:onPause()->onStop()
SurfaceView 调用顺序: surfaceDestroyed()

4、程序切到前台
Activity 调用顺序: onRestart()->onStart()->onResume()
SurfaceView 调用顺序: surfaceChanged()->surfaceCreated()

5、屏幕锁定(挂断键或锁定屏幕)
Activity 调用顺序: onPause()
SurfaceView 什么方法都不调用

6、屏幕解锁
Activity 调用顺序: onResume()
SurfaceView 什么方法都不调用

横屏录制横屏播放,竖屏录制竖屏播放
通过以下方法可以获取到视频的宽高,根据视频的宽高就可以知道该视频是横屏还是竖屏录制的。

public void onVideoSizeChanged(MediaPlayer mp, int width, int height)

横屏判断:width>height
旋转屏幕:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

竖屏录制:height>width
旋转屏幕:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

mediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {@Overridepublic void onVideoSizeChanged(MediaPlayer mp, int width, int height) {Log.e(TAG, "onVideoSizeChanged:WIDTH>>" + width);Log.e(TAG, "onVideoSizeChanged:HEIGHT>>" + height);if (width > height) {//横屏录制if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);}} else {//竖屏录制if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}}}
});

View的绘制要知道的知识
View的绘制其实是在UI线程(实现onCanvas方法进行绘制)。如果进行绘制高效复杂的UI,最好不用自定义View。要用SurfaceView进行绘制。

View的绘画三要素

  • Canvas (画布,绘制BitMap操作)
  • Paint (绘制的画笔Paint,颜色、样式)
  • Path (路径)

一、Canvas

  • 如果直接extends View 可以重写onDraw(Canvas canvas)方法,直接用里面的canvas进行绘制。
  • 可以直接利用Activity的绘制机制,用lockCanvas()方法来获得当前的Activity的Canvas。
  • 在SurfaceView中,同2可以利用SurfaceHolder的对象的lockCanvas()方法来Canvas。

二、Paint

直接通过new关键字来实例化,然后通过Paint对象来对画笔进行相应的设置:
如:

  • 1.1 去锯齿setAntiAlia(true)
  • 1.2 去抖动setDither(true)
  • 1.3 设置图层混合模式setXfermode(Xfermode,xfermode)

三、 Path

  • 1、Path路径 直接用new来实例化
  • 2、通过path对象设置想要画图的轨迹或路线,如:矩形 、三角形 、圆、曲线等

实现一个自定义View,代码如下:

//自定义绘图类
public class BallView extends View {private Paint paint;        //定义画笔private float cx = 150;      //圆点默认X坐标private float cy = 250;      //圆点默认Y坐标private int radius = 60;    // 半径//定义颜色数组private int colorArray[] = {Color.BLACK,Color.BLACK,Color.GREEN,Color.YELLOW, Color.RED};private int paintColor = colorArray[0]; //定义画笔默认颜色private int screenW;        //屏幕宽度private int screenH;        //屏幕高度public BallView(Context context,int screenW,int screenH) {super(context);this.screenW=screenW;this.screenH=screenH;//初始化画笔initPaint();}private void initPaint(){paint = new Paint();//设置消除锯齿paint.setAntiAlias(true);//设置画笔颜色paint.setColor(paintColor);}//重写onDraw方法实现绘图操作@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//将屏幕设置为白色canvas.drawColor(Color.WHITE);//修正圆点坐标revise();//随机设置画笔颜色setPaintRandomColor();//绘制小圆作为小球canvas.drawCircle(cx, cy, radius, paint);}//为画笔设置随机颜色private void setPaintRandomColor(){Random rand = new Random();int randomIndex = rand.nextInt(colorArray.length);paint.setColor(colorArray[randomIndex]);}//修正圆点坐标private void revise(){if(cx <= radius){cx = radius;}else if(cx >= (screenW-radius)){//防止出边界cx = screenW-radius;}if(cy <= radius){cy = radius;}else if(cy >= (screenH-radius)){//防止出边界cy = screenH-radius;}}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// 按下cx = (int) event.getX();cy = (int) event.getY();// 通知重绘postInvalidate();   //该方法会调用onDraw方法,重新绘图break;case MotionEvent.ACTION_MOVE:// 移动cx = (int) event.getX();cy = (int) event.getY();// 通知重绘postInvalidate();break;case MotionEvent.ACTION_UP:// 抬起cx = (int) event.getX();cy = (int) event.getY();// 通知重绘postInvalidate();break;}/** 备注1:此处一定要将return super.onTouchEvent(event)修改为return true,原因是:* 1)父类的onTouchEvent(event)方法可能没有做任何处理,但是返回了false。* 2)一旦返回false,在该方法中再也不会收到MotionEvent.ACTION_MOVE及MotionEvent.ACTION_UP事件。*///return super.onTouchEvent(event);return true;}
}

懂得运用View的绘画三要素,画出自己想要的图也不难。

ImageView 绘制图片就不多说了,看一下例子吧

public class RoundImageView extends ImageView {private Bitmap mBitmap;private Rect mRect = new Rect();private PaintFlagsDrawFilter pdf = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG);private Paint mPaint = new Paint();private Path mPath=new Path();public RoundImageView(Context context, AttributeSet attrs) {super(context, attrs);init();}//传入一个Bitmap对象public void setBitmap(Bitmap bitmap) {this.mBitmap = bitmap;}private void init() {mPaint.setStyle(Paint.Style.STROKE);mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);mPaint.setAntiAlias(true);// 抗锯尺}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if(mBitmap == null){return;}mRect.set(0,0,getWidth(),getHeight());canvas.save();canvas.setDrawFilter(pdf);mPath.addCircle(getWidth() / 2, getWidth() / 2, getHeight() / 2, Path.Direction.CCW);canvas.clipPath(mPath, Region.Op.REPLACE);canvas.drawBitmap(mBitmap, null, mRect, mPaint);canvas.restore();}
}

综上所述,为什么视频技术入门要先了解图片绘制,那么图片绘制的API也有多种,为什么选择用SurfaceView这个API,因为其一,绘制是在子线程中进行绘制的,其二,可能绘制出高效复杂的UI效果,其三,使用双缓冲机制,播放视频时画面更流畅。

原创作者:安仔夏天勤奋,原文链接:https://www.jianshu.com/p/70912c55a03b

欢迎关注我的微信公众号「码农突围」,分享Python、Java、大数据、机器学习、人工智能等技术,关注码农技术提升•职场突围•思维跃迁,20万+码农成长充电第一站,陪有梦想的你一起成长。

音视频入门之如何绘制一张图片相关推荐

  1. 吹爆系列:教科书级别的Android音视频入门进阶学习手册,学完我成功“挤进”了抖音音视频开发岗

    Android开发工作两年,真的是感觉Android应用层开发没什么前景了,于是打算在网络安全,智能硬件,音视频这几个方向发展,考虑了一段时间,最终决定选择音视频.理由就不说了,既然选择了就要好好深耕 ...

  2. 安卓音视频入门难,老司机分享音视频开发学习技巧与痛点

    最近在写文章的时候,发现很多读者给我私信提问,想了解安卓音视频这块, 很多同学也对音视频开发有点浓厚的兴趣,但奈何没有系统的学习知识,仅靠自己苦苦钻研确实力不从心,今天就借这个机会分享一下我学习音视频 ...

  3. 堪称教科书级别的Android音视频入门进阶学习手册,开源分享

    概述 随着整个互联网的崛起,数据传递的形式也在不断升级变化,总的流行趋势如下: 纯文本的短信,QQ -> 空间,微博,朋友圈的图片文字结合 -> 微信语音 -> 各大直播软件 -&g ...

  4. 音视频入门-02-RGB拼图

    音视频入门文章目录 图片 & 像素点 & RGB 平时浏览的图片看不出像素点: 图片放大时,可以看出图片是一个个像素点组成的: 每个像素点的颜色可以用 RGB 表示: RGB 拼图 既 ...

  5. 音视频入门-10-使用libyuv对YUV数据进行缩放、旋转、镜像、裁剪、混合

    音视频入门文章目录 libyuv libyuv 是 Google 开源的实现各种 YUV 与 RGB 之间相互转换.旋转.缩放等的库.它是跨平台的,可在 Windows.Linux.Mac.Andro ...

  6. 音视频入门-17-GIF文件格式详解

    * 音视频入门文章目录 * GIF 文件格式解析 图像互换格式主要分为两个版本,即图像互换格式 87a 和图像互换格式 89a. 图像互换格式 87a:是在 1987 年制定的版本. 图像互换格式 8 ...

  7. 音视频入门系列-音视频基础知识篇(录播、点播、直播)

    在学习音视频技术前,笔者还是希望可以分享给小伙伴们一些音视频方便的基本概念,掌握这些概念,有助于大家对于音视频有一个更直观和清晰的了解. 话不多说,今天笔者给大家分享下:录播.点播和直播. 录播:录播 ...

  8. 音视频入门 (iOS上fdk-aac的交叉编译)

    前篇音视频入门记录了我对音视频的一些基础理解.现在将我在交叉编译ffmpeg之前的一些准备工作记录一下. 本文记录fdk-aac的交叉编译,首先需要说明几个问题: 基本概念 交叉编译:有过移动开发经验 ...

  9. 音视频入门系列-服务器篇(nginx-rtmp 的部署)

    本篇文章,我们详细介绍下使用nginx-rtmp来部署一套流媒体服务器,并用FFmpeg来推流,VLC来拉流. RTMP是Real Time Messaging Protocol(实时消息传输协议)的 ...

最新文章

  1. leetcode : 基础技巧
  2. linux分区转换gpt命令,Linux中磁盘如何转换GPT格式
  3. 【面向对象】实现继承的正确实践和不当实践
  4. java在枚举方法中调方法_java – 值方法如何在枚举中工作
  5. 【玩转Golang】 自定义json序列化对象时,非法字符错误原因
  6. 又一个统计浏览器历史纪录的扩展,支持 Firefox/Chrome
  7. 主成分分析二级指标权重_主成分分析确定指标权重的问题_主成分分析法确定权重...
  8. 利用Word2Vec模型训练Word Embedding,并进行聚类分析
  9. 四大汽车总线:LIN、CAN、FlexRay、MOST简述
  10. Chrome 优化指南
  11. gentoo 下Local time zone must be set--see zic manual page解决办法[原创]
  12. android 评论发表情,安卓手机怎么在微信朋友圈评论发表情包?
  13. kill 和 kill -9 的区别
  14. uniapp拨打电话
  15. Java高并发程序设计(三)——JDK并发包(二)
  16. CSS 让背景图片全部显示,填满父div
  17. Linux系统调用列表
  18. ogg转mp3格式转换器
  19. 对于学习新技术的思考
  20. caj转换成word免费转换怎么转换?

热门文章

  1. Q3c「最大可以支持多大容量的硬盘?」AirDisk
  2. vim quickfix——最灵活的quickfix
  3. 矿机价格又双叕暴跌!个人挖不到收益,区块已经垄断
  4. shiro的学习使用
  5. 综合布线可视化运维管理平台与电子配线架的区别
  6. IOS学习笔记56-IOS7状态栏适配方法一
  7. 苹果双卡双待买哪款合适_2020年买哪款苹果手机好?
  8. 海康RTSP转flv并实现h5页面播放
  9. 多言统计及R语言建模按组距为300编制频数表,计算频数,频率和累积频率表,并绘制直方图
  10. Dropout技术之随机神经元与随机深度