Android音视频技术入门之绘制一张图片
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. */
@Deprecated
public static final int SURFACE_TYPE_NORMAL = 0;
/** @deprecated this is ignored, this value is set automatically when needed. */
@Deprecated
public static final int SURFACE_TYPE_HARDWARE = 1;
/** @deprecated this is ignored, this value is set automatically when needed. */
@Deprecated
public static final int SURFACE_TYPE_GPU = 2;
/** @deprecated this is ignored, this value is set automatically when needed. */
@Deprecated
public 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呢?
- 获取SurfaceHolder对象,其是SurfaceView的内部类。
- 监听Surface生命周期。
- 只有当native层的Surface创建完毕之后,才可以调用lockCanvas(),否则失败。
- holder.Callback。
- 调用holder.lockCanvas()。
- 绘制
- 调用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);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
isRunning = true;
mThread = new Thread(this);
mThread.start();//开启线程
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isRunning = false;
}
@Override
public void run() {
//不断地进行绘制
while (isRunning){
draw();
}
}
private void draw() {
//为什么要try catch 因为当view在主界面时有可能按Home键或Back键时回到界面,Surface被销毁了。
//这时有可能已经进入到draw() ,这时候获取的mCanvas可能为null。
// 还有一种可能,就是界面被销毁的,我们的线程还没有销毁,mCanvas可能为null。
try{
//获取Canvas
mCanvas = mHolder.lockCanvas();
if(mCanvas !=null){
//do something
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(mCanvas !=null){
//释放Canvas
mHolder.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 set
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// punch a whole in the view-hierarchy below us
canvas.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
的显示。
/**
* Return the SurfaceHolder providing access and control over this
* SurfaceView's underlying surface.
*
* @return SurfaceHolder The holder of the surface.
*/
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。
/**
* Gets a {@link Canvas} for drawing into the SurfaceView's Surface
*
* After drawing into the provided {@link Canvas}, the caller must
* invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
*
* The caller must redraw the entire surface.
* @return A canvas for drawing into the surface.
*/
@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
/**
* Posts the new contents of the {@link Canvas} to the surface and
* releases the {@link Canvas}.
*
* @param canvas The canvas previously obtained from {@link #lockCanvas}.
*/
@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 什么方法都不调用
横屏录制横屏播放,竖屏录制竖屏播放
通过以下方法可以获取到视频的宽高,根据视频的宽高就可以知道该视频是横屏还是竖屏录制的。
MediaPlayer# 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() {
@Override
public 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方法实现绘图操作
@Override
protected 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;
}
}
@Override
public 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);// 抗锯尺
}
@Override
protected 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效果,其三,使用双缓冲机制,播放视频时画面更流畅。
所以我也把我了解到的,学习到的基础知识分享给大家。希望对大家有帮助。