diff --git a/AudioVideo/README.md b/AudioVideo/README.md new file mode 100644 index 00000000..de09b5cb --- /dev/null +++ b/AudioVideo/README.md @@ -0,0 +1,4 @@ +# Audio/Video 杂记 + +- [通用视频解码播放流程](通用视频解码播放流程.md) + diff --git a/AudioVideo/SDL.md b/AudioVideo/SDL.md new file mode 100644 index 00000000..02b79fb9 --- /dev/null +++ b/AudioVideo/SDL.md @@ -0,0 +1,73 @@ +--- +typora-copy-images-to: ./image +--- + +## SDL + +### 简介 + +SDL(Simple DirectMedia Layer)库的作用就是封装了复杂的音视频底层交互工作,简化了音视频处理的难度。 + +**特点:** 开源、跨平台。 + +### 结构 + +![SDL结构](image/SDL结构.png) + +它是对底层进行了封装,最终还是调用的平台底层接口与硬件进行交互。 + +### SDL 流程 + +![SDL流程](image/SDL流程.jpg) + +### SDL 主要函数 + +| 函数 | 简介 | +| -------------------- | -------------------------- | +| SDL_Init() | 初始化 SDL 系统。 | +| SDL_CreateWindow() | 创建窗口 SDL_Window。 | +| SDL_CreateRenderer() | 创建渲染器 SDL_Renderer。 | +| SDL_CreateTexture() | 创建纹理 SDL_Texture。 | +| SDL_UpdateTexture() | 设置纹理数据。 | +| SDL_RenderCopy() | 将纹理的数据拷贝给渲染器。 | +| SDL_RenderPresent() | 显示。 | +| SDL_Delay() | 工具函数,用于延时。 | +| SDL_Quit() | 退出 SDL 系统。 | + +### SDL 数据结构 + +![SDL数据结构](image/SDL数据结构.jpg) + +**数据结构简介:** + +| 结构 | 简介 | +| ------------ | -------------------- | +| SDL_Window | 代表一个“窗口”。 | +| SDL_Renderer | 代表一个“渲染器”。 | +| SDL_Texture | 代表一个“纹理”。 | +| SDL_Rect | 一个简单的矩形结构。 | + +### SDL 事件和多线程 + +#### **SDL 多线程** + +| 函数 | 简介 | +| ------------------ | -------------- | +| SDL_CreateThread() | 创建一个线程。 | +| SDL_Thread() | 线程的句柄。 | + +#### **SDL 事件** + +**函数:** + +| 函数 | 简介 | +| --------------- | -------------- | +| SDL_WaitEvent() | 等待一个事件。 | +| SDL_PushEvent() | 发送一个事件。 | + +**数据结构:** + +| 结构 | 简介 | +| ----------- | -------------- | +| SDL_Event() | 代表一个事件。 | + diff --git "a/AudioVideo/image/SDL\346\225\260\346\215\256\347\273\223\346\236\204.jpg" "b/AudioVideo/image/SDL\346\225\260\346\215\256\347\273\223\346\236\204.jpg" new file mode 100755 index 00000000..0fa937b7 Binary files /dev/null and "b/AudioVideo/image/SDL\346\225\260\346\215\256\347\273\223\346\236\204.jpg" differ diff --git "a/AudioVideo/image/SDL\346\265\201\347\250\213.jpg" "b/AudioVideo/image/SDL\346\265\201\347\250\213.jpg" new file mode 100755 index 00000000..18105033 Binary files /dev/null and "b/AudioVideo/image/SDL\346\265\201\347\250\213.jpg" differ diff --git "a/AudioVideo/image/SDL\347\273\223\346\236\204.png" "b/AudioVideo/image/SDL\347\273\223\346\236\204.png" new file mode 100644 index 00000000..9d150d37 Binary files /dev/null and "b/AudioVideo/image/SDL\347\273\223\346\236\204.png" differ diff --git "a/AudioVideo/image/\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.gliffy" "b/AudioVideo/image/\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.gliffy" new file mode 100644 index 00000000..f4a83d31 --- /dev/null +++ "b/AudioVideo/image/\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.gliffy" @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.1","metadata":{"title":"untitled","revision":0,"exportBorder":false},"embeddedResources":{"index":0,"resources":[]},"stage":{"objects":[{"x":20,"y":580,"rotation":0,"id":3,"uid":"com.gliffy.shape.network.network_v3.home.speakers","width":74,"height":100,"lockAspectRatio":true,"lockShape":false,"order":36,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.speakers_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":69,"uid":null,"width":75,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"below","hposition":"none","html":"

音频驱动/设备

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":264,"y":580,"rotation":0,"id":9,"uid":"com.gliffy.shape.network.network_v3.home.tv_flatscreen","width":76,"height":100,"lockAspectRatio":true,"lockShape":false,"order":35,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.tv_flatscreen_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":70,"uid":null,"width":75,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"below","hposition":"none","html":"

视频驱动/设备

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":245,"y":580,"rotation":0,"id":60,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":34,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":null,"controlPath":[[-65,0],[-65,50],[57,50]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":45,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":9,"px":0.5,"py":0.5}}},"linkMap":[]},{"x":114,"y":584,"rotation":0,"id":59,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":33,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":null,"controlPath":[[66,-4],[66,46],[-57,46]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":45,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":3,"px":0.5,"py":0.5}}},"linkMap":[]},{"x":270,"y":502,"rotation":0,"id":58,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":32,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":null,"controlPath":[[10,-2],[10,18],[-90,18],[-90,38]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":40,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":45,"px":0.5,"py":0}}},"linkMap":[]},{"x":92,"y":497,"rotation":0,"id":56,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":31,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":null,"controlPath":[[-12,3],[-12,23],[88,23],[88,43]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":38,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":45,"px":0.5,"py":0}}},"linkMap":[]},{"x":267,"y":433,"rotation":0,"id":54,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":30,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[13,-3],[13,7],[13,17],[13,27]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":36,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":40,"px":0.5,"py":0}}},"linkMap":[]},{"x":87,"y":431,"rotation":0,"id":53,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":29,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-7,-1],[-7,9],[-7,19],[-7,29]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":34,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":38,"px":0.5,"py":0}}},"linkMap":[]},{"x":271,"y":362,"rotation":0,"id":52,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":28,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[9,-2],[9,8],[9,18],[9,28]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":32,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":36,"px":0.5,"py":0}}},"linkMap":[]},{"x":92,"y":356,"rotation":0,"id":51,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":27,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-12,4],[-12,14],[-12,24],[-12,34]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":29,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":34,"px":0.5,"py":0}}},"linkMap":[]},{"x":243,"y":271,"rotation":0,"id":50,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":26,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":null,"controlPath":[[-63,-1],[-63,24],[37,24],[37,49]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":27,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":32,"px":0.5,"py":0}}},"linkMap":[]},{"x":106,"y":268,"rotation":0,"id":49,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":25,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":null,"controlPath":[[74,2],[74,27],[-26,27],[-26,52]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":27,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":29,"px":0.5,"py":0}}},"linkMap":[]},{"x":181,"y":201,"rotation":0,"id":48,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":24,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-1,-1],[-1,9],[-1,19],[-1,29]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":25,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":27,"px":0.5,"py":0}}},"linkMap":[]},{"x":179,"y":134,"rotation":0,"id":47,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":23,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[1,-4],[1,6],[1,16],[1,26]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":23,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":25,"px":0.5,"py":0}}},"linkMap":[]},{"x":120,"y":540,"rotation":0,"id":45,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":21,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4000000000000004,"y":0,"rotation":0,"id":46,"uid":null,"width":115.2,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

音视频同步

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":182,"y":62,"rotation":0,"id":42,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":20,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-2,-2],[-2,8],[-2,18],[-2,28]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":19,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":23,"px":0.5,"py":0}}},"linkMap":[]},{"x":220,"y":460,"rotation":0,"id":40,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":18,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#d0e0e3","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4,"y":0,"rotation":0,"id":41,"uid":null,"width":115.2,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

视频原始数据

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":20,"y":460,"rotation":0,"id":38,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":16,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#d0e0e3","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4,"y":0,"rotation":0,"id":39,"uid":null,"width":115.2,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

音频原始数据

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":220,"y":390,"rotation":0,"id":36,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":14,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#f9cb9c","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4,"y":0,"rotation":0,"id":37,"uid":null,"width":115.2,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

视频解码

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":20,"y":390,"rotation":0,"id":34,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":12,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#f9cb9c","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4,"y":0,"rotation":0,"id":35,"uid":null,"width":115.2,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

音频解码

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":220,"y":320,"rotation":0,"id":32,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":10,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#a2c4c9","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4,"y":0,"rotation":0,"id":33,"uid":null,"width":115.2,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

视频压缩数据

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":20,"y":320,"rotation":0,"id":29,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":8,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#a2c4c9","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4,"y":0,"rotation":0,"id":30,"uid":null,"width":115.2,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

音频压缩数据

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":120,"y":230,"rotation":0,"id":27,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":6,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.3999999999999995,"y":0,"rotation":0,"id":28,"uid":null,"width":115.19999999999999,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

解封装

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":120,"y":160,"rotation":0,"id":25,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":4,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#76a5af","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.3999999999999995,"y":0,"rotation":0,"id":26,"uid":null,"width":115.19999999999999,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

封装格式数据

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":120,"y":90,"rotation":0,"id":23,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":2,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4000000000000004,"y":0,"rotation":0,"id":24,"uid":null,"width":115.19999999999999,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

解协议

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":120,"y":20,"rotation":0,"id":19,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","width":120,"height":40,"lockAspectRatio":false,"lockShape":false,"order":0,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#45818e","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.3999999999999995,"y":0,"rotation":0,"id":21,"uid":null,"width":115.19999999999993,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

网络数据

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]}],"background":"#FFFFFF","width":340.5,"height":698,"maxWidth":5000,"maxHeight":5000,"nodeIndex":72,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"shapeStyles":{"com.gliffy.shape.network.network_v3.home":{"fill":"#003366"},"com.gliffy.shape.flowchart.flowchart_v1.default":{"fill":"#f9cb9c","stroke":"#333333","strokeWidth":2}},"lineStyles":{"global":{"endArrow":2,"orthoMode":0}},"textStyles":{},"themeData":null}} \ No newline at end of file diff --git "a/AudioVideo/image/\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.jpg" "b/AudioVideo/image/\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.jpg" new file mode 100644 index 00000000..25f9b820 Binary files /dev/null and "b/AudioVideo/image/\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.jpg" differ diff --git "a/AudioVideo/\345\270\270\350\247\201\345\260\201\350\243\205\346\240\274\345\274\217.md" "b/AudioVideo/\345\270\270\350\247\201\345\260\201\350\243\205\346\240\274\345\274\217.md" new file mode 100644 index 00000000..5dcf80b8 --- /dev/null +++ "b/AudioVideo/\345\270\270\350\247\201\345\260\201\350\243\205\346\240\274\345\274\217.md" @@ -0,0 +1,28 @@ +## 常见封装格式 + +封装格式的主要作用是把视频码流和音频码流按照一定的格式存储在一个文件中。现如今流行的封装格式如下表所示: + +主要封装格式一览 + +| 名称 | 推出机构 | 流媒体 | 支持的视频编码 | 支持的音频编码 | 目前使用领域 | +| ---- | ------------------ | ---- | ----------------------------- | ------------------------------------ | --------- | +| AVI | Microsoft Inc. | 不支持 | 几乎所有格式 | 几乎所有格式 | BT下载影视 | +| MP4 | MPEG | 支持 | MPEG-2, MPEG-4, H.264, H.263等 | AAC, MPEG-1 Layers I, II, III, AC-3等 | 互联网视频网站 | +| TS | MPEG | 支持 | MPEG-1, MPEG-2, MPEG-4, H.264 | MPEG-1 Layers I, II, III, AAC, | IPTV,数字电视 | +| FLV | Adobe Inc. | 支持 | Sorenson, VP6, H.264 | MP3, ADPCM, Linear PCM, AAC等 | 互联网视频网站 | +| MKV | CoreCodec Inc. | 支持 | 几乎所有格式 | 几乎所有格式 | 互联网视频网站 | +| RMVB | Real Networks Inc. | 支持 | RealVideo 8, 9, 10 | AAC, Cook Codec, RealAudio Lossless | BT下载影视 | + +由表可见,除了AVI之外,其他封装格式都支持流媒体,即可以“边下边播”。有些格式更“万能”一些,支持的视音频编码标准多一些,比如MKV。而有些格式则支持的相对比较少,比如说RMVB。 + +这些封装格式都有相关的文档,在这里就不一一例举了。 + +我自己也做过辅助学习的小项目: + +[TS封装格式分析器](http://blog.csdn.net/leixiaohua1020/article/details/17973587) + +[FLV封装格式分析器](http://blog.csdn.net/leixiaohua1020/article/details/17934487) + +### 参考资料: + +[视音频编解码技术零基础学习方法](http://blog.csdn.net/leixiaohua1020/article/details/18893769) \ No newline at end of file diff --git "a/AudioVideo/\345\270\270\350\247\201\345\260\201\350\243\205\346\240\274\345\274\217\346\246\202\350\247\210.md" "b/AudioVideo/\345\270\270\350\247\201\345\260\201\350\243\205\346\240\274\345\274\217\346\246\202\350\247\210.md" new file mode 100644 index 00000000..98f1e818 --- /dev/null +++ "b/AudioVideo/\345\270\270\350\247\201\345\260\201\350\243\205\346\240\274\345\274\217\346\246\202\350\247\210.md" @@ -0,0 +1,14 @@ +## 常见封装格式概览 + +| 名称 | 推出机构 | 流媒体 | 支持的视频编码 | 支持的音频编码 | 目前使用领域 | +| ---- | ------------------ | ---- | ----------------------------- | ------------------------------------ | --------- | +| AVI | Microsoft Inc. | 不支持 | 几乎所有格式 | 几乎所有格式 | BT下载影视 | +| MP4 | MPEG | 支持 | MPEG-2, MPEG-4, H.264, H.263等 | AAC, MPEG-1 Layers I, II, III, AC-3等 | 互联网视频网站 | +| TS | MPEG | 支持 | MPEG-1, MPEG-2, MPEG-4, H.264 | MPEG-1 Layers I, II, III, AAC, | IPTV,数字电视 | +| FLV | Adobe Inc. | 支持 | Sorenson, VP6, H.264 | MP3, ADPCM, Linear PCM, AAC等 | 互联网视频网站 | +| MKV | CoreCodec Inc. | 支持 | 几乎所有格式 | 几乎所有格式 | 互联网视频网站 | +| RMVB | Real Networks Inc. | 支持 | RealVideo 8, 9, 10 | AAC, Cook Codec, RealAudio Lossless | BT下载影视 | + +### 参考资料: + +[视音频编解码技术零基础学习方法](http://blog.csdn.net/leixiaohua1020/article/details/18893769) \ No newline at end of file diff --git "a/AudioVideo/\345\270\270\350\247\201\346\265\201\345\252\222\344\275\223\345\215\217\350\256\256.md" "b/AudioVideo/\345\270\270\350\247\201\346\265\201\345\252\222\344\275\223\345\215\217\350\256\256.md" new file mode 100644 index 00000000..7cb5b7a2 --- /dev/null +++ "b/AudioVideo/\345\270\270\350\247\201\346\265\201\345\252\222\344\275\223\345\215\217\350\256\256.md" @@ -0,0 +1,33 @@ +## 常见流媒体协议 + +流媒体协议是服务器与客户端之间通信遵循的规定。当前网络上主要的流媒体协议如表所示。 + +| 名称 | 推出机构 | 传输层协议 | 客户端 | 目前使用领域 | +| -------- | -------------- | ------- | -------- | -------- | +| RTSP+RTP | IETF | TCP+UDP | VLC, WMP | IPTV | +| RTMP | Adobe Inc. | TCP | Flash | 互联网直播 | +| RTMFP | Adobe Inc. | UDP | Flash | 互联网直播 | +| MMS | Microsoft Inc. | TCP/UDP | WMP | 互联网直播+点播 | +| HTTP | WWW+IETF | TCP | Flash | 互联网点播 | + +RTSP+RTP经常用于IPTV领域。因为其采用UDP传输视音频,支持组播,效率较高。但其缺点是网络不好的情况下可能会丢包,影响视频观看质量。因而围绕IPTV的视频质量的研究还是挺多的。 + +RTSP规范可参考:[RTSP协议学习笔记](http://blog.csdn.net/leixiaohua1020/article/details/11955341) + +RTSP+RTP系统中衡量服务质量可参考:[网络视频传输的服务质量(QoS)](http://blog.csdn.net/leixiaohua1020/article/details/11883393) + +上海IPTV码流分析结果可参考:[IPTV视频码流分析](http://blog.csdn.net/leixiaohua1020/article/details/11846761) + +因为互联网网络环境的不稳定性,RTSP+RTP较少用于互联网视音频传输。互联网视频服务通常采用TCP作为其流媒体的传输层协议,因而像RTMP,MMS,HTTP这类的协议广泛用于互联网视音频服务之中。这类协议不会发生丢包,因而保证了视频的质量,但是传输的效率会相对低一些。 + +此外RTMFP是一种比较新的流媒体协议,特点是支持P2P。 + +RTMP我做的研究相对多一些:比如[RTMP规范简单分析](http://blog.csdn.net/leixiaohua1020/article/details/11694129),或者[RTMP流媒体播放过程](http://blog.csdn.net/leixiaohua1020/article/details/11704355) + +相关工具的源代码分析:[RTMPdump源代码分析 1: main()函数[系列文章\]](http://blog.csdn.net/leixiaohua1020/article/details/12952977) + +RTMP协议学习:[RTMP流媒体技术零基础学习方法](http://blog.csdn.net/leixiaohua1020/article/details/15814587) + +### 参考资料: + +[视音频编解码技术零基础学习方法](http://blog.csdn.net/leixiaohua1020/article/details/18893769) \ No newline at end of file diff --git "a/AudioVideo/\345\270\270\350\247\201\351\237\263\350\247\206\351\242\221\347\274\226\347\240\201.md" "b/AudioVideo/\345\270\270\350\247\201\351\237\263\350\247\206\351\242\221\347\274\226\347\240\201.md" new file mode 100644 index 00000000..524415e3 --- /dev/null +++ "b/AudioVideo/\345\270\270\350\247\201\351\237\263\350\247\206\351\242\221\347\274\226\347\240\201.md" @@ -0,0 +1,110 @@ +## 常见音视频编码 + +### 1. 视频编码 + +视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。视频编码是视音频技术中最重要的技术之一。视频码流的数据量占了视音频总数据量的绝大部分。高效率的视频编码在同等的码率下,可以获得更高的视频质量。 + +视频编码的简单原理可以参考:[视频压缩编码和音频压缩编码的基本原理](http://blog.csdn.net/leixiaohua1020/article/details/28114081) + +注:视频编码技术在整个视音频技术中应该是最复杂的技术。如果没有基础的话,可以先买一些书看一下原理,比如说《现代电视原理》《数字电视广播原理与应用》(本科的课本)中的部分章节。 + +主要视频编码一览 + +| 名称 | 推出机构 | 推出时间 | 目前使用领域 | +| ----------- | -------------- | ---- | ------ | +| HEVC(H.265) | MPEG/ITU-T | 2013 | 研发中 | +| H.264 | MPEG/ITU-T | 2003 | 各个领域 | +| MPEG4 | MPEG | 2001 | 不温不火 | +| MPEG2 | MPEG | 1994 | 数字电视 | +| VP9 | Google | 2013 | 研发中 | +| VP8 | Google | 2008 | 不普及 | +| VC-1 | Microsoft Inc. | 2006 | 微软平台 | + +由表可见,有两种视频编码方案是最新推出的:VP9和HEVC。目前这两种方案都处于研发阶段,还没有到达实用的程度。当前使用最多的视频编码方案就是H.264。 + +#### **1.1 主流编码标准** + +H.264仅仅是一个编码标准,而不是一个具体的编码器,H.264只是给编码器的实现提供参照用的。 + +基于H.264标准的编码器还是很多的,究竟孰优孰劣?可参考:[MSU出品的 H.264编码器比较(2011.5)](http://blog.csdn.net/leixiaohua1020/article/details/12373947) + +在学习视频编码的时候,可能会用到各种编码器(实际上就是一个exe文件),他们常用的编码命令可以参考:[各种视频编码器的命令行格式](http://blog.csdn.net/leixiaohua1020/article/details/11705495) + +学习H.264最标准的源代码,就是其官方标准JM了。但是要注意,JM速度非常的慢,是无法用于实际的:[H.264参考软件JM12.2RC代码详细流程](http://blog.csdn.net/leixiaohua1020/article/details/11980219) + +实际中使用最多的就是x264了,性能强悍(超过了很多商业编码器),而且开源。其基本教程网上极多,不再赘述。编码时候可参考:[x264编码指南——码率控制](http://blog.csdn.net/leixiaohua1020/article/details/12720135)。编码后统计值的含义:[X264输出的统计值的含义(X264 Stats Output)](http://blog.csdn.net/leixiaohua1020/article/details/11884559) + +Google推出的VP8属于和H.264同一时代的标准。总体而言,VP8比H.264要稍微差一点。有一篇写的很好的VP8的介绍文章:[深入了解 VP8](http://blog.csdn.net/leixiaohua1020/article/details/12760173)。除了在技术领域,VP8和H.264在专利等方面也是打的不可开交,可参考文章:[WebM(VP8) vs H.264](http://blog.csdn.net/leixiaohua1020/article/details/12720237) + +此外,我国还推出了自己的国产标准AVS,性能也不错,但目前比H.264还是要稍微逊色一点。不过感觉我国在视频编解码领域还算比较先进的,可参考:[视频编码国家标准AVS与H.264的比较(节选)](http://blog.csdn.net/leixiaohua1020/article/details/12851745) + +近期又推出了AVS新一代的版本AVS+,具体的性能测试还没看过。不过据说AVS+得到了国家政策上非常强力的支持。 + +#### **1.2 下一代编码标准** + +下一代的编解码标准就要数HEVC和VP9了。VP9是Google继VP8之后推出的新一代标准。VP9和HEVC相比,要稍微逊色一些。它们的对比可参考:(1)[HEVC与VP9编码效率对比](http://blog.csdn.net/leixiaohua1020/article/details/11713041) (2)[HEVC,VP9,x264性能对比](http://blog.csdn.net/leixiaohua1020/article/details/19014955) + +HEVC在未来拥有很多大的优势,可参考:[HEVC将会取代H.264的原因](http://blog.csdn.net/leixiaohua1020/article/details/11844949) + +学习HEVC最标准的源代码,就是其官方标准HM了。其速度比H.264的官方标准代码又慢了一大截,使用可参考:[HEVC学习—— HM的使用](http://blog.csdn.net/leixiaohua1020/article/details/12759297) + +未来实际使用的HEVC开源编码器很有可能是x265,目前该项目还处于发展阶段,可参考:[x265(HEVC编码器,基于x264)](http://blog.csdn.net/leixiaohua1020/article/details/13991351)[介绍](http://blog.csdn.net/leixiaohua1020/article/details/13991351)。x265的使用可以参考:[HEVC(H.265)标准的编码器(x265,DivX265)试用](http://blog.csdn.net/leixiaohua1020/article/details/18861635) + +主流以及下一代编码标准之间的比较可以参考文章:[视频编码方案之间的比较(HEVC,H.264,MPEG2等)](http://blog.csdn.net/leixiaohua1020/article/details/12237177) + +此外,在码率一定的情况下,几种编码标准的比较可参考:[限制码率的视频编码标准比较(包括MPEG-2,H.263,MPEG-4,以及 H.264)](http://blog.csdn.net/leixiaohua1020/article/details/12851975) + +结果大致是这样的: + +HEVC > VP9 > H.264> VP8 > MPEG4 > H.263 > MPEG2。 + +截了一些图,可以比较直观的了解各种编码标准: + +HEVC码流简析:[HEVC码流简单分析](http://blog.csdn.net/leixiaohua1020/article/details/11845069) + +H.264码流简析:[H.264简单码流分析](http://blog.csdn.net/leixiaohua1020/article/details/11845625) + +MPEG2码流简析:[MPEG2简单码流分析](http://blog.csdn.net/leixiaohua1020/article/details/11846185) + +以上简析使用的工具:[视频码流分析工具](http://blog.csdn.net/leixiaohua1020/article/details/11845435) + +我自己做的小工具: [H.264码流分析器](http://blog.csdn.net/leixiaohua1020/article/details/17933821) + +### 2. 音频编码 + +音频编码的主要作用是将音频采样数据(PCM等)压缩成为音频码流,从而降低音频的数据量。音频编码也是互联网视音频技术中一个重要的技术。但是一般情况下音频的数据量要远小于视频的数据量,因而即使使用稍微落后的音频编码标准,而导致音频数据量有所增加,也不会对视音频的总数据量产生太大的影响。高效率的音频编码在同等的码率下,可以获得更高的音质。 + +音频编码的简单原理可以参考:[视频压缩编码和音频压缩编码的基本原理](http://blog.csdn.net/leixiaohua1020/article/details/28114081) + +主要音频编码一览 + +| 名称 | 推出机构 | 推出时间 | 目前使用领域 | +| ---- | -------------- | ---- | ------- | +| AAC | MPEG | 1997 | 各个领域(新) | +| AC-3 | Dolby Inc. | 1992 | 电影 | +| MP3 | MPEG | 1993 | 各个领域(旧) | +| WMA | Microsoft Inc. | 1999 | 微软平台 | + +由表可见,近年来并未推出全新的音频编码方案,可见音频编码技术已经基本可以满足人们的需要。音频编码技术近期绝大部分的改动都是在MP3的继任者——AAC的基础上完成的。 + +这些编码标准之间的比较可以参考文章:[音频编码方案之间音质比较(AAC,MP3,WMA等)](http://blog.csdn.net/leixiaohua1020/article/details/11730661) + +结果大致是这样的: + +AAC+ > MP3PRO > AAC> RealAudio > WMA > MP3 + +AAC格式的介绍:[AAC格式简介](http://blog.csdn.net/leixiaohua1020/article/details/11822537) + +AAC几种不同版本之间的对比:[AAC规格(LC,HE,HEv2)及性能对比](http://blog.csdn.net/leixiaohua1020/article/details/11971419) + +AAC专利方面的介绍:[AAC专利介绍](http://blog.csdn.net/leixiaohua1020/article/details/11854587) + +此外杜比数字的编码标准也比较流行,但是貌似比最新的AAC稍为逊色:[AC-3技术综述](http://blog.csdn.net/leixiaohua1020/article/details/11822737) + +我自己做的小工具:[ AAC格式分析器](http://blog.csdn.net/leixiaohua1020/article/details/18155549) + + + +### 参考资料: + +[视音频编解码技术零基础学习方法](http://blog.csdn.net/leixiaohua1020/article/details/18893769) \ No newline at end of file diff --git "a/AudioVideo/\351\200\232\347\224\250\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.md" "b/AudioVideo/\351\200\232\347\224\250\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.md" new file mode 100644 index 00000000..6f35f5d3 --- /dev/null +++ "b/AudioVideo/\351\200\232\347\224\250\350\247\206\351\242\221\350\247\243\347\240\201\346\222\255\346\224\276\346\265\201\347\250\213.md" @@ -0,0 +1,18 @@ +--- +typora-copy-images-to: ./image +--- + +## 通用视频解码播放流程 + +**通用的网络视频播放流程:** + +1. 从网络数据流中获得视频数据流。 +2. 将视频数据流解析成压缩音频数据和压缩视频数据。 +3. 分别对音频和视频解码获取原始(采样)数据。 +4. 经过同步策略后,有序的将原始(采样)数据输出到指定设备播放。 + +![视频解码播放流程](image/视频解码播放流程.jpg) + +### 参考资料: + +[视音频编解码技术零基础学习方法](http://blog.csdn.net/leixiaohua1020/article/details/18893769) \ No newline at end of file diff --git a/CustomView/Advance/[01]CustomViewProcess.md b/CustomView/Advance/[01]CustomViewProcess.md index 5166f06f..70e4a5be 100644 --- a/CustomView/Advance/[01]CustomViewProcess.md +++ b/CustomView/Advance/[01]CustomViewProcess.md @@ -29,7 +29,7 @@ > 例如:制作一个支持自动加载网络图片的ImageView,制作图表等。 -**PS: 自定义View在大多数情况下都有替代方案,利用图片或者组合动画来实现,但是使用后者可能会面临内存耗费过大,制作麻烦更诸多问题。** +**PS: 自定义View在大多数情况下都有替代方案,利用图片或者组合动画来实现,但是使用后者可能会面临内存耗费过大,制作麻烦等诸多问题。** ******* diff --git a/CustomView/Advance/[02]Canvas_BasicGraphics.md b/CustomView/Advance/[02]Canvas_BasicGraphics.md index 65720057..60143dc4 100644 --- a/CustomView/Advance/[02]Canvas_BasicGraphics.md +++ b/CustomView/Advance/[02]Canvas_BasicGraphics.md @@ -111,22 +111,22 @@ Canvas我们可以称之为画布,能够在上面绘制各种东西,是安 ****** ### 绘制矩形: -确定一个矩形最少需要四个数据,就是**对角线的两个点**的坐标值,这里一般采用**左上角和右下角**的两个点的坐标。 +我们都知道,确定一个矩形最少需要四个数据,就是**对角线的两个点**的坐标值,这里一般采用**左上角和右下角**的两个点的坐标。 关于绘制矩形,Canvas提供了三种重载方法,第一种就是提供**四个数值(矩形左上角和右下角两个点的坐标)来确定一个矩形**进行绘制。 其余两种是先将矩形封装为**Rect或RectF**(实际上仍然是用两个坐标点来确定的矩形),然后传递给Canvas绘制,如下: ``` java - // 第一种 - canvas.drawRect(100,100,800,400,mPaint); +// 第一种 +canvas.drawRect(100,100,800,400,mPaint); - // 第二种 - Rect rect = new Rect(100,100,800,400); - canvas.drawRect(rect,mPaint); +// 第二种 +Rect rect = new Rect(100,100,800,400); +canvas.drawRect(rect,mPaint); - // 第三种 - RectF rectF = new RectF(100,100,800,400); - canvas.drawRect(rectF,mPaint); +// 第三种 +RectF rectF = new RectF(100,100,800,400); +canvas.drawRect(rectF,mPaint); ``` 以上三种方法所绘制出来的结果是完全一样的。 @@ -192,7 +192,7 @@ Canvas我们可以称之为画布,能够在上面绘制各种东西,是安 ****** ### 绘制椭圆: -相对于绘制圆角矩形,绘制椭圆就简单的多了,因为他只需要一个矩形矩形作为参数: +相对于绘制圆角矩形,绘制椭圆就简单的多了,因为他只需要一个矩形作为参数: ``` java // 第一种 diff --git a/CustomView/Advance/[03]Canvas_Convert.md b/CustomView/Advance/[03]Canvas_Convert.md index 2e1c8206..4ad9f63f 100644 --- a/CustomView/Advance/[03]Canvas_Convert.md +++ b/CustomView/Advance/[03]Canvas_Convert.md @@ -77,15 +77,15 @@ 缩放比例(sx,sy)取值范围详解: -| 取值范围(n) | 说明 | -| -------- | -------------------------- | -| [-∞, -1) | 先根据缩放中心放大n倍,再根据中心轴进行翻转 | -| -1 | 根据缩放中心轴进行翻转 | -| (-1, 0) | 先根据缩放中心缩小到n,再根据中心轴进行翻转 | -| 0 | 不会显示,若sx为0,则宽度为0,不会显示,sy同理 | -| (0, 1) | 根据缩放中心缩小到n | -| 1 | 没有变化 | -| (1, +∞) | 根据缩放中心放大n倍 | +| 取值范围(n) | 说明 | +| ----------- | ---------------------------------------------- | +| (-∞, -1) | 先根据缩放中心放大n倍,再根据中心轴进行翻转 | +| -1 | 根据缩放中心轴进行翻转 | +| (-1, 0) | 先根据缩放中心缩小到n,再根据中心轴进行翻转 | +| 0 | 不会显示,若sx为0,则宽度为0,不会显示,sy同理 | +| (0, 1) | 根据缩放中心缩小到n | +| 1 | 没有变化 | +| (1, +∞) | 根据缩放中心放大n倍 | 如果在缩放时稍微注意一下就会发现缩放的中心默认为坐标原点,而缩放中心轴就是坐标轴,如下: @@ -181,6 +181,9 @@ 调用两次缩放则 x轴实际缩放为0.5x0.5=0.25 y轴实际缩放为0.5x0.1=0.05 下面我们利用这一特性制作一个有趣的图形。 + +> 注意设置画笔模式为描边(STROKE) + ``` java // 将坐标系原点移动到画布正中心 canvas.translate(mWidth / 2, mHeight / 2); @@ -324,7 +327,7 @@ Y = sy * x + y #### ⑸快照(save)和回滚(restore) -Q: 为什存在快照与回滚
+Q: 为什么存在快照与回滚
A:画布的操作是不可逆的,而且很多画布操作会影响后续的步骤,例如第一个例子,两个圆形都是在坐标原点绘制的,而因为坐标系的移动绘制出来的实际位置不同。所以会对画布的一些状态进行保存和回滚。
diff --git a/CustomView/Advance/[05]Path_Basic.md b/CustomView/Advance/[05]Path_Basic.md index 41fb94e8..58e9c2d2 100644 --- a/CustomView/Advance/[05]Path_Basic.md +++ b/CustomView/Advance/[05]Path_Basic.md @@ -36,7 +36,7 @@ **请关闭硬件加速,以免引起不必要的问题!
请关闭硬件加速,以免引起不必要的问题!
请关闭硬件加速,以免引起不必要的问题!** -**在AndroidMenifest文件中application节点下添上 android:hardwareAccelerated="false"以关闭整个应用的硬件加速。
更多请参考这里:[Android的硬件加速及可能导致的问题](https://github.com/GcsSloop/AndroidNote/issues/7)** +**在AndroidMainfest文件中application节点下添上 android:hardwareAccelerated="false"以关闭整个应用的硬件加速。
更多请参考这里:[Android的硬件加速及可能导致的问题](https://github.com/GcsSloop/AndroidNote/issues/7)** ## Path作用 本次特地开了一篇详细讲解Path,为什么要单独摘出来呢,这是因为Path在2D绘图中是一个很重要的东西。 @@ -60,10 +60,10 @@ _The Path class encapsulates compound (multiple contour) geometric paths consist 另外路径有开放和封闭的区别。 -| 图像 | 名称 | 备注 | -| ---------------------------------------- | ---- | ------------- | +| 图像 | 名称 | 备注 | +| ------------------------------------------------------------ | -------- | -------------------------- | | ![](http://ww4.sinaimg.cn/thumbnail/005Xtdi2jw1f0zx9g9gggj30f00aiwek.jpg) | 封闭路径 | 首尾相接形成了一个封闭区域 | -| ![](http://ww1.sinaimg.cn/thumbnail/005Xtdi2jw1f0zxg8ilpxj30f00aimx8.jpg) | 开放路径 | 没有首位相接形成封闭区域 | +| ![](http://ww1.sinaimg.cn/thumbnail/005Xtdi2jw1f0zxg8ilpxj30f00aimx8.jpg) | 开放路径 | 没有首尾相接形成封闭区域 | > 这个是我随便画的,仅为展示一下区别,请无视我灵魂画师一般的绘图水准。 diff --git a/CustomView/Advance/[08]Path_Play.md b/CustomView/Advance/[08]Path_Play.md index 7b4285a8..35387d5f 100644 --- a/CustomView/Advance/[08]Path_Play.md +++ b/CustomView/Advance/[08]Path_Play.md @@ -234,7 +234,7 @@ canvas.drawPath(dst, mDeafultPaint); // 绘制 Path ### 4.nextContour -我们知道 Path 可以由多条曲线构成,但不论是 getLength , getgetSegment 或者是其它方法,都只会在其中第一条线段上运行,而这个 `nextContour` 就是用于跳转到下一条曲线到方法,_如果跳转成功,则返回 true, 如果跳转失败,则返回 false。_ +我们知道 Path 可以由多条曲线构成,但不论是 getLength , getSegment 或者是其它方法,都只会在其中第一条线段上运行,而这个 `nextContour` 就是用于跳转到下一条曲线到方法,_如果跳转成功,则返回 true, 如果跳转失败,则返回 false。_ 如下,我们创建了一个 Path 并使其中包含了两个闭合的曲线,内部的边长是200,外面的边长是400,现在我们使用 PathMeasure 分别测量两条曲线的总长度。 diff --git a/CustomView/Advance/[09]Matrix_Basic.md b/CustomView/Advance/[09]Matrix_Basic.md index e60d8bfe..e3985de3 100644 --- a/CustomView/Advance/[09]Matrix_Basic.md +++ b/CustomView/Advance/[09]Matrix_Basic.md @@ -162,15 +162,15 @@ Matrix 是一个矩阵,最根本的作用就是坐标转换,下面我们就 前乘相当于矩阵的右乘: -![](http://latex.codecogs.com/png.latex?$$ M' = M \\cdot S $$) +![](https://ww1.sinaimg.cn/large/006tKfTcgy1fhe1ul01s9j302m00gq2u.jpg) > 这表示一个矩阵与一个特殊矩阵前乘后构造出结果矩阵。 ### 后乘(post) -前乘相当于矩阵的左乘: +后乘相当于矩阵的左乘: -![](http://latex.codecogs.com/png.latex?$$ M' = S \\cdot M $$) +![](https://ww3.sinaimg.cn/large/006tKfTcgy1fhe1vta7ooj302s00pq2u.jpg) > 这表示一个矩阵与一个特殊矩阵后乘后构造出结果矩阵。 diff --git a/CustomView/Advance/[10]Matrix_Method.md b/CustomView/Advance/[10]Matrix_Method.md index 6d7b0a0c..3254f4bb 100644 --- a/CustomView/Advance/[10]Matrix_Method.md +++ b/CustomView/Advance/[10]Matrix_Method.md @@ -40,16 +40,7 @@ Matrix matrix = new Matrix(); 通过这种方式创建出来的并不是一个数值全部为空的矩阵,而是一个单位矩阵,如下: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -1 & 0 & 0 \\\\ -0 & 1 & 0 \\\\ -0 & 0 & 1 -\\end{1} -\\right ] -$$) - +![](https://ww2.sinaimg.cn/large/006tKfTcgy1fhe1potuf8j302301z3yf.jpg) #### 有参构造 diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md index 0e4c83b6..e59848c0 100644 --- a/CustomView/Advance/[11]Matrix_3D_Camera.md +++ b/CustomView/Advance/[11]Matrix_3D_Camera.md @@ -16,7 +16,7 @@ | 基本方法 | save、restore | 保存、 回滚 | | 常用方法 | getMatrix、applyToCanvas | 获取Matrix、应用到画布 | | 平移 | translate | 位移 | -| 旋转 | rotat (API 12)、rotateX、rotateY、rotateZ | 各种旋转 | +| 旋转 | rotate (API 12)、rotateX、rotateY、rotateZ | 各种旋转 | | 相机位置 | setLocation (API 12)、getLocationX (API 16)、getLocationY (API 16)、getLocationZ (API 16) | 设置与获取相机位置 | > Camera的方法并不是特别多,很多内容与之前的讲解的Canvas和Matrix类似,不过又稍有不同,之前的画布操作和Matrix主要是作用于2D空间,而Camera则主要作用于3D空间。 @@ -71,7 +71,9 @@ ![](http://ww4.sinaimg.cn/large/005Xtdi2jw1f7q71yek4wg308c058go5.gif) -> 摄像机的位置默认是 (0, 0, -576)。其中 -576= -8 x 72,虽然官方文档说距离屏幕的距离是 -8, 但经过测试实际距离是 -576 像素,当距离为 -10 的时候,实际距离为 -720 像素。不过这个数值72我也不明白是什么东西,我使用了3款手机测试,屏幕大小和像素密度均不同,但结果都是一样的,知道的小伙伴可以告诉我一声。 +> 摄像机的位置默认是 (0, 0, -576)。其中 -576= -8 x 72,虽然官方文档说距离屏幕的距离是 -8, 但经过测试实际距离是 -576 像素,当距离为 -10 的时候,实际距离为 -720 像素。我使用了3款手机测试,屏幕大小和像素密度均不同,但结果都是一样的。 +> +> 这个魔数可以在 Android 底层的图像引擎 Skia 中找到。在 Skia 中,Camera 的位置单位是英寸,英寸和像素的换算单位在 Skia 中被固定为 72 像素,而 Android 中把这个换算单位照搬了过来。 diff --git a/CustomView/Advance/[19]gesture-detector.md b/CustomView/Advance/[19]gesture-detector.md index 814c6f4f..94e60e18 100644 --- a/CustomView/Advance/[19]gesture-detector.md +++ b/CustomView/Advance/[19]gesture-detector.md @@ -1,16 +1,18 @@ # Android 手势检测(GestureDetector) -因为工作等原因,已经很长时间没有写文章了,趁节假日来水一篇简单的文章,Android 手势检测,如题,本文依旧是和事件相关的,如果你没看过之前的文章,可以到 [自定义 View 系列](http://www.gcssloop.com/customview/CustomViewIndex) 来查看前面的内容。 +Android 手势检测,主要是 GestureDetector 相关内容的用法和注意事项,本文依旧属于事件处理这一体系,部分内容会涉及到之前文章提及过的知识点,如果你没看过之前的文章,可以到 [自定义 View 系列](http://www.gcssloop.com/customview/CustomViewIndex) 来查看这些内容。 在开发 Android 手机应用过程中,可能需要对一些手势作出响应,如:单击、双击、长按、滑动、缩放等。这些都是很常用的手势。就拿最简单的双击来说吧,假如我们需要判断一个控件是否被双击(即在较短的时间内快速的点击两次),似乎是一个很容易的任务,但仔细考虑起来,要处理的细节问题也有不少,例如: 1. **记录点击次数**,为了判断是否被点击超过 1 次,所以必须记录点击次数。 -2. **记录点击时间**,由于双击事件是较快速的点击两次,像点击一次后,过来几分钟再点击一次肯定不能算是双击事件,所以在记录点击次数的同时也要记录上一次的点击时间,我们可以设置本次点击距离上一次时间超过一定时间(例如:超过1s)就不识别为双击事件。 +2. **记录点击时间**,由于双击事件是较快速的点击两次,像点击一次后,过来几分钟再点击一次肯定不能算是双击事件,所以在记录点击次数的同时也要记录上一次的点击时间,我们可以设置本次点击距离上一次时间超过一定时间(例如:超过100ms)就不识别为双击事件。 3. **点击状态重置**,在响应双击事件,或者判断不是双击事件的时候要重置计数器和上一次点击时间。重置既可以在点击的时候判断并进行重新设置,也可以使用定时器等超过一定时间后重置状态。 这样看起来,判断一个双击事件就有这么多麻烦事情,更别其他的手势了,虽然这些看起来都很简单,但设计起来需要考虑的细节情况实在是太多了。 -那么有没有一种更好的方法来方便的检测手势呢?当然有啦,因为这些手势很常用,系统早就封装了一些方法给我们用,它就是 **GestureDetector** 。我们先看一下关于它的简单介绍: +那么有没有一种更好的方法来方便的检测手势呢?当然有啦,因为这些手势很常用,系统早就封装了一些方法给我们用,接下来我们就看看它们是如何使用的。 + +## GestureDetector > GestureDetector 可以使用 MotionEvents 检测各种手势和事件。GestureDetector.OnGestureListener 是一个回调方法,在发生特定的事件时会调用 Listener 中对应的方法回调。这个类只能用于检测触摸事件的 MotionEvent,不能用于轨迹球事件。 > (话说轨迹球已经消失多长时间了,估计很多人都没见过轨迹球这种东西)。 @@ -21,10 +23,10 @@ > - 在onTouchEvent(MotionEvent)方法中,确保调用 GestureDetector 实例的 onTouchEvent(MotionEvent)。回调中定义的方法将在事件发生时执行。 > - 如果侦听 onContextClick(MotionEvent),则必须在 View 的 onGenericMotionEvent(MotionEvent)中调用 GestureDetector OnGenericMotionEvent(MotionEvent)。 - GestureDetector 本身的方法很少,使用起来也非常简单,下面让我们看一下它的简单使用方法。 + GestureDetector 本身的方法比较少,使用起来也非常简单,下面让我们先看一下它的简单使用示例,分解开来大概需要三个步骤。 ```java -// 创建一个监听回调 +// 1.创建一个监听回调 SimpleOnGestureListener listener = new SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { Toast.makeText(MainActivity.this, "双击666", Toast.LENGTH_SHORT).show(); @@ -32,10 +34,10 @@ SimpleOnGestureListener listener = new SimpleOnGestureListener() { } }; -// 创建一个检测器 +// 2.创建一个检测器 final GestureDetector detector = new GestureDetector(this, listener); -// 给监听器设置数据源 +// 3.给监听器设置数据源 view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return detector.onTouchEvent(event); @@ -43,5 +45,342 @@ view.setOnTouchListener(new View.OnTouchListener() { }); ``` -上面是监听一个双击事件,可以看到它使用起来非常简单,接下来我们就看一下有关于它的详细信息。 +接下来我们先了解一下 GestureDetector 里面都有哪些内容。 + +### 1. 构造函数 + +GestureDetector 一共有 5 种构造函数,但有 2 种被废弃了,1 种是重复的,所以我们只需要关注其中的 2 种构造函数即可,如下: + +| 构造函数 | +| ---------------------------------------- | +| GestureDetector(Context context, GestureDetector.OnGestureListener listener) | +| GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) | + +第 1 种构造函数里面需要传递两个参数,上下文(Context) 和 手势监听器(OnGestureListener),这个很容易理解,就不再过多叙述,上面的例子中使用的就是这一种。 + +第 2 种构造函数则需要多传递一个 Handler 作为参数,这个有什么作用呢?其实作用也非常简单,这个 Handler 主要是为了给 GestureDetector 提供一个 Looper。 + +在通常情况下是不需这个 Handler 的,因为它会在内部自动创建一个 Handler 用于处理数据,如果你在主线程中创建 GestureDetector,那么它内部创建的 Handler 会自动获得主线程的 Looper,然而如果你在一个没有创建 Looper 的子线程中创建 GestureDetector 则需要传递一个带有 Looper 的 Handler 给它,否则就会因为无法获取到 Looper 导致创建失败。 + +第 2 种构造函数使用方式如下(下面是两种在子线程中创建 GestureDetector 的方法): + +```java +// 方式一、在主线程创建 Handler +final Handler handler = new Handler(); +new Thread(new Runnable() { + @Override public void run() { + final GestureDetector detector = new GestureDetector(MainActivity.this, new + GestureDetector.SimpleOnGestureListener() , handler); + // ... 省略其它代码 ... + } +}).start(); + +// 方式二、在子线程创建 Handler,并且指定 Looper +new Thread(new Runnable() { + @Override public void run() { + final Handler handler = new Handler(Looper.getMainLooper()); + final GestureDetector detector = new GestureDetector(MainActivity.this, new + GestureDetector.SimpleOnGestureListener() , handler); + // ... 省略其它代码 ... + } +}).start(); +``` + +当然了,使用其它创建 Handler 的方式也是可以的,重点传递的 Handler 一定要有 Looper,敲黑板,重点是 Handler 中的 Looper。假如子线程准备了 Looper 那么可以直接使用第 1 种构造函数进行创建,如下: + +```java +new Thread(new Runnable() { + @Override public void run() { + Looper.prepare(); // <- 重点在这里 + final GestureDetector detector = new GestureDetector(MainActivity.this, new + GestureDetector.SimpleOnGestureListener()); + // ... 省略其它代码 ... + } +}).start(); +``` + +### 2.手势监听器 + +既然是手势检测,自然要在对应的手势出现的时候通知调用者,最合适的自然是事件监听器模式。目前 GestureDetecotr 有四种监听器。 + +| 监听器 | 简介 | +| ---------------------------------------- | ---------------------------------------- | +| [OnContextClickListener](https://developer.android.com/reference/android/view/GestureDetector.OnContextClickListener.html) | 这个很容易让人联想到ContextMenu,然而它和ContextMenu并没有什么关系,它是在Android6.0(API 23)才添加的一个选项,是用于检测外部设备上的按钮是否按下的,例如蓝牙触控笔上的按钮,一般情况下,忽略即可。 | +| [OnDoubleTapListener](https://developer.android.com/reference/android/view/GestureDetector.OnDoubleTapListener.html) | 双击事件,有三个回调类型:双击(DoubleTap)、单击确认(SingleTapConfirmed) 和 双击事件回调(DoubleTapEvent) | +| [OnGestureListener](https://developer.android.com/reference/android/view/GestureDetector.OnGestureListener.html) | 手势检测,主要有以下类型事件:按下(Down)、 一扔(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress) 和 单击抬起(SingleTapUp) | +| [SimpleOnGestureListener](https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html) | 这个是上述三个接口的空实现,一般情况下使用这个比较多,也比较方便。 | + +#### 2.1 OnContextClickListener + +由于 OnContextClickListener 主要是用于检测外部设备按钮的,关于它需要注意一点,如果侦听 onContextClick(MotionEvent),则必须在 View 的 onGenericMotionEvent(MotionEvent)中调用 GestureDetector 的 OnGenericMotionEvent(MotionEvent)。 + +由于目前我们用到这个监听器的场景并不多,所以也就不展开介绍了,重点关注后面几个监听器。 + +#### 2.2 OnDoubleTapListener + +这个很明显就是用于检测双击事件的,它有三个回调接口,分别是 onDoubleTap、onDoubleTapEvent 和 onSingleTapConfirmed。 + +##### **2.2.1 onDoubleTap 与 onSingleTapConfirmed** + +**如果你只想监听双击事件,那么只用关注 onDoubleTap 就行了,如果你同时要监听单击事件则需要关注 onSingleTapConfirmed 这个回调函数**。 + +有人可能会有疑问,监听单击事件为什么要使用 onSingleTapConfirmed,使用 OnClickListener 不行吗?从理论上是可行的,但是我并不推荐这样使用,主要有两个原因: +1.它们两个是存在一定冲突的,如果你看过 [事件分发机制详解](http://www.gcssloop.com/customview/dispatch-touchevent-source) 就会知道,如果想要两者同时被触发,则 setOnTouchListener 不能消费事件,如果 onTouchListener 消费了事件,就可能导致 OnClick 无法正常触发。 +2.需要同时监听单击和双击,则说明单击和双击后响应逻辑不同,然而使用 OnClickListener 会在双击事件发生时触发两次,这显然不是我们想要的结果。而使用 onSingleTapConfirmed 就不用考虑那么多了,你完全可以把它当成单击事件来看待,而且在双击事件发生时,onSingleTapConfirmed 不会被调用,这样就不会引发冲突。 + +如果你需要同时监听两种点击事件可以这样写: + +```java +GestureDetector detector = new GestureDetector(this, new GestureDetector + .SimpleOnGestureListener() { + @Override public boolean onSingleTapConfirmed(MotionEvent e) { + Toast.makeText(MainActivity.this, "单击", Toast.LENGTH_SHORT).show(); + return false; + } + @Override public boolean onDoubleTap(MotionEvent e) { + Toast.makeText(MainActivity.this, "双击", Toast.LENGTH_SHORT).show(); + return false; + } +}); +``` + +关于 onSingleTapConfirmed 原理也非常简单,这一个回调函数在单击事件发生后300ms后触发(注意,不是立即触发的),只有在确定不会有后续的事件后,既当前事件肯定是单击事件才触发 onSingleTapConfirmed,所以在进行点击操作时,onDoubleTap 和 onSingleTapConfirmed 只会有一个被触发,也就不存在冲突了。 + +当然,如果你对事件分发机制非常了解的话,随便怎么用都行,条条大路通罗马,我这里只是推荐一种最简单而且不容易出错的实现方案。 + +##### **2.2.2 onDoubleTapEvent** + +**有些细心的小伙伴可能注意到还有一个 onDoubleTapEvent 回调函数,它是干什么的呢?它在双击事件确定发生时会对第二次按下产生的 MotionEvent 信息进行回调。** + +至于为什么要存在这样的回调,就要涉及到另一个比较细致的问题了,那就是 onDoubleTap 的触发时间,如果你在这些函数被调用时打印一条日志,那么你会看到这样的信息: + +``` +GCS-LOG: onDoubleTap +GCS-LOG: onDoubleTapEvent - down +GCS-LOG: onDoubleTapEvent - move +GCS-LOG: onDoubleTapEvent - move +GCS-LOG: onDoubleTapEvent - up +``` + +通过观察这些信息你会发现它们的调用顺序非常有趣,首先是 onDoubleTap 被触发,之后依次触发 onDoubleTapEvent 的 down、move、up 等信息,为什么说它们有趣呢?是因为这样的调用顺序会引发两种猜想,第一种猜想是 onDoubleTap 是在第二次手指抬起(up)后触发的,而 onDoubleTapEvent 是一种延时回调。第二种猜想则是 onDoubleTap 在第二次手指按下(dowm)时触发,onDoubleTapEvent 是一种实时回调。 + +通过测试和观察源码发现第二种猜想是正确的,因为第二次按下手指时,即便不抬起也会触发 onDoubleTap 和 onDoubleTapEvent 的 down,而且源码中逻辑也表明 onDoubleTapEvent 是一种实时回调。 + +这就引发了另一个问题,双击的触发时间,虽然这是一个细微到很难让人注意到的问题,假如说我们想要在第二次按下抬起后才判定这是一个双击操作,触发后续的内容,则不能使用 onDoubleTap 了,需要使用 onDoubleTapEvent 来进行更细微的控制,如下: + +```java +final GestureDetector detector = new GestureDetector(MainActivity.this, new GestureDetector.SimpleOnGestureListener() { + @Override public boolean onDoubleTap(MotionEvent e) { + Logger.e("第二次按下时触发"); + return super.onDoubleTap(e); + } + + @Override public boolean onDoubleTapEvent(MotionEvent e) { + switch (e.getActionMasked()) { + case MotionEvent.ACTION_UP: + Logger.e("第二次抬起时触发"); + break; + } + return super.onDoubleTapEvent(e); + } +}); +``` + +如果你不需要控制这么细微的话,忽略即可(Logger 是我自己封装的日志库,忽略即可)。 + +#### 2.3 OnGestureListener + +这个是手势检测中较为核心的一个部分了,主要检测以下类型事件:按下(Down)、 一扔(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress) 和 单击抬起(SingleTapUp)。 + +##### 2.3.1 onDown + +```java +@Override public boolean onDown(MotionEvent e) { + return true; +} +``` + +看过前面的文章应该知道,down 在事件分发体系中是一个较为特殊的事件,为了保证事件被唯一的 View 消费,哪个 View 消费了 down 事件,后续的内容就会传递给该 View。如果我们想让一个 View 能够接收到事件,有两种做法: + +1、让该 View 可以点击,因为可点击状态会默认消费 down 事件。 + +2、手动消费掉 down 事件。 + +由于图片、文本等一些控件默认是不可点击的,所以我们要么声明它们的 clickable 为 true,要么在发生 down 事件是返回 true。所以 onDown 在这里的作用就很明显了,就是为了保证让该控件能拥有消费事件的能力,以接受后续的事件。 + +##### 2.3.2 onFling + +Failing 中文直接翻译过来就是一扔、抛、甩,最常见的场景就是在 ListView 或者 RecyclerView 上快速滑动时手指抬起后它还会滚动一段时间才会停止。onFling 就是检测这种手势的。 + +```java +@Override +public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float + velocityY) { + return super.onFling(e1, e2, velocityX, velocityY); +} +``` + +在 onFling 的回调中共有四个参数,分别是: + +| 参数 | 简介 | +| --------- | ------------------ | +| e1 | 手指按下时的 Event。 | +| e2 | 手指抬起时的 Event。 | +| velocityX | 在 X 轴上的运动速度(像素/秒)。 | +| velocityY | 在 Y 轴上的运动速度(像素/秒)。 | + +我们可以通过 e1 和 e2 获取到手指按下和抬起时的坐标、时间等相关信息,通过 velocityX 和 velocityY 获取到在这段时间内的运动速度,单位是像素/秒(即 1 秒内滑动的像素距离)。 + +这个我们自己用到的地方比较少,但是也可以帮助我们简单的做出一些有趣的效果,例如下面的这种弹球效果,会根据滑动的力度和方向产生不同的弹跳效果。 + +![](https://ww2.sinaimg.cn/large/006tKfTcgy1fhe10uwa4fg308c0e6wn5.gif) + +其实这种原理非常简单,简化之后如下: + +1. 记录 velocityX 和 velocityY 作为初始速度,之后不断让速度衰减,直至为零。 +2. 根据速度和当前小球的位置计算一段时间后的位置,并在该位置重新绘制小球。 +3. 判断小球边缘是否碰触控件边界,如果碰触了边界则让速度反向。 + +根据这三条基本的逻辑就可以做出比较像的弹球效果,[具体的Demo可以看这里](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Demo/FailingBall.zip)。 + +##### 2.3.3 onLongPress + +这个是检测长按事件的,即手指按下后不抬起,在一段时间后会触发该事件。 + +```java +@Override +public void onLongPress(MotionEvent e) { +} +``` + +##### 2.3.4 onScroll + +onScroll 就是监听滚动事件的,它看起来和 onFaling 比较像,不同的是,onSrcoll 后两个参数不是速度,而是滚动的距离。 + +```java +@Override +public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float + distanceY) { + return super.onScroll(e1, e2, distanceX, distanceY); +} +``` + +| 参数 | | +| --------- | ----------- | +| e1 | 手指按下时的Event | +| e2 | 手指抬起时的Event | +| distanceX | 在 X 轴上划过的距离 | +| distanceY | 在 Y 轴上划过的距离 | + +##### 2.3.5 onShowPress + +它是用户按下时的一种回调,主要作用是给用户提供一种视觉反馈,可以在监听到这种事件时可以让控件换一种颜色,或者产生一些变化,告诉用户他的动作已经被识别。 + +不过这个消息和 onSingleTapConfirmed 类似,也是一种延时回调,延迟时间是 180 ms,假如用户手指按下后立即抬起或者事件立即被拦截,时间没有超过 180 ms的话,这条消息会被 remove 掉,也就不会触发这个回调。 + +```java +@Override +public void onShowPress(MotionEvent e) { +} +``` + +##### 2.3.6 onSingleTapUp + +```java +@Override +public boolean onSingleTapUp(MotionEvent e) { + return super.onSingleTapUp(e); +} +``` + +这个也很容易理解,就是用户单击抬起时的回调,但是它和上面的 `onSingleTapConfirmed` 之间有何不同呢?和 `onClick` 又有何不同呢? + +单击事件触发: + +```java +GCS: onSingleTapUp +GCS: onClick +GCS: onSingleTapConfirmed +``` + +| 类型 | 触发次数 | 摘要 | +| -------------------- | ---- | ---- | +| onSingleTapUp | 1 | 单击抬起 | +| onSingleTapConfirmed | 1 | 单击确认 | +| onClick | 1 | 单击事件 | + +双击事件触发: + +```java +GCS: onSingleTapUp +GCS: onClick +GCS: onDoubleTap // <- 双击 +GCS: onClick +``` + +| 类型 | 触发次数 | 摘要 | +| -------------------- | ---- | ------------ | +| onSingleTapUp | 1 | 在双击的第一次抬起时触发 | +| onSingleTapConfirmed | 0 | 双击发生时不会触发。 | +| onClick | 2 | 在双击事件时触发两次。 | + +可以看出来这三个事件还是有所不同的,根据自己实际需要进行使用即可 + +#### 2.4 SimpleOnGestureListener + +这个里面并没有什么内容,只是对上面三种 Listener 的空实现,在上面的例子中使用的基本都是这监听器。因为它用起来更方便一点。 + +这主要是 GestureDetector 构造函数的设计问题,以只监听 OnDoubleTapListener 为例,如果想要使用 OnDoubleTapListener 接口则需要这样进行设置: + +```java +GestureDetector detector = new GestureDetector(this, new GestureDetector + .SimpleOnGestureListener()); +detector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() { + @Override public boolean onSingleTapConfirmed(MotionEvent e) { + Toast.makeText(MainActivity.this, "单击确认", Toast.LENGTH_SHORT).show(); + return false; + } + + @Override public boolean onDoubleTap(MotionEvent e) { + Toast.makeText(MainActivity.this, "双击", Toast.LENGTH_SHORT).show(); + return false; + } + + @Override public boolean onDoubleTapEvent(MotionEvent e) { + // Toast.makeText(MainActivity.this,"",Toast.LENGTH_SHORT).show(); + return false; + } +}); +``` + +既然都已经创建 SimpleOnGestureListener 了,再创建一个 OnDoubleTapListener 显然十分浪费,如果构造函数不使用 SimpleOnGestureListener,而是使用 OnGestureListener 的话,会多出几个无用的空实现,显然很浪费,所以在一般情况下,老老实实的使用 SimpleOnGestureListener 就好了。 + +### 3. 相关方法 + +除了各类监听器之外,与 GestureDetector 相关的方法其实并不多,只有几个,下面来简单介绍一下。 + +| 方法 | 摘要 | +| ----------------------- | ---------------------------------------- | +| setIsLongpressEnabled | 通过布尔值设置是否允许触发长按事件,true 表示允许,false 表示不允许。 | +| isLongpressEnabled | 判断当前是否允许触发长按事件,true 表示允许,false 表示不允许。 | +| onTouchEvent | 这个是其中一个重要的方法,在最开始已经演示过使用方式了。 | +| onGenericMotionEvent | 这个是在 API 23 之后才添加的内容,主要是为 OnContextClickListener 服务的,暂时不用关注。 | +| setContextClickListener | 设置 ContextClickListener 。 | +| setOnDoubleTapListener | 设置 OnDoubleTapListener 。 | + +### 结语 + +关于手势检测部分的 GestureDetector 相关内容基本就这么多了,其实手势检测还有一个 ScaleGestureDetector 也是为手势检测服务的,限于篇幅,本次就讲这么多吧。 + +其实手势检测辅助类 GestureDetector 本身并不是很复杂,带上注释等内容才不到1000行,感兴趣的可以自己研究一下实现方式。 + +## About Me + +### 作者微博: @GcsSloop + + + +## 参考资料 +[文档 · GestureDetector ](https://developer.android.com/reference/android/view/GestureDetector.html) +[源码 · GestureDetector](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/GestureDetector.java) \ No newline at end of file diff --git a/CustomView/Base/[03]Color.md b/CustomView/Base/[03]Color.md index 86d3983c..300b9cc5 100644 --- a/CustomView/Base/[03]Color.md +++ b/CustomView/Base/[03]Color.md @@ -32,7 +32,7 @@ B(Blue) | 蓝色 | 无色 | 蓝色 *其中 A R G B 的取值范围均为0~255(即16进制的0x00~0xff)* -A 从ox00到oxff表示从透明到不透明。 +A 从0x00到0xff表示从透明到不透明。 RGB 从0x00到0xff表示颜色从浅到深。 @@ -131,7 +131,7 @@ PicPick具备了截取全屏、活动窗口、指定区域、固定区域、手 **注意:** -1.这里我们一般把每个通道的取值从0(ox00)到255(0xff)映射到0到1的浮点数表示。 +1.这里我们一般把每个通道的取值从0(0x00)到255(0xff)映射到0到1的浮点数表示。 2.这里等式右边的“绘制的颜色"、“Canvas上的原有颜色”都是经过预乘了自己的Alpha通道的值。如绘制颜色:0x88ffffff,那么参与运算时的每个颜色通道的值不是1.0,而是(1.0 * 0.5333 = 0.5333)。 (其中0.5333 = 0x88/0xff) diff --git a/CustomView/Demo/FailingBall.zip b/CustomView/Demo/FailingBall.zip new file mode 100644 index 00000000..92f0b8f5 Binary files /dev/null and b/CustomView/Demo/FailingBall.zip differ diff --git a/README.md b/README.md index e3b614e8..1e8b8821 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,30 @@ * [安卓自定义View进阶 - MotionEvent详解](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B16%5DMotionEvent.md) * [安卓自定义View进阶 - 特殊形状控件事件处理方案](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B17%5Dtouch-matrix-region.md) * [安卓自定义View进阶 - 多点触控详解](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B18%5Dmulti-touch.md) + * [安卓自定义View进阶 - 手势检测(GestureDetector)](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B19%5Dgesture-detector.md) + * [安卓自定义View进阶 - 缩放手势检测(ScaleGestureDetector)](http://www.gcssloop.com/customview/scalegesturedetector) + * [安卓自定义View进阶 - 画笔基础(Paint)](http://www.gcssloop.com/customview/paint-base) * [ViewSupport - 自定义View工具包](https://github.com/GcsSloop/ViewSupport) ****** +## 雕虫晓技 + +* [雕虫晓技(一) 组件化](http://www.gcssloop.com/gebug/componentr) +* [雕虫晓技(二) 编码](http://www.gcssloop.com/gebug/coding) +* [雕虫晓技(三) 通用圆角布局全解析](http://www.gcssloop.com/gebug/rclayout) +* [雕虫晓技(四) 搭建私有Maven仓库(带容灾备份)](http://www.gcssloop.com/gebug/maven-private) +* [雕虫晓技(五) 网格分页布局源码解析(上) (付费)](https://xiaozhuanlan.com/topic/5841730926) +* [雕虫晓技(六) 网格分页布局源码解析(下) (付费)](https://xiaozhuanlan.com/topic/1456397082) +* [雕虫晓技(七) 用旧Android手机做远程摄像头](http://www.gcssloop.com/gebug/internet-ip-webcam) +* [雕虫晓技(八) Android与数据流的斗争](http://www.gcssloop.com/gebug/android-stream) +* [雕虫晓技(九) Netty与私有协议框架](http://www.gcssloop.com/gebug/netty-private-protocol) +* [雕虫晓技(十) Android超简单气泡效果](http://www.gcssloop.com/gebug/bubble-sample) + +****** + ## [教程类](https://github.com/GcsSloop/AndroidNote/tree/master/Course/README.md) * [在AndroidStudio中使用PlantUML(Win)](https://github.com/GcsSloop/AndroidNote/blob/master/Course/HowToUsePlantUMLInAS.md) @@ -93,6 +111,9 @@ ## 开源库 +* [arc-seekbar - 弧形SeekBar](https://github.com/GcsSloop/arc-seekbar) +* [encrypt - 加密工具包](https://github.com/GcsSloop/encrypt) +* [rclayout - 通用圆角布局](https://github.com/GcsSloop/rclayout) * [FontsManager - 快速替换字体](https://github.com/GcsSloop/FontsManager) * [Rocker - 自定义摇杆](https://github.com/GcsSloop/Rocker) * [LeafLoading - 进度条](https://github.com/GcsSloop/LeafLoading) @@ -139,10 +160,19 @@ * 商业用途请点击最下面图片联系本人。 * 微信公众号转载一律不授权 `原创` 标志。 +## 捐赠 + +#### 如果你觉得我的文章有帮助的话,捐赠一些晶石,鼓励我继续研究! 🐾 + +| | | +| ---------------------------------------- | ---------------------------------------- | +| ![](http://www.gcssloop.com/assets/images/wechat.png) | ![](http://www.gcssloop.com/assets/images/alipay.png) | + ## 交流群 QQ群:612310796 微信群:加我个人微信 GcsSloop,备注加群。 +![](https://ww3.sinaimg.cn/large/006tNc79gy1fl8bemmtmxj30p005k406.jpg) ### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)