美文网首页音视频多媒体科技音视频技术
Android音视频技术入门之绘制一张图片

Android音视频技术入门之绘制一张图片

作者: 安仔夏天勤奋 | 来源:发表于2018-05-06 16:41 被阅读58次

    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的生命周期管理有三个方法:

    1. SurfaceCreated
    2. SurfaceChanged
    3. SurfaceDestoryed

    如何使用SurfaceView呢?

    1. 获取SurfaceHolder对象,其是SurfaceView的内部类。
    2. 监听Surface生命周期。
      • 只有当native层的Surface创建完毕之后,才可以调用lockCanvas(),否则失败。
      • holder.Callback。
    3. 调用holder.lockCanvas()。
    4. 绘制
    5. 调用SurfaceHolder.unlockCanvasAndPost,将绘制内容post到Surface中

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

    SurfaceView的特点有那些

    • 具有独立的绘图表面Surface。

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

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

    SurfaceView的优缺点

    优点

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

    缺点

    1. 每次绘制都会优先绘制黑色背景,更新不及时会出现黑边现象。
    2. 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 怎么交互?

    SurfaceHolderSurfaceView 内部类,可以通过 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的绘画三要素

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

    一、Canvas

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

    二、Paint

    1. 直接通过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效果,其三,使用双缓冲机制,播放视频时画面更流畅。

    所以我也把我了解到的,学习到的基础知识分享给大家。希望对大家有帮助。

    相关文章

      网友评论

      • 721d739b6619:视频播放中,横竖屏切换尽管你更改了SurfaceView的宽高,视频内容也不会随宽高变化吧。你确定你实践过?
        安仔夏天勤奋:这里只是用SurfaceView如何绘制一张图片,如果录制视频还是要结合Camera的方向对图片进行方向调整的,可以看https://www.jianshu.com/p/96ee1b7e67e3

      本文标题:Android音视频技术入门之绘制一张图片

      本文链接:https://www.haomeiwen.com/subject/hzcurftx.html