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/Course/Markdown/markdown-html.md b/Course/Markdown/markdown-html.md index e62c3b86..ec82fc0f 100644 --- a/Course/Markdown/markdown-html.md +++ b/Course/Markdown/markdown-html.md @@ -1,2 +1,3 @@ -Markdown 网页格式兼容 - +# Markdown 网页格式兼容 + +Markdown 作为一种标记型语言,在大多数情况下都是需要转换为 HTML 格式的,所以 Markdown 理论上是兼容 HTML 语法的,在 Markdown 所提供的标记无法满足我们需要的时候,可以尝试使用 HTML 相关语法来实现。 \ No newline at end of file diff --git a/Course/README.md b/Course/README.md index b9fd97f3..d2e3d8c9 100644 --- a/Course/README.md +++ b/Course/README.md @@ -2,7 +2,7 @@

- - - + + +

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 86336f5e..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/[04]Canvas_PictureText.md b/CustomView/Advance/[04]Canvas_PictureText.md index ba7f2c4f..06bffcd5 100644 --- a/CustomView/Advance/[04]Canvas_PictureText.md +++ b/CustomView/Advance/[04]Canvas_PictureText.md @@ -415,9 +415,9 @@ PS:图片左上角位置默认为坐标原点。 | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | | 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -假设我我们指定star为1,end为3,那么最终截取的字符串就是"BC"。 +假设我们指定start为1,end为3,那么最终截取的字符串就是"BC"。 -一般来说,**使用start和end指定的区间是前闭后开的,即包含start指定的下标,而不包含end指定的下标**,故[1,3)最后获取到的下标只有 下标1 和 下标2 的字符,就是"BC". +一般来说,**使用start和end指定的区间是前闭后开的,即包含start指定的下标,而不包含end指定的下标**,故[1,3)最后获取到的下标只有 下标1 和 下标2 的字符,就是"BC"。 示例: ``` java @@ -430,7 +430,7 @@ PS:图片左上角位置默认为坐标原点。 另外,对于字符数组char[]我们截取字符串使用起始位置(index)和长度(count)来确定。 -同样,我们指定index为1,count为3,那么最终截取到的字符串是"BCD". +同样,我们指定index为1,count为3,那么最终截取到的字符串是"BCD"。 其实就是从下标位置为1处向后数3位就是截取到的字符串,示例: ``` java diff --git a/CustomView/Advance/[05]Path_Basic.md b/CustomView/Advance/[05]Path_Basic.md index 85f12f53..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) | 开放路径 | 没有首尾相接形成封闭区域 | > 这个是我随便画的,仅为展示一下区别,请无视我灵魂画师一般的绘图水准。 @@ -239,7 +239,7 @@ close方法用于连接当前最后一个点和最初的一个点(如果两个 **这一类就是在path中添加一个基本形状,基本形状部分和前面所讲的绘制基本形状并无太大差别,详情参考[Canvas(1)颜色与基本形状](https://github.com/GcsSloop/AndroidNote/blob/master/%E9%97%AE%E9%A2%98/Canvas/Canvas(1).md), 本次只将其中不同的部分摘出来详细讲解一下。** -**仔细观察一下第一类是方法,无一例外,在最后都有一个_Path.Direction_,这是一个什么神奇的东东?** +**仔细观察一下第一类的方法,无一例外,在最后都有一个_Path.Direction_,这是一个什么神奇的东东?** Direction的意思是 方向,趋势。 点进去看一下会发现Direction是一个枚举(Enum)类型,里面只有两个枚举常量,如下: @@ -369,7 +369,7 @@ Direction的意思是 方向,趋势。 点进去看一下会发现Direction是 -首先我们新建地方两个Path(矩形和圆形)中心都是坐标原点,我们在将包含圆形的path添加到包含矩形的path之前将其进行移动了一段距离,最终绘制出来的效果就如上面所示。 +首先我们新建的两个Path(矩形和圆形)中心都是坐标原点,我们在将包含圆形的path添加到包含矩形的path之前将其进行移动了一段距离,最终绘制出来的效果就如上面所示。 #### 第三类(addArc与arcTo) 方法预览: @@ -524,12 +524,12 @@ log 输出结果: **但是第二个方法最后怎么会有一个path作为参数?** -其实第二个方法中最后的参数das是存储平移后的path的。 +其实第二个方法中最后的参数dst是存储平移后的path的。 | dst状态 | 效果 | | ----------- | ------------------------------ | | dst不为空 | 将当前path平移后的状态存入dst中,不会影响当前path | -| dat为空(null) | 平移将作用于当前path,相当于第一种方法 | +| dst为空(null) | 平移将作用于当前path,相当于第一种方法 | 示例: ``` java diff --git a/CustomView/Advance/[06]Path_Bezier.md b/CustomView/Advance/[06]Path_Bezier.md index 4384d9c2..e98db733 100644 --- a/CustomView/Advance/[06]Path_Bezier.md +++ b/CustomView/Advance/[06]Path_Bezier.md @@ -3,7 +3,7 @@ ### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop) ### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md) -在上一篇文章[Path之基本图形](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B05%5DPath_BasicGraphics.md)中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线。 +在上一篇文章[Path之基本图形](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B05%5DPath_Basic.md)中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线。 ****** @@ -122,7 +122,7 @@ > **PS: 三阶曲线对应的方法是cubicTo** -#### [贝塞尔曲线速查表](https://github.com/GcsSloop/AndroidNote/blob/master/QuickChart/Bessel.md) +#### [贝塞尔曲线速查表](https://github.com/GcsSloop/AndroidNote/blob/master/QuickChart/Bezier.md) #### 强烈推荐[点击这里](http://bezier.method.ac/)练习贝塞尔曲线,可以加深对贝塞尔曲线的理解程度。 diff --git a/CustomView/Advance/[07]Path_Over.md b/CustomView/Advance/[07]Path_Over.md index 19007696..dad580ce 100644 --- a/CustomView/Advance/[07]Path_Over.md +++ b/CustomView/Advance/[07]Path_Over.md @@ -113,8 +113,8 @@ ![](http://ww2.sinaimg.cn/large/005Xtdi2jw1f42368af2jj308c0dwt8z.jpg) > ->P1: 从P1点发出一条射线,沿射线防线移动,并没有与边相交点部分,环绕数为0,故P1在图形外边。
->P2: 从P2点发出一条射线,沿射线方向移动,与图形点左侧边相交,该边从左到右穿过穿过射线,环绕数-1,最终环绕数为-1,故P2在图形内部。
+>P1: 从P1点发出一条射线,沿射线方向移动,并没有与边相交点部分,环绕数为0,故P1在图形外边。
+>P2: 从P2点发出一条射线,沿射线方向移动,与图形点左侧边相交,该边从左到右穿过射线,环绕数-1,最终环绕数为-1,故P2在图形内部。
>P3: 从P3点发出一条射线,沿射线方向移动,在第一个交点处,底边从右到左穿过射线,环绕数+1,在第二个交点处,右侧边从左到右穿过射线,环绕数-1,最终环绕数为0,故P3在图形外部。
通常,这两种方法的判断结果是相同的,但也存在两种方法判断结果不同的情况,如下面这种情况: @@ -144,7 +144,7 @@ Android中的填充模式有四种,是封装在Path中的一个枚举。 我们可以看到上面有四种模式,分成两对,例如 "奇偶规则" 与 "反奇偶规则" 是一对,它们之间有什么关系呢? -Inverse 和含义是“相反,对立”,说明反奇偶规则刚好与奇偶规则相反,例如对于一个矩形而言,使用奇偶规则会填充矩形内部,而使用反奇偶规则会填充矩形外部,这个会在后面示例中代码展示两者对区别。 +Inverse 的含义是“相反,对立”,说明反奇偶规则刚好与奇偶规则相反,例如对于一个矩形而言,使用奇偶规则会填充矩形内部,而使用反奇偶规则会填充矩形外部,这个会在后面示例中代码展示两者的区别。 #### Android与填充模式相关的方法 @@ -176,7 +176,7 @@ Inverse 和含义是“相反,对立”,说明反奇偶规则刚好与奇偶 path.addRect(-200,-200,200,200, Path.Direction.CW); // 给Path中添加一个矩形 ``` -下面两张图片分别是在奇偶规则于反奇偶规则的情况下绘制的结果,可以看出其填充的区域刚好相反: +下面两张图片分别是在奇偶规则与反奇偶规则的情况下绘制的结果,可以看出其填充的区域刚好相反: > PS: 白色为背景色,黑色为填充色。 @@ -214,7 +214,7 @@ Inverse 和含义是“相反,对立”,说明反奇偶规则刚好与奇偶 ### 布尔操作(API19) -布尔操作与我们中学所学的集合操作非常像,只要知道集合操作中等交集,并集,差集等操作,那么理解布尔操作也是很容易的。 +布尔操作与我们中学所学的集合操作非常像,只要知道集合操作中的交集,并集,差集等操作,那么理解布尔操作也是很容易的。 **布尔操作是两个Path之间的运算,主要作用是用一些简单的图形通过一些规则合成一些相对比较复杂,或难以直接得到的图形**。 @@ -257,7 +257,7 @@ Path的布尔运算有五种逻辑,如下: #### 布尔运算方法 -通过前面到理论知识铺垫,相信大家对布尔运算已经有了基本的认识和理解,下面我们用代码演示一下布尔运算: +通过前面的理论知识铺垫,相信大家对布尔运算已经有了基本的认识和理解,下面我们用代码演示一下布尔运算: 在Path中的布尔运算有两个方法 @@ -268,7 +268,7 @@ Path的布尔运算有五种逻辑,如下: 两个方法中的返回值用于判断布尔运算是否成功,它们使用方法如下: -``` `java +``` java // 对 path1 和 path2 执行布尔运算,运算方式由第二个参数指定,运算结果存入到path1中。 path1.op(path2, Path.Op.DIFFERENCE); @@ -334,7 +334,7 @@ Path的布尔运算有五种逻辑,如下: | 参数 | 作用 | | ------ | ------------------------------- | | bounds | 测量结果会放入这个矩形 | -| exact | 是否精确测量,目前这一个参数作用已经废弃,一般写true即可。 | +| exact | 是否精确测量,目前这一个参数作用已经废弃,一般写true即可 | 关于exact如有疑问可参见Google官方的提交记录[Path.computeBounds()](https://code.google.com/p/android/issues/detail?id=4070) @@ -369,7 +369,7 @@ Path的布尔运算有五种逻辑,如下: ### 重置路径 -重置Path有两个方法,分别是reset和rewind,两者区别主要有一下两点: +重置Path有两个方法,分别是reset和rewind,两者区别主要有以下两点: | 方法 | 是否保留FillType设置 | 是否保留原有数据结构 | | ------ | :------------: | :--------: | @@ -385,7 +385,7 @@ _因为“FillType”影响的是显示效果,而“数据结构”影响的 ## 总结 -Path中常用的方法到此已经结束,希望能够帮助大家加深对Path对理解运用,让大家能够用Path愉快的玩耍。( ̄▽ ̄) +Path中常用的方法到此已经结束,希望能够帮助大家加深对Path的理解运用,让大家能够用Path愉快的玩耍。( ̄▽ ̄) (,,• ₃ •,,) #### PS: 由于本人水平有限,某些地方可能存在误解或不准确,如果你对此有疑问可以提交Issues进行反馈。 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 852ee6d9..e3985de3 100644 --- a/CustomView/Advance/[09]Matrix_Basic.md +++ b/CustomView/Advance/[09]Matrix_Basic.md @@ -12,17 +12,7 @@ 它看起来大概是下面这样: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -MSCALE\\_X & MSKEW\\_X & MTRANS\\_X \\\\ -\\ -MSKEW\\_Y & MSCALE\\_Y & MTRANS\\_Y \\\\ -\\ -MPERSP\\_0 & MPERSP\\_1 & MPERSP\\_2 -\\end{1} -\\right ] -$$) +![](http://ww1.sinaimg.cn/large/006tKfTcly1fdz72rjfnjj30ak01yglo.jpg) **Matrix作用就是坐标映射,那么为什么需要Matrix呢? 举一个简单的例子:** @@ -75,36 +65,11 @@ Matrix 是一个矩阵,最根本的作用就是坐标转换,下面我们就 ### 1.缩放(Scale) -![](http://latex.codecogs.com/png.latex?$$x = k_1 x_0 $$) - -![](http://latex.codecogs.com/png.latex?$$y = k_2 y_0 $$) +![](http://ww2.sinaimg.cn/large/006tKfTcly1fdz7baj17gj302h01rdfr.jpg) 用矩阵表示: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -x\\\\ -y\\\\ -1 -\\end{1} -\\right ] - = -\\left [ -\\begin{matrix} -k_1 & 0 & 0 \\\\ - 0 & k_2 & 0 \\\\ - 0 & 0 & 1 -\\end{1} -\\right ] -\\left [ -\\begin{matrix} -x_0 \\\\ -y_0 \\\\ -1 -\\end{1} -\\right ] -$$) +![](http://ww3.sinaimg.cn/large/006tKfTcly1fdz7busaiej3062020mx4.jpg) > 你可能注意到了,我们坐标多了一个1,这是使用了齐次坐标系的缘故,在数学中我们的点和向量都是这样表示的(x, y),两者看起来一样,计算机无法区分,为此让计算机也可以区分它们,增加了一个标志位,增加之后看起来是这样:
> @@ -123,36 +88,11 @@ $$) #### 水平错切 -![](http://latex.codecogs.com/png.latex?$$ x = x_0 + ky_0 $$) - -![](http://latex.codecogs.com/png.latex?$$ y = y_0 $$) +![](http://ww3.sinaimg.cn/large/006tKfTcly1fdz7d0niaqj303601mglj.jpg) 用矩阵表示: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -x\\\\ -y\\\\ -1 -\\end{1} -\\right ] - = -\\left [ -\\begin{matrix} - 1 & k & 0 \\\\ - 0 & 1 & 0 \\\\ - 0 & 0 & 1 -\\end{1} -\\right ] -\\left [ -\\begin{matrix} -x_0\\\\ -y_0\\\\ -1 -\\end{1} -\\right ] -$$) +![](http://ww4.sinaimg.cn/large/006tKfTcly1fdz7dryrfcj305m020glk.jpg) 图例: @@ -160,36 +100,11 @@ $$) #### 垂直错切 -![](http://latex.codecogs.com/png.latex?$$ x = x_0 $$) - -![](http://latex.codecogs.com/png.latex?$$ y = kx_0 + y_0 $$) +![](http://ww3.sinaimg.cn/large/006tKfTcly1fdz7esq5j4j303701pdfr.jpg) 用矩阵表示: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -x\\\\ -y\\\\ -1 -\\end{1} -\\right ] - = -\\left [ -\\begin{matrix} - 1 & 0 & 0 \\\\ - k & 1 & 0 \\\\ - 0 & 0 & 1 -\\end{1} -\\right ] -\\left [ -\\begin{matrix} -x_0\\\\ -y_0\\\\ -1 -\\end{1} -\\right ] -$$) +![](http://ww4.sinaimg.cn/large/006tKfTcly1fdz7ffdxauj305n024glk.jpg) 图例: @@ -199,36 +114,11 @@ $$) > 水平错切和垂直错切的复合。 -![](http://latex.codecogs.com/png.latex?$$ x = x_0 + k_1 y_0 $$) - -![](http://latex.codecogs.com/png.latex?$$ y = k_2 x_0 + y_0 $$) +![](http://ww4.sinaimg.cn/large/006tKfTcly1fdz7g0lmcaj303801mq2v.jpg) 用矩阵表示: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -x\\\\ -y\\\\ -1 -\\end{1} -\\right ] - = -\\left [ -\\begin{matrix} - 1 & k_1 & 0 \\\\ - k_2 & 1 & 0 \\\\ - 0 & 0 & 1 -\\end{1} -\\right ] -\\left [ -\\begin{matrix} -x_0\\\\ -y_0\\\\ -1 -\\end{1} -\\right ] -$$) +![](http://ww2.sinaimg.cn/large/006tKfTcly1fdz7gkg5dej3062021mx4.jpg) 图例: @@ -238,49 +128,11 @@ $$) 假定一个点 A(x0, y0) ,距离原点距离为 r, 与水平轴夹角为 α 度, 绕原点旋转 θ 度, 旋转后为点 B(x, y) 如下: -![](http://latex.codecogs.com/png.latex?$$ x_0 = r \\cdot cos \\alpha $$) - -![](http://latex.codecogs.com/png.latex?$$ y_0 = r \\cdot sin \\alpha $$) - -![](http://latex.codecogs.com/png.latex?$$ -x = r \\cdot cos( \\alpha + \\theta) -= r \\cdot cos \\alpha \\cdot cos \\theta - r \\cdot sin \\alpha \\cdot sin \\theta -= x_0 \\cdot cos \\theta - y_0 \\cdot sin \\theta -$$) - -![](http://latex.codecogs.com/png.latex?$$ -y = r \\cdot sin( \\alpha + \\theta) -= r \\cdot sin \\alpha \\cdot cos \\theta + r \\cdot cos \\alpha \\cdot sin \\theta -= y_0 \\cdot cos \\theta + x_0 \\cdot sin \\theta -$$) +![](http://ww3.sinaimg.cn/large/006tKfTcly1fdz7h61ddsj30gm03twel.jpg) 用矩阵表示: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -x\\\\ -y\\\\ -1 -\\end{1} -\\right ] - = -\\left [ -\\begin{matrix} -cos(\\theta) & -sin(\\theta) & 0 \\\\ -sin(\\theta) & cos(\\theta) & 0 \\\\ - 0 & 0 & 1 -\\end{1} -\\right ] - . -\\left [ -\\begin{matrix} -x_0\\\\ -y_0\\\\ -1 -\\end{1} -\\right ] -$$) +![](http://ww2.sinaimg.cn/large/006tKfTcly1fdz7hn7pbdj308i0240sq.jpg) 图例: @@ -290,37 +142,11 @@ $$) > 此处也是使用齐次坐标的优点体现之一,实际上前面的三个操作使用 2x2 的矩阵也能满足需求,但是使用 2x2 的矩阵,无法将平移操作加入其中,而将坐标扩展为齐次坐标后,将矩阵扩展为 3x3 就可以将算法统一,四种算法均可以使用矩阵乘法完成。 -![](http://latex.codecogs.com/png.latex?$$ x = x_0 + \\Delta x $$) - -![](http://latex.codecogs.com/png.latex?$$ y = y_0 + \\Delta y $$) +![](http://ww4.sinaimg.cn/large/006tKfTcly1fdz7igi28cj302w01kdfr.jpg) 用矩阵表示: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -x\\\\ -y\\\\ -1 -\\end{1} -\\right ] - = -\\left [ -\\begin{matrix} -1 & 0 & \\Delta x \\\\ -0 & 1 & \\Delta y \\\\ -0 & 0 & 1 -\\end{1} -\\right ] - . -\\left [ -\\begin{matrix} -x_0\\\\ -y_0\\\\ -1 -\\end{1} -\\right ] -$$) +![](http://ww2.sinaimg.cn/large/006tKfTcly1fdz7izsq8hj306b022mx4.jpg) 图例: @@ -336,15 +162,15 @@ $$) 前乘相当于矩阵的右乘: -![](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) > 这表示一个矩阵与一个特殊矩阵后乘后构造出结果矩阵。 @@ -473,42 +299,11 @@ Log.e(TAG, "MatrixTest" + matrix.toShortString()); 之所以平移距离是 MTRANS\_X = 500,MTRANS\_Y = 800,那是因为执行 Translate 之前 Matrix 已经具有了一个缩放比例。在右乘的时候影响到了具体的数值计算,可以用矩阵乘法计算一下。 -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -0.5 & 0 & 0 \\\\ -0 & 0.8 & 0 \\\\ -0 & 0 & 1 -\\end{1} -\\right ] -\\cdot -\\left [ -\\begin{matrix} -1 & 0 & 1000 \\\\ -0 & 1 & 1000 \\\\ -0 & 0 & 1 -\\end{1} -\\right ] = -\\left [ -\\begin{matrix} -0.5*1+0*0+0*0 & 0.5*0+0*1+0*0 & 0.5*1000+0*1000+0*1\\\\ -0*1+0.8*0+0*0 & 0*0+0.8*1+0*1 & 0*1000+0.8*1000+0*1\\\\ -0*1+0*0+1*0 & 0*0+0*1+1*0 & 0*1000+0*1000+1*1 -\\end{1} -\\right ] -$$) +![](http://ww3.sinaimg.cn/large/006tKfTcly1fdz7lhb20fj30lz01zgm8.jpg) 最终结果为: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} -0.5 & 0 & 500\\\\ -0 & 0.8 & 800\\\\ -0 & 0 & 1 -\\end{1} -\\right ] -$$) +![](http://ww2.sinaimg.cn/large/006tKfTcly1fdz7m2pgyuj303o022wef.jpg) 当 T*S 的时候,缩放比例则不会影响到 MTRANS\\_X 和 MTRANS\\_Y ,具体可以使用矩阵乘法自己计算一遍。 @@ -620,40 +415,7 @@ m.preScale(sx, sy); ``` 用矩阵表示: - -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} - & &\\\\ - & Result Matrix &\\\\ - & & -\\end{1} -\\right ] - = - \\left [ -\\begin{matrix} - & &\\\\ - & Initial Matrix &\\\\ - & & -\\end{1} -\\right ] -\\cdot -\\left [ -\\begin{matrix} -1 & 0 & \\Delta x \\\\ -0 & 1 & \\Delta y \\\\ -0 & 0 & 1 -\\end{1} -\\right ] -\\cdot -\\left [ -\\begin{matrix} -sx & 0 & 0\\\\ -0 & sy & 0\\\\ -0 & 0 & 1 -\\end{1} -\\right ] -$$) +![](http://ww4.sinaimg.cn/large/006tKfTcly1fdz7mv29jhj30gg02374b.jpg) #### 2.仅用post: @@ -667,39 +429,7 @@ m.postTranslate(tx, ty); 用矩阵表示: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} - & &\\\\ - & Result Matrix &\\\\ - & & -\\end{1} -\\right ] - = -\\left [ -\\begin{matrix} -1 & 0 & \\Delta x \\\\ -0 & 1 & \\Delta y \\\\ -0 & 0 & 1 -\\end{1} -\\right ] -\\cdot -\\left [ -\\begin{matrix} -sx & 0 & 0\\\\ -0 & sy & 0\\\\ -0 & 0 & 1 -\\end{1} -\\right ] -\\cdot - \\left [ -\\begin{matrix} - & &\\\\ - & Initial Matrix &\\\\ - & & -\\end{1} -\\right ] -$$) +![](http://ww1.sinaimg.cn/large/006tKfTcly1fdz7nde6gcj30gh020dfv.jpg) #### 3.混合: @@ -725,39 +455,7 @@ m.preScale(sx, sy); 用矩阵表示: -![](http://latex.codecogs.com/png.latex?$$ -\\left [ -\\begin{matrix} - & &\\\\ - & Result Matrix &\\\\ - & & -\\end{1} -\\right ] - = -\\left [ -\\begin{matrix} -1 & 0 & \\Delta x \\\\ -0 & 1 & \\Delta y \\\\ -0 & 0 & 1 -\\end{1} -\\right ] -\\cdot - \\left [ -\\begin{matrix} - & &\\\\ - & Initial Matrix &\\\\ - & & -\\end{1} -\\right ] -\\cdot -\\left [ -\\begin{matrix} -sx & 0 & 0\\\\ -0 & sy & 0\\\\ -0 & 0 & 1 -\\end{1} -\\right ] -$$) +![](http://ww4.sinaimg.cn/large/006tKfTcly1fdz7o3i9kfj30gh021aa3.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/[18]multi-touch.md b/CustomView/Advance/[18]multi-touch.md index ff59eacc..98efa326 100644 --- a/CustomView/Advance/[18]multi-touch.md +++ b/CustomView/Advance/[18]multi-touch.md @@ -1,4 +1,4 @@ -# 多点触控详解 +# Android 多点触控详解 Android 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带大家了解 Android 多点触控相关的一些知识。 @@ -48,7 +48,7 @@ Android 多点触控详解,在前面的几篇文章中我们大致了解了 An 在引入多点触控之前,事件的类型很少,基本事件类型只有按下(down)、移动(move) 和 抬起(up),即便加上那些特殊的事件类型也只有几种而已,所以我们可以用几个常量来标记这些事件,在使用的时候使用 `getAction()` 方法来获取具体的事件,之后和这些常量进行对比就行了。 -在 Android 2.0 版本的时候,开始引入多点触控技术,由于技术上并不成熟,硬件和驱动也跟不上,很数设备只能支持追踪两三个点而已,因此在设计 API 上采取了一种简单粗暴的方案,添加了几个常量用语多点触控的事件类型的判断。 +在 Android 2.0 版本的时候,开始引入多点触控技术,由于技术上并不成熟,硬件和驱动也跟不上,多数设备只能支持追踪两三个点而已,因此在设计 API 上采取了一种简单粗暴的方案,添加了几个常量用于多点触控的事件类型的判断。 | 事件 | 简介 | | ----------------------- | -------------------- | @@ -95,7 +95,7 @@ switch (event.getAction()) { 注意观察上面编号和id的变化,有两个问题,**1、B手指的编号变化了。2、A手指和C手指id是相同的(A手指抬起后,C手指按下替代了A手指)。**所以这就引出了一个问题:如果存在 ACTION_POINTER_X_MOVE,那么X应该用什么标志呢?编号会变化,id虽然不会变化,但id会被复用,例如A手指抬起后C手指按下,C手指复用了A手指的id。所以不论使用哪一个都不能保证唯一性。 -当然了,解决问题最好的方式就是把问题抛出去,既然从硬件和软件上都不能保证唯一性和不变性,就不做区分了,因此所有的 move 事件都是 `ACTION_MOVE`, 具体是哪个手指产生的 move 用户可以根据其他事件(按下和抬起)来综合判断。 +当然了,解决问题最好的方式就是把问题抛出去,既然从硬件和软件上都不能保证唯一性和不变性,就不做区分了,因此所有的 move 事件都是 `ACTION_MOVE`, 具体是哪个手指产生的 move 用户可以结合其他事件(按下和抬起)来综合判断。 ### 2.超过4个手指怎么办? @@ -243,17 +243,17 @@ switch (event.getActionMasked()) { | 第4个手指按下 | ACTION_POINTER_DOWN (0x0000**01**05) | | **第3个手指抬起** | ACTION_POINTER_UP (0x0000**02**06) | -这个要和上一个对比这看,**重点观察第 3 个手指所触发事件区别**,在上一个示例中,随着第 2 个手指的抬起,第 3 个手指变化为第 2 个,所以抬起时触发的是第 2 根手指的抬起事件(删除线部分)。 +这个要和上一个对比这看,**重点观察第 3 个手指所触发事件区别**,在上一个示例中,随着第 2 个手指的抬起,第 3 个手指变化为第 2(01) 个,所以抬起时触发的是第 2 根手指的抬起事件(删除线部分)。 -但是,如果第 2 个手指抬起后,落在屏幕上另外一个手指会怎样?经过测试,发现另外**落下的手指会替代之前第 2 个手指的位置,系统判定为 2,而不是顺延下去变成 3**,但是如果继续落下其他的手指,数值则会顺延。 +但是,如果第 2 个手指抬起后,落在屏幕上另外一个手指会怎样?经过测试,发现另外**落下的手指会替代之前第 2 个手指的位置,系统判定为 2(01),而不是顺延下去变成 3(02),并且原本第3个手指的index变为原来数值(02)**,但是如果继续落下其他的手指,数值则会顺延。 -**即手指抬起时的 Index 会趋向于和按下时相同,虽然在手指数量不足时,Index 会变小,但是当手指变多时,Index 会保持和按下时一样。** +**即手指抬起时的 Index 会趋向于和按下时相同,虽然在手指数量不足时,Index 会变小,但是当手指变多时,Index 会趋向于保持和按下时一样。** > PS:由于程序是从0开始计数的,所以 0 就是 1, 1 就是 2 ... #### 3.4、对 move 事件无效。 -这个也比较容易理解,我们所取得的 Index 属性实际上是从事件上分离下来的,但是 move 事件始终为 0x0000**00**02,也就是说,在 move 时不论你移动哪个手指,使用 ` getActionIndex()` 获取到的始终是数值 0。 +这个也比较容易理解,我们所取得的 Index 属性实际上是从事件上分离下来的,但是 move 事件始终为 0x0000**00**02,也就是说,在 move 时不论你移动哪个手指,使用 `getActionIndex()` 获取到的始终是数值 0。 既然 move 事件无法用事件索引(Index)区别,那么该如何区分 move 是那个手指发出的呢?这就要用到 pointId 了,**pointId 和 index 最大的区别就是 pointId 是不变的,始终为第一次落下时生成的数值,不会受到其他手指抬起和落下的影响。** @@ -410,7 +410,7 @@ public class MultiTouchTest extends CustomView { 举一个简单的例子: -如果我们需要一个**可以用单指拖动的图片**。加入我们不进行多指触控的判断,像下面这样: +如果我们需要一个**可以用单指拖动的图片**。假如我们不进行多指触控的判断,像下面这样: **没有针对多指触控处理版本:** @@ -568,7 +568,7 @@ public class DragView extends CustomView { } ``` -可以看到,比起上一个版本并么有多多少代码,但是更加“智能”了,可以准确识别某一个手指,不会因为手指抬起而认错手指。 +可以看到,比起上一个版本,只添加了少量代码,就变得更加“智能”了,可以准确识别某一个手指,不会因为手指抬起而认错手指。 ![dragview2](http://ww2.sinaimg.cn/large/006y8lVagw1fbs1vmwpu4g308c0d4nf9.gif) @@ -610,4 +610,4 @@ public class DragView extends CustomView { -[motionevent]: http://www.gcssloop.com/customview/motionevent +[motionevent]: http://www.gcssloop.com/customview/motionevent \ No newline at end of file diff --git a/CustomView/Advance/[19]gesture-detector.md b/CustomView/Advance/[19]gesture-detector.md new file mode 100644 index 00000000..94e60e18 --- /dev/null +++ b/CustomView/Advance/[19]gesture-detector.md @@ -0,0 +1,386 @@ +# Android 手势检测(GestureDetector) + +Android 手势检测,主要是 GestureDetector 相关内容的用法和注意事项,本文依旧属于事件处理这一体系,部分内容会涉及到之前文章提及过的知识点,如果你没看过之前的文章,可以到 [自定义 View 系列](http://www.gcssloop.com/customview/CustomViewIndex) 来查看这些内容。 + +在开发 Android 手机应用过程中,可能需要对一些手势作出响应,如:单击、双击、长按、滑动、缩放等。这些都是很常用的手势。就拿最简单的双击来说吧,假如我们需要判断一个控件是否被双击(即在较短的时间内快速的点击两次),似乎是一个很容易的任务,但仔细考虑起来,要处理的细节问题也有不少,例如: + +1. **记录点击次数**,为了判断是否被点击超过 1 次,所以必须记录点击次数。 +2. **记录点击时间**,由于双击事件是较快速的点击两次,像点击一次后,过来几分钟再点击一次肯定不能算是双击事件,所以在记录点击次数的同时也要记录上一次的点击时间,我们可以设置本次点击距离上一次时间超过一定时间(例如:超过100ms)就不识别为双击事件。 +3. **点击状态重置**,在响应双击事件,或者判断不是双击事件的时候要重置计数器和上一次点击时间。重置既可以在点击的时候判断并进行重新设置,也可以使用定时器等超过一定时间后重置状态。 + +这样看起来,判断一个双击事件就有这么多麻烦事情,更别其他的手势了,虽然这些看起来都很简单,但设计起来需要考虑的细节情况实在是太多了。 + +那么有没有一种更好的方法来方便的检测手势呢?当然有啦,因为这些手势很常用,系统早就封装了一些方法给我们用,接下来我们就看看它们是如何使用的。 + +## GestureDetector + +> GestureDetector 可以使用 MotionEvents 检测各种手势和事件。GestureDetector.OnGestureListener 是一个回调方法,在发生特定的事件时会调用 Listener 中对应的方法回调。这个类只能用于检测触摸事件的 MotionEvent,不能用于轨迹球事件。 +> (话说轨迹球已经消失多长时间了,估计很多人都没见过轨迹球这种东西)。 +> +> 如何使用: +> +> - 创建一个 GestureDetector 实例。 +> - 在onTouchEvent(MotionEvent)方法中,确保调用 GestureDetector 实例的 onTouchEvent(MotionEvent)。回调中定义的方法将在事件发生时执行。 +> - 如果侦听 onContextClick(MotionEvent),则必须在 View 的 onGenericMotionEvent(MotionEvent)中调用 GestureDetector OnGenericMotionEvent(MotionEvent)。 + + GestureDetector 本身的方法比较少,使用起来也非常简单,下面让我们先看一下它的简单使用示例,分解开来大概需要三个步骤。 + +```java +// 1.创建一个监听回调 +SimpleOnGestureListener listener = new SimpleOnGestureListener() { + @Override public boolean onDoubleTap(MotionEvent e) { + Toast.makeText(MainActivity.this, "双击666", Toast.LENGTH_SHORT).show(); + return super.onDoubleTap(e); + } +}; + +// 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); + } +}); +``` + +接下来我们先了解一下 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/CustomViewRule.md b/CustomView/CustomViewRule.md index e69de29b..766fcaa0 100644 --- a/CustomView/CustomViewRule.md +++ b/CustomView/CustomViewRule.md @@ -0,0 +1,48 @@ +# 自定义View基本法 + +我们使用手机,是想要获取某些信息,而 View 是这些信息的直接展示界面,因为信息种类繁多,为了更好的展示这些信息, View 也必须有多种多样,Android 系统本身就给我们提供了不少类型的 View,但有时仍不能满足我们的需要,所以有时可能需要自定义 View 来完成任务。 + +自定义 View 有许多需要注意的地方,关于这些需要注意的内容,我都会整理在这里,其名为《自定义 View 基本法》。 + +#### 第一条:尽量避免自定义 View。 + +由于 View 直接承载了与用户交互的重任,所以必须要考虑到各种情况,例如: + +* 当没有设置宽高属性时,View 默认应该多大。 +* 横竖屏转换时 View 可能重新设定大小,此时应如何处理。 +* View 因为特殊情况被销毁后重建,应如何保存和恢复数据。 + +由于某些情况很特殊,触发条件也特殊,我们简单的实现了一个自定义了一个 View,可能在 99% 的情况下都是正常的,但在某些特殊情况下就会出问题。 + +而系统提供给我们的组件都是经过千锤百炼的,基本上考虑到了各种特殊情况的处理,所以通常情况下,系统提供给我们的组件稳定性要好一些。所以我的建议是,能使用系统提供的组件的尽量使用系统的。 + +除此之外,使用系统提供的组件也方便于其他人快速读懂项目,便于交流。 + +#### 第二条:尽量避免从头开始。 + +如果一定要使用自定义 View,那么尽量去继承系统已有的组件,并重写其中的部分方法,不要自己从头开始写。例如:图像相关的 View 可以考虑继承 ImageView,容器类 View 可以考虑继承 LinerLayout,RelativeLayout 等,原因同上。 + +#### 第三条:处理特殊情况。 + +针对能想到的一些特殊情况进行处理并且测试,尽量保证自定义 View 能适应各种特殊场景。 + +#### 第四条:留下文档。 + +**程序员有两大痛苦,1、别人写的项目居然没有文档。2、自己写的项目居然要写文档。** + +虽然有时候文档写起来确实挺麻烦,但是个人建议要留下一份文档,至少要为自己写的程序添加**有效的注释**,这不仅是方便他人,更重要的是方便自己以后修改项目。 + +* 于自定义View而言,首先要标明这个自定义View是解决什么问题,有怎样的效果,使用的场景如何。 +* 其次要在关键部位标注实现原理。(例如:显示圆形的ImageView,要标明圆形是如何实现的,使用的是遮罩还是剪裁。) +* 避免无效注释 (例如:在onDraw上面标注绘图),大家都知道这个是绘图,但绘制逻辑才是重点,要去标注绘制逻辑。 + +#### 第五条:面向结果编程。 + +我们既然使用自定义View,自然是想要实现一些系统组件无法实现的效果,所以要时刻谨记自己所需要的内容,让其中的所有逻辑都为这个结果服务,我自己实现自定义 View 一般有如下步骤: + +1. 原型,用我能想到的逻辑实现原型(demo),不管其代码复杂度,首先要得到结果,通常情况下,第一份代码可得性和整洁性都比较差。 +2. 优化,在原型的基础上对代码进行优化,剔除不必要的内容,例如尝试优化逻辑,对与一些重复性的内容抽取函数进行封装,想办法消灭一些中间变量。同时添加上必要注释,让其逻辑更加清晰易懂。 +3. 测试,对其进行场景测试,尽量保证其正常运行。 + + + 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/CustomView/README.md b/CustomView/README.md index 6fefef4f..cf5a50ac 100644 --- a/CustomView/README.md +++ b/CustomView/README.md @@ -5,52 +5,52 @@ ## 基础篇

- - - + + +

******* ## 进阶篇

- - - + + +

*******

- - - + + +

*******

- - - + + +

*******

- - - + + +

*******

- - - + + +

### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop) - + diff --git a/Lecture/README.md b/Lecture/README.md index adeb5d56..959ec327 100644 --- a/Lecture/README.md +++ b/Lecture/README.md @@ -1 +1,4 @@ # 演讲稿 + +* [程序员练级指北(郑州GDG-2016DevFest)](https://github.com/GcsSloop/AndroidNote/blob/master/Lecture/gdg-developer-growth-guide.md) + diff --git a/README.md b/README.md index 3b7c74ac..1e8b8821 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ ******

+ ## [自定义View](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md) * 基础篇 @@ -38,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) @@ -92,11 +111,20 @@ ## 开源库 +* [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) * [Rotate3dAnimation - 3D旋转动画(修正版)](https://github.com/GcsSloop/Rotate3dAnimation) +------ + +## 源码解析 + +- [AtomicFile 源码解析](https://github.com/GcsSloop/AndroidNote/blob/master/SourceAnalysis/AtomicFile.md) + ## 传送门 通往异世界的传送门,请谨慎使用。 @@ -132,6 +160,20 @@ * 商业用途请点击最下面图片联系本人。 * 微信公众号转载一律不授权 `原创` 标志。 +## 捐赠 + +#### 如果你觉得我的文章有帮助的话,捐赠一些晶石,鼓励我继续研究! 🐾 + +| | | +| ---------------------------------------- | ---------------------------------------- | +| ![](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) diff --git a/SourceAnalysis/AtomicFile.md b/SourceAnalysis/AtomicFile.md index 6f6d8995..78e669c2 100644 --- a/SourceAnalysis/AtomicFile.md +++ b/SourceAnalysis/AtomicFile.md @@ -71,7 +71,7 @@ public AtomicFile(File baseName) { ```java public FileOutputStream startWrite() throws IOException { - // 如果备份文件不存在,将原文件重命名为备份文件,并删除原文件 + // 当原文件存在,备份文件不存在的时候,原文件更名为备份文件 if (mBaseName.exists()) { if (!mBackupName.exists()) { // 如果原文件存在且备份文件不存在,直接将原文件重命名为备份文件 diff --git a/SourceAnalysis/CircularArray.md b/SourceAnalysis/CircularArray.md new file mode 100644 index 00000000..e69de29b