音視頻入門之如何繪製一張圖片

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的源碼

<code>/**
* Handle onto a raw buffer that is being managed by the screen compositor.
*
*

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.


*
*

Note: 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.


*/
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);
}
}

........

}
/<code>

也不難看出,其實Surface就充當著Model層,也是一個原始數據的緩衝區,表面通常是由圖像緩衝區的使用者創建的。

SurfaceHolder看看SurfaceHolder的源碼

<code>/**
* 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.
*
*

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);

.........
}

/<code>

從源碼有可以看出,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的實現。

<code>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);
}
}

}
}
/<code>

總結一下有那些問題、疑慮

  • 不繪製任何東西,SurfaceView顯示的是黑色?
  • SurfaceView 能繪製什麼東西?
  • SurfaceVeiw雙緩衝區
  • SurfaceView 和 SurfaceHolder 怎麼交互?
  • SurfaceHolder與Surface的交互
  • SurfaceView 怎麼進行旋轉,透明操作的?
  • 一般視頻播放器可以橫豎屏切換,是如何實現的?
  • SurfaceView 和普通的View的區別?
  • SurfaceView 挖洞原理
  • SurfaceView 生命週期
  • 橫屏錄製橫屏播放,豎屏錄製豎屏播放

那麼我們來解答一下上面的一些疑慮和問題,就淺析一下,有做得不好的請多多指出,謝謝。

不繪製任何東西,SurfaceView顯示的是黑色?

每次更新視圖時都會先將背景繪製成黑色。所以在移動或者縮放過程,會更新不及時時就會看黑邊。

<code>@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
/**
*


*


* <figcaption>Destination pixels covered by the source are cleared to 0./<figcaption>
*


*

\\(\\alpha_{out} = 0\\)


*

\\(C_{out} = 0\\)


*/
CLEAR (0),
}
/<code>

SurfaceView 能繪製什麼東西?

從下面代碼可以看到,SurfaceView 的繪製也是使用 Canvas 進行繪製的,繪製應該跟普通的 View 繪製差不多

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

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。

相當與多個線程,交替解析和渲染每一幀視頻數據。

<code>surfaceholder.lockCanvas--surfaceholder.unlockCanvasAndPost
/<code>

SurfaceView 和 SurfaceHolder 怎麼交互?

SurfaceHolder 是 SurfaceView 內部類,可以通過 SurfaceView.getHolder() 即可獲取對應的 SurfaceHolder 對象。

通過getHolder() 就可以將SurfaceHolder,然後將其傳遞給MediaPlayer或者Camera 顯示出來。實際上就是通過SurfaceHolder去控制SurfaceView的顯示。

<code>public SurfaceHolder getHolder() {
return mSurfaceHolder;
}
/<code>

SurfaceHolder與Surface的交互

SurfaceHolder 是一個接口,它具體的實現在 SurfaceView 中定義的一個內部類。對於 SurfaceHolder 的操作,實際上是操作Surface 的相關接口。

因為 Surface 會牽扯到 native 層的 Surface ,只有 Native 層的 Surface 創建成功之後,我們才能真正開始去繪製我們的視圖。

那麼如何去捕獲到這個 Surface 的創建生命週期呢?

註冊 SurfaceHolder.Callback 接口,監聽這個接口的回調:

surfaceCreated播放視頻

surfaceDestroy停止視頻播放

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

mHolder.lockCanvas(); 實際獲取的是 Surface 中的 Canvas。

<code>@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;
}
/<code>

canvas.drawXXX();

  • 在 Canvas 中繪製內容。

mHolder.unlockCanvasAndPost(canvas);將 繪製在 Canvas 中的內容刷新到 Surface 中。

<code>//將 backcanvas 中的內容刷新到 surface 中並且釋放這個 canvas
@Override

public void unlockCanvasAndPost(Canvas canvas) {
mSurface.unlockCanvasAndPost(canvas);
mSurfaceLock.unlock();
}
/<code>

SurfaceView 怎麼進行旋轉,透明操作的?

  • 普通View旋轉後,View的內容也跟著同步做了旋轉.
  • SurfaceView在旋轉之後,其顯示內容並沒有跟著一起旋轉.

比喻:這就好比在牆上開了一個窗(Surface),通過窗口可以看外面的花花世界,但窗口無論怎樣變化,窗外面的世界是不會跟著窗口一同變化。

一般視頻播放器可以橫豎屏切換,是如何實現的?在 Activity 中覆寫 onConfigurationChanged 方法就可以。根據橫豎屏切換,修改 SurfaceView 的 Parameter 的寬高參數即可。

<code>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) {
//變成豎屏了
}
}
/<code>

SurfaceView 挖洞原理

挖洞原理了解之後再補上吧

SurfaceView 和普通的View的區別?

  • surfaceView是在一個新起的單獨線程中可以重新繪製畫面。
  • View必須在UI的主線程中更新畫面。
  • 那麼在UI的主線程中更新畫面可能會引發問題,比如你更新畫面的時間過長,那麼你的主UI線程會被你正在畫的函數阻塞。那麼將無法響應按鍵,觸屏等消息。
  • 當使用surfaceView 由於是在新的線程中更新畫面所以不會阻塞你的UI主線程。

SurfaceView 生命週期

使用:雙緩衝導致:需要更多的內存開銷為了節約系統內存開銷:

SurfaceView 可見時 -> 創建 SurfaceHolderSurfaecView 不可見時 -> 摧毀 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 什麼方法都不調用

橫屏錄製橫屏播放,豎屏錄製豎屏播放通過以下方法可以獲取到視頻的寬高,根據視頻的寬高就可以知道該視頻是橫屏還是豎屏錄製的。

<code>public void onVideoSizeChanged(MediaPlayer mp, int width, int height)
/<code>

橫屏判斷:width>height旋轉屏幕:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

豎屏錄製:height>width旋轉屏幕:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

<code>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);
}
}
}
});
/<code>

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,代碼如下:

<code>//自定義繪圖類
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;
}
}
/<code>

懂得運用View的繪畫三要素,畫出自己想要的圖也不難。

ImageView 繪製圖片就不多說了,看一下例子吧

<code>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();
}
}
/<code>

綜上所述,為什麼視頻技術入門要先了解圖片繪製,那麼圖片繪製的API也有多種,為什麼選擇用SurfaceView這個API,因為其一,繪製是在子線程中進行繪製的,其二,可能繪製出高效複雜的UI效果,其三,使用雙緩衝機制,播放視頻時畫面更流暢。

原創作者:安仔夏天勤奮,原文鏈接:https://www.jianshu.com/p/70912c55a03b


分享到:


相關文章: