Showing preview only (438K chars total). Download the full file or copy to clipboard to get everything.
Repository: GcsSloop/AndroidNote
Branch: master
Commit: 21ba33e3d40c
Files: 66
Total size: 416.2 KB
Directory structure:
gitextract_f641qz65/
├── AudioVideo/
│ ├── README.md
│ ├── SDL.md
│ ├── image/
│ │ └── 视频解码播放流程.gliffy
│ ├── 常见封装格式.md
│ ├── 常见封装格式概览.md
│ ├── 常见流媒体协议.md
│ ├── 常见音视频编码.md
│ └── 通用视频解码播放流程.md
├── ChaosCrystal/
│ ├── ADB常用命令.md
│ ├── AndroidStudio常用快捷键(Mac).md
│ ├── Android中dip、dp、sp、pt和px.md
│ ├── HowToViewAPISourceOnline.md
│ ├── README.md
│ └── 录屏与GIF制作.md
├── Course/
│ ├── HowToUsePlantUMLInAS.md
│ ├── HowToUsePlantUMLInAS[Mac].md
│ ├── Markdown/
│ │ ├── README.md
│ │ ├── markdown-editor.md
│ │ ├── markdown-grammar.md
│ │ ├── markdown-html.md
│ │ ├── markdown-link.md
│ │ └── markdown-start.md
│ ├── README.md
│ ├── ReleaseLibraryByJitPack.md
│ └── jitpack-javadoc.md
├── CustomView/
│ ├── Advance/
│ │ ├── Code/
│ │ │ ├── CheckView.java
│ │ │ ├── CheckView.md
│ │ │ ├── SearchView.java
│ │ │ ├── SearchView.md
│ │ │ ├── SetPolyToPoly.java
│ │ │ └── SetPolyToPoly.md
│ │ ├── [01]CustomViewProcess.md
│ │ ├── [02]Canvas_BasicGraphics.md
│ │ ├── [03]Canvas_Convert.md
│ │ ├── [04]Canvas_PictureText.md
│ │ ├── [05]Path_Basic.md
│ │ ├── [06]Path_Bezier.md
│ │ ├── [07]Path_Over.md
│ │ ├── [08]Path_Play.md
│ │ ├── [09]Matrix_Basic.md
│ │ ├── [10]Matrix_Method.md
│ │ ├── [11]Matrix_3D_Camera.md
│ │ ├── [12]Dispatch-TouchEvent-Theory.md
│ │ ├── [15]Dispatch-TouchEvent-Source.md
│ │ ├── [16]MotionEvent.md
│ │ ├── [17]touch-matrix-region.md
│ │ ├── [18]multi-touch.md
│ │ ├── [19]gesture-detector.md
│ │ └── [99]DrawText.md
│ ├── Base/
│ │ ├── [01]CoordinateSystem.md
│ │ ├── [02]AngleAndRadian.md
│ │ └── [03]Color.md
│ ├── CustomViewRule.md
│ └── README.md
├── Lecture/
│ ├── README.md
│ └── gdg-developer-growth-guide.md
├── OpenGL/
│ └── README.md
├── QuickChart/
│ ├── Bezier.md
│ ├── Canvas.md
│ ├── Matrix.md
│ ├── Path.md
│ └── README.md
├── README.md
└── SourceAnalysis/
├── AtomicFile.md
├── CircularArray.md
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: AudioVideo/README.md
================================================
# Audio/Video 杂记
- [通用视频解码播放流程](通用视频解码播放流程.md)
================================================
FILE: AudioVideo/SDL.md
================================================
---
typora-copy-images-to: ./image
---
## SDL
### 简介
SDL(Simple DirectMedia Layer)库的作用就是封装了复杂的音视频底层交互工作,简化了音视频处理的难度。
**特点:** 开源、跨平台。
### 结构

它是对底层进行了封装,最终还是调用的平台底层接口与硬件进行交互。
### SDL 流程

### 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_Window | 代表一个“窗口”。 |
| SDL_Renderer | 代表一个“渲染器”。 |
| SDL_Texture | 代表一个“纹理”。 |
| SDL_Rect | 一个简单的矩形结构。 |
### SDL 事件和多线程
#### **SDL 多线程**
| 函数 | 简介 |
| ------------------ | -------------- |
| SDL_CreateThread() | 创建一个线程。 |
| SDL_Thread() | 线程的句柄。 |
#### **SDL 事件**
**函数:**
| 函数 | 简介 |
| --------------- | -------------- |
| SDL_WaitEvent() | 等待一个事件。 |
| SDL_PushEvent() | 发送一个事件。 |
**数据结构:**
| 结构 | 简介 |
| ----------- | -------------- |
| SDL_Event() | 代表一个事件。 |
================================================
FILE: AudioVideo/image/视频解码播放流程.gliffy
================================================
{"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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">音频驱动/设备</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">视频驱动/设备</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">音视频同步</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">视频原始数据</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">音频原始数据</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">视频解码</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">音频解码</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">视频压缩数据</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">音频压缩数据</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">解封装</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">封装格式数据</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">解协议</span></p>","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":"<p style=\"text-align:center;\"><span style=\"font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);\">网络数据</span></p>","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}}
================================================
FILE: AudioVideo/常见封装格式.md
================================================
## 常见封装格式
封装格式的主要作用是把视频码流和音频码流按照一定的格式存储在一个文件中。现如今流行的封装格式如下表所示:
主要封装格式一览
| 名称 | 推出机构 | 流媒体 | 支持的视频编码 | 支持的音频编码 | 目前使用领域 |
| ---- | ------------------ | ---- | ----------------------------- | ------------------------------------ | --------- |
| 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)
================================================
FILE: AudioVideo/常见封装格式概览.md
================================================
## 常见封装格式概览
| 名称 | 推出机构 | 流媒体 | 支持的视频编码 | 支持的音频编码 | 目前使用领域 |
| ---- | ------------------ | ---- | ----------------------------- | ------------------------------------ | --------- |
| 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)
================================================
FILE: AudioVideo/常见流媒体协议.md
================================================
## 常见流媒体协议
流媒体协议是服务器与客户端之间通信遵循的规定。当前网络上主要的流媒体协议如表所示。
| 名称 | 推出机构 | 传输层协议 | 客户端 | 目前使用领域 |
| -------- | -------------- | ------- | -------- | -------- |
| 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)
================================================
FILE: AudioVideo/常见音视频编码.md
================================================
## 常见音视频编码
### 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)
================================================
FILE: AudioVideo/通用视频解码播放流程.md
================================================
---
typora-copy-images-to: ./image
---
## 通用视频解码播放流程
**通用的网络视频播放流程:**
1. 从网络数据流中获得视频数据流。
2. 将视频数据流解析成压缩音频数据和压缩视频数据。
3. 分别对音频和视频解码获取原始(采样)数据。
4. 经过同步策略后,有序的将原始(采样)数据输出到指定设备播放。

### 参考资料:
[视音频编解码技术零基础学习方法](http://blog.csdn.net/leixiaohua1020/article/details/18893769)
================================================
FILE: ChaosCrystal/ADB常用命令.md
================================================
# ADB常用命令
命令 | 说明
--------------------------------|-----------------------------------------------------
adb start-server | 启动服务
adb kill-server | 关闭服务
adb devices | 显示当前连接的所有设备(如果服务没有开启会自动开启)
adb install xxx.apk | 将应用安装进设备中
adb uninstall <包名> | 卸载应用
adb -s <设备名> <命令> | 如果有多个设备,指定某一个设备进行操作
adb pull <手机文件> <电脑文件> | 将手机内文件导入到电脑上(文件名均为全称)
adb push <电脑文件> <手机文件> | 将电脑中文件推送到手机上(文件名均为全称)
adb shell | 进入手机命令行终端
┗ ls | 查看目录列表
┗ ls -l | 显示详细信息(权限 用户名 组名 时间 包名)
┗ ps | 当前正在运行的进程
┗ ping | 手机的网络连通性
================================================
FILE: ChaosCrystal/AndroidStudio常用快捷键(Mac).md
================================================
# AndroidStudio常用快捷键(Mac)
这里的快捷键是基于OSX个人定制版本的,具体请到 setting -> keymap 设置
快捷键 | 作用
----------------------------|-----------------------------------------------
Option + Enter | 自动修正
Command + N | 自动生成代码(Getter Setter)
Command + Alt + L | 格式化代码
Contral + Shift + F | 格式化代码(定制)
Command + Alt + T | 把选中的代码放在 try{} 、if{} 、 else{} 里
Command + / | 注释 //
Command + Shift + / | 注释 /* */
Command + Shift + Up/Down | 语句上下移动
Option + Shift + Up/Down | 内容上下移动
Option + Command + M | 将选中代码块封装成一个方法
Command + D | 复制当前一行(或选择区域),并粘贴到下面
Command + Z | 后退
Command + Shift + Z | 前进
Control + Alt + O | 优化导入的包
Ctrl(Command)+ - / + | 折叠/展开代码
Ctrl(Command)+Shift+ - / + | 折叠/展开全部代码
Ctrl(Command)+Shift+. | 折叠/展开当前花括号中的代码
Command + Y | 快速查看代码实现
Contral + H | 查看继承关系
Contral + Alt + H | 查看调用关系
Command + [ | 返回上一次查看的位置
Command + ] | 前进到返回之前查看的位置
Command + J | 自动生成代码
Command + E | 查看最近打开的文件
================================================
FILE: ChaosCrystal/Android中dip、dp、sp、pt和px.md
================================================
# Android中dip、dp、sp、pt和px
概念区别:
单位 | 含义
--- | ---
dip | device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA、HVGA和QVGA **推荐使用这个,不依赖像素**。
dp | 同上,和dip一样。
px | pixels(像素). 不同设备显示效果相同,一般我们HVGA代表320x480像素。
sp | scaled pixels(放大像素). 主要用于字体显示best for textsize。
pt | point,是一个标准的长度单位,1pt=1/72英寸,用于印刷业,非常简单易用。
in | (英寸):长度单位。
mm | (毫米):长度单位。
## 工具包
在 [ViewSupport](https://github.com/GcsSloop/ViewSupport) 支持包中可以找到该工具。
## 单位转换代码:
``` java
/**
* dp、sp 转换为 px 的工具类
*
* @author fxsky 2012.11.12
*
*/
public class DisplayUtil {
/**
* 将px值转换为dip或dp值,保证尺寸大小不变
*
* @param pxValue
* @param scale
* (DisplayMetrics类中属性density)
* @return
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* 将dip或dp值转换为px值,保证尺寸大小不变
*
* @param dipValue
* @param scale
* (DisplayMetrics类中属性density)
* @return
*/
public static int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
/**
* 将px值转换为sp值,保证文字大小不变
*
* @param pxValue
* @param fontScale
* (DisplayMetrics类中属性scaledDensity)
* @return
*/
public static int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
/**
* 将sp值转换为px值,保证文字大小不变
*
* @param spValue
* @param fontScale
* (DisplayMetrics类中属性scaledDensity)
* @return
*/
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
```
================================================
FILE: ChaosCrystal/HowToViewAPISourceOnline.md
================================================
# 在线查看Android API源码的方法
方法 | 推荐程度| 链接 | 备注
---|---|---|---
官方GitHub仓库 | ★☆☆☆☆ | [GitHub-Android](https://github.com/android) | 搜索不方便
通过GrepCode | ★★★☆☆ | [GrepCode-Android](http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android/) | 有搜索功能,可以在线查看到各个版本的源码,也可以下载
通过Chrome插件 | ★★★★★ | [插件:SDKSearch](https://chrome.google.com/webstore/detail/android-sdk-search/hgcbffeicehlpmgmnhnkjbjoldkfhoin) <br/> [Android API官网](http://developer.android.com/reference/packages.html) | 在官网查看API时会在下面多出一个View Source按钮(如下图)

================================================
FILE: ChaosCrystal/README.md
================================================

# 混沌水晶
混沌水晶本身并没有太大功效,但与其他物品合成之后可能产生质的变化。
* [Android中dip、dp、sp、pt和px](https://github.com/GcsSloop/AndroidNote/blob/master/ChaosCrystal/Android%E4%B8%ADdip%E3%80%81dp%E3%80%81sp%E3%80%81pt%E5%92%8Cpx.md)
* [AndroidStudio常用快捷键(Mac)](https://github.com/GcsSloop/AndroidNote/blob/master/ChaosCrystal/AndroidStudio%E5%B8%B8%E7%94%A8%E5%BF%AB%E6%8D%B7%E9%94%AE(Mac).md)
* [在线查看Android API源码](https://github.com/GcsSloop/AndroidNote/blob/master/ChaosCrystal/HowToViewAPISourceOnline.md)
* [录屏与GIF制作](https://github.com/GcsSloop/AndroidNote/blob/master/ChaosCrystal/%E5%BD%95%E5%B1%8F%E4%B8%8EGIF%E5%88%B6%E4%BD%9C.md)
* [ADB常用命令](https://github.com/GcsSloop/AndroidNote/blob/master/ChaosCrystal/ADB%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4.md)
================================================
FILE: ChaosCrystal/录屏与GIF制作.md
================================================
# 录屏与GIF制作
## 说明
在实际工作或学习过程中,很多地方都需要展示一些过程,或者展示动态的效果,使用视频显得比较大了,而且也不利于分享,故使用GIF就是一种很好的方法,体积小,易于分享展示。
## 制作GIF的方法
> 此处大部分参考自 廖祜秋(我们的秋百万大哥) 整理的内容: [Make GIF Snapshot for Android APP](http://www.liaohuqiu.net/posts/make-gif-for-android-app/)
制作GIF一般分为以下两种情况:
类别 | 说明 | 备注
--- | --- | ---
第一种 | 在电脑上可以看到效果 | 在模拟器中运行的app,视频等
第二种 | 在手机上可以看到效果 | 在真机中运行的app
### 第一种的制作方法
第一种方法比较容易直接录制屏幕然后转换为GIF就行了,有很多可视化的制作软件,这里推荐几种:
名称 | 说明 | 地址
--- | --- | ---
都叫兽GIF | 可视化GIF制作工具,支持录制后再次编辑,不同程度压缩,添加水印等功能,但默认右下角有一个都叫兽的水印,不适合强迫症患者 | [都叫兽GIF](http://www.reneelab.com.cn)<br/>[Windows直接下载](http://www.reneelab.com.cn/download-center/renee-gifer)
LICEcap | 可视化GIF制作工具,支持编辑录制区域大小,可改变录制帧率,功能没有上面多,但用起来很简单 | [LICEcap](http://www.cockos.com/licecap/)<br/>[Windows直接下载](http://www.cockos.com/licecap/licecap123-install.exe)
ezgif | 在线制作GIF的工具,这个的功能也很强大,支持将多张图片合成为GIF,将视频转换为GIF,剪切旋转等多种操作 | [ezgif](http://ezgif.com/)
## 第二种的制作方法
虽然手机上也有一些录屏GIF制作软件,但是大部分都很渣,很难达到我们想要的效果。
我个人一般是将操作过程录制下来,然后发送到电脑上,用电脑软件截取GIF,也就是转为第一种方法。
### 录制手机操作的方法
#### 通过AndroidStudio录屏工具:
这个用起来很方便,录制后能直接保存在电脑上。

1.选到该选项卡
2.上面一个按钮是截屏,下面一个按钮是录屏。
#### 通过ADB命令:
作为一个程序猿,虽然可视化操作很爽,但不会两条命令怎么能装逼呢。下面就教大家如何用命令录屏。
``` shell
$ adb shell // 进入shell
shell@ $ screenrecord --verbose /sdcard/demo.mp4 // 开始录制(保存到内存卡上)
(press Ctrl-C to stop) // 按Ctrl+C结束录制
shell@ $ exit // 结束录制
$ adb pull /sdcard/demo.mp4 // 将内存卡中的文件传到电脑上
```
**[更多ADB命令看这里](http://developer.android.com/tools/help/shell.html#screenrecord)**
#### 通过Python命令录制
虽说是Python命令,但是底层调用的依旧是adb,内部实现依赖了ffmpeg。
**详情请参考RoboGif的文档->[RoboGif](https://github.com/GcsSloop/RoboGif)**
如果你对Python还不了解,可以到这里学习一下Python的基础知识:**[PythonNote](https://github.com/GcsSloop/PythonNote)**
================================================
FILE: Course/HowToUsePlantUMLInAS.md
================================================
# 在AndroidStudio中使用PlantUML
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
## 前言
### 这是Windows平台设置教程,Mac平台戳这里:<a href="https://github.com/GcsSloop/AndroidNote/blob/master/Course/HowToUsePlantUMLInAS(Mac).md">在AndroidStudio中使用PlantUML(Mac)</a>
**Unified Modeling Language (UML)又称统一建模语言或标准建模语言,用来描述 类(对象的)、对象、关联、职责、行为、接口、用例、包、顺序、协作,以及状态。是用来帮助自己理清众多类之间复杂关系的不二利器,也能帮助别人快速理解你的设计思路。**
那么,我们怎么在AndroidStudio中创建自己的UML类图呢?接下来我就教大家如何用正确的姿势创建UML类图。
## 一.用正确的姿势安装panltUML插件
### 1.File->Settings->Plugins->Browse repositories

### 2.在搜索框输入plantUML

### 3.导入插件
#### (ps:由于我已经安装过了,所以没有Install plugin 按钮,未安装的都有这样一个按钮,如下,点击安装即可。)

#### 如果以上步骤正确的完成,重启AndroidStudio 右键->new 的时候你会发现多了这么一堆东西,如果出现了这些说明plantUML已经正确的安装了。

#### 当然了,所有事情都不会是一帆风顺的,当你迫不及待的想创建一个文件试试的时候你会发现下面的情况。

#### 想必此时你的内心一定和我当时一样,一万头草泥马奔腾而过,这都是什么东西!!!
#### 一切事情都是有原因的,而这个因为你还缺少一个必要的东西,就是大名鼎鼎的贝尔实验室开发的一个工具包:Graphviz。
## 二,用正确的姿势安装Graphviz
### 1.下载Graphviz
###[【下载地址戳这里】](http://www.graphviz.org/Download_windows.php)

### 2.安装
#### 安装过程我就不详细讲解了,点击next后要记住安装的目录。之后下一步,直到完成就行。

## 三.用正确的姿势设置plantUML
### 1.点击右上角的设置按钮或进入File->Settings->Other Settings ->PlantUML
### 2.将文件路径填写为刚刚Graphviz的目录下bin目录中dot.exe文件。
#### (我的为:D:/Program/Graphviz/bin/dot.exe)

### 3.点击OK 刷新一下界面就能看到这个了。

#### 讲到这里,就已经安装完成了,可以愉快的用代码来书写UML图了。
#### 什么?你说你还不会书写的语法?没关系,其实我也不会,不过我有一个好的教程推荐给你,相信你看完就明白啦。
## 四.用正确的姿势学习使用UML
### 1.[【PlantUML快速指南戳这里】](http://archive.3zso.com/archives/plantuml-quickstart.html)
### 2.注意,这个教程中的语法和AndroidStudio中基本一致,区别就是开始和结束标志不同。
####好了,到这里该教程正式结束,祝各位小伙伴能愉快的使用plantUML玩耍。
## About Me
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width=300 height=100 /> </a>
================================================
FILE: Course/HowToUsePlantUMLInAS[Mac].md
================================================
# 在AndroidStudio中使用PlantUML(Mac)
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
## 前言
### 这是Mac平台设置教程,Windows平台戳这里:[在AndroidStudio中使用PlantUML(Windows)](https://github.com/GcsSloop/AndroidNote/blob/master/Course/HowToUsePlantUMLInAS.md)
**Unified Modeling Language (UML)又称统一建模语言或标准建模语言,用来描述 类(对象的)、对象、关联、职责、行为、接口、用例、包、顺序、协作,以及状态。是用来帮助自己理清众多类之间复杂关系的不二利器,也能帮助别人快速理解你的设计思路。**
那么,我们怎么在AndroidStudio中创建自己的UML类图呢?接下来我就教大家如何用正确的姿势创建UML类图。
## 一.用正确的姿势安装panltUML插件
### 1.File->Settings->Plugins->Browse repositories

### 2.在搜索框输入plantUML

### 3.导入插件
#### (ps:由于我已经安装过了,所以没有Install plugin 按钮,未安装的都有这样一个按钮,如下,点击安装即可。)

#### 如果以上步骤正确的完成,重启AndroidStudio 右键->new 的时候你会发现多了这么一堆东西,如果出现了这些说明plantUML已经正确的安装了。

#### 当然了,所有事情都不会是一帆风顺的,当你迫不及待的想创建一个文件试试的时候你会发现下面的情况。

#### 想必此时你的内心一定和我当时一样,一万头草泥马奔腾而过,这都是什么东西!!!
#### 一切事情都是有原因的,而这个因为你还缺少一个必要的东西,就是大名鼎鼎的贝尔实验室开发的一个工具包:Graphviz。
## 二,用正确的姿势安装Graphviz
此处建议使用Homebrew自动下载安装,如果你还没有用过Homebrew来帮助你管理软件,参考这里: [Homebrew&HomebrewCask](https://github.com/GcsSloop/MacDeveloper/blob/master/Tools/Homebrew.md)
安装好homebrew后直接输入如下命令,按下回车后等待片刻即可安装成功:
```
brew install graphviz
```
安装完成后,Homebrew会告诉你安装位置,请记好这个位置,你也可以用info命名查看安装位置:
```
brew info graphviz
```
我的安装位置在 `/usr/local/Cellar/graphviz/2.38.0`

## 三.用正确的姿势设置plantUML
### 1.点击右上角的设置按钮或进入File->Settings->Other Settings ->PlantUML或者在预览页面点击右上角的设置图标,如下:
### 2.将文件路径填写为刚刚Graphviz的目录下bin目录中dot文件。
#### (我的为:/usr/local/Cellar/graphviz/2.38.0/bin/dot)

### 3.点击OK 刷新一下界面就能看到结果了。
#### 讲到这里,就已经安装完成了,可以愉快的用代码来书写UML图了。
#### 什么?你说你还不会书写的语法?没关系,其实我也不会,不过我有一个好的教程推荐给你,相信你看完就明白啦。
## 四.用正确的姿势学习使用UML
### 1.[【PlantUML快速指南戳这里】](http://archive.3zso.com/archives/plantuml-quickstart.html)
### 2.注意,这个教程中的语法和AndroidStudio中基本一致,区别就是开始和结束标志不同。
####好了,到这里该教程正式结束,祝各位小伙伴能愉快的使用plantUML玩耍。
## About Me
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width=300 height=100 /> </a>
================================================
FILE: Course/Markdown/README.md
================================================
# Markdown 实用技巧
* [Markdown 快速入门](https://github.com/GcsSloop/AndroidNote/blob/master/Course/Markdown/markdown-start.md)
* [Markdown 基础语法](https://github.com/GcsSloop/AndroidNote/blob/master/Course/Markdown/markdown-grammar.md)
* [Markdown 链接图片](https://github.com/GcsSloop/AndroidNote/blob/master/Course/Markdown/markdown-link.md)
* [Markdown 编辑器](https://github.com/GcsSloop/AndroidNote/blob/master/Course/Markdown/markdown-editor.md)
================================================
FILE: Course/Markdown/markdown-editor.md
================================================
# Markdown实用技巧-编辑器(Typora)
本次的安利对象是一个 Markdown 编辑器,是会长见过的最简单,最优雅的编辑器,先来看一下它的界面吧:

它的界面非常简单,有多种主题可选,更重要的是**它的预览界面和编辑界面是一体的,而不像其他编辑器那样是左右分开的。**

从上面的示例中可以看出,其输入模式支持多种,不论是手动输入语法还是使用快捷键,都非常的流畅,实时看到效果变化,除此之外 Typora 还有很多优点。
## Typora 的优点
* 预览和编辑界面一体。
* 强大的快捷键。
* 兼容常见扩展语法。
* 兼容 HTML (新版进行了完善)。
* 支持 YAML 格式。
* 支持公式编辑(LaTeX公式)。
* 表格和代码块编辑起来非常方便。
* 支持本地图片相对链接。
* 支持将本地图片拖进编辑页面。
* 支持多种导出格式(PDF,HTML,Word,LaTeX 等)。
* 有多种主题可选。
虽然上面的内容在其他编辑器上也可以见到,但做到这么完善的并不多,想要尝试的小伙伴赶紧来试试吧,相信你也会爱上它的。
#### [点击这里进入 Typora 官网](http://www.typora.io/)
既然 Typora 这么好,为什么不在一开始就推荐呢,这是因为在 Markdown 系列文章第一篇发布的时候,Typora 还有一些小瑕疵(HTML语法兼容部分)让会长不满意,所以只是放在了推荐首位,在最近更新了新版本之后,这一部分已经修复完善了,基本算是完美了,所以会长我才特地写一篇文章来推荐。
**Typora 支持在 WIndows,Linux,和 Mac 上使用,如果你正在使用的是 Mac 的话,那么恭喜你,除了上述的优点,你可以用到一些 Mac 系统才有的福利。**
## 版本回溯功能
这是 Mac 系统提供的一个小功能,使用 Mac 的时候你可能会注意到一个小细节,**使用系统文本编辑器进行编辑文本的时候,从来不会提示你保存文本,即便编辑后不点保存立即退出里面的内容也不会丢失。**你可能会觉得这不就是一个自动保存功能么,有啥稀奇的?
那么你是否遇到过这一种情况,编辑了半天的内容觉得不太好想放弃保存,继续使用之前的内容,这在Windows上很容易实现,只要编辑的时候没有手动保存,直接点击关闭不保存,再打开的时候就是编辑之前的内容。
然而在 Mac 上自动保存了,如果关闭后再打开,点 `Command + Z` 也没用了,这不就尴尬了,难道说遇到这种情况只能关闭前狂点 `Command + Z` 回退?
作为一个有情怀的操作系统,自然不会意识不到这个问题,它提供了更强大的方法,那就是版本回溯,相当于一个自动的 Git 系统,这就厉害了。
Trpora 也用到了这一特性,点击 `File > Revert To > Browse All Versions` 就能看到了。以我之前发布的一篇文章为例:

**上图中左边为当前版本,右边为选中版本,最右侧是时间线,使用这一功能可以将文章回溯到任意时间点,妈妈再也不怕我的文章内容丢失啦,也成功避免了以下这种情况 ▼**

由于这一功能是 Mac 系统提供的,所以不仅对纯文本有效,对 iWork 也是有效的,iWork 上面也有版本回溯功能,像 key,numbers,pages 都支持版本回溯,中文版点 `文件 > 复原 > 浏览所有版本` 即可看到过去保存的数据。
当然了,这只是福利之一。
## 更便捷的图片插入和上传方式
众所周知,Markdown 插入图片是一个大问题,尤其是在本地编辑的时候,但是这些在遇到 Typora 后就变的越来越简单了。
**对于本地图片,我们可以直接拖进来,再也不用记复杂的语法和查图片地址了,没错,只要拖进来就行,Typora 会自动识别图片并进行插入。**
不过还有一个问题,我们的很多文章都是要发布的,直接拖进来这种方式在本地看起来没问题,但上传到网站上是读取不到本地图片的,通常解决方案就是先上传到图床上,之后再将链接插入到 Markdown 中,这样发布就没问题了。
但是,当一篇文章需要插入很多图片的时候,一次次的上传,一次次的复制链接,一次次的粘贴,也是很麻烦的,虽然有一些脚本可以通过快捷键进行上传,但终归还是多了一些步骤,很不方便,而且对新手很不友好,更何况很多新手根本不知道图床所谓何物。
这时候就要祭出 Mac 上另一个神器了: iPic [(点击此处进行下载)](https://itunes.apple.com/cn/app/ipic-tu-chuang-shen-qi-zhong/id1101244278?mt=12),它是一个专业图床管理工具,貌似出来还没多久,目前免费版只能上传到微博图床,专业版支持大部分图床,专业版一年 30RMB,如果经常使用的话,倒也不算贵。
**话说不还是要用图床么?**
**但 Typora 的方便之处就在于可以和 iPic 无缝结合,纯傻瓜式一键操作,能一次性的将文章中所有本地图片上传到图床上。**
**首先安装 iPic,运行 iPic,之后点击 `Edit > Image Tools > Upload Local Images via iPic` 就像下面这样就行了。**
QQ20161202-8](http://ww1.sinaimg.cn/large/006y8lVajw1facuf1yjjkj30fo0dmq4v.jpg)
**如果你觉得这样还是很麻烦的话,还有另外的选项,可以选择插入本地图片的时候自动上传到服务器,当然了,为了保护个人隐私,这一个选项默认是关闭的,首先你要找到首选项,点击 `Typora > Preferences` 或者快捷键 `Command + ,` 都行,之后打开以下两个选项,其中一个选项是后面用到的。**

**打开之后继续选择 `Edit > Image Tools > When Insert Local Images > Upload Image via iPic` 它的意思是当插入本地图片时自动通过 iPic 上传到服务器上。**

这种方案虽然方便,但是会长并不推荐大家这么做,主要还是不安全,误操作的话可能会将一些包含个人隐私的照片上传上去。
会长推荐另一种方案,就是 `Copy Image File to Folder`,操作步骤和上面类似,它的意思是插入本地图片时自动归档到某一个文件夹,在文章编辑完成后在点击上传按钮统一上传,这样有以下几点好处:
* 安全,不会因为误操作将涉及隐私照片传到服务器上。
* 相当于自动建立了一个本地图片档案,方便备份保存。
* 如果图床上的图片丢失,可以从快速从本地备份中找到并恢复。
关于 Typora 的推荐内容暂时就到这里结束啦,更多关于 Typora 的多详情请参阅 [Support](http://support.typora.io/)。关于 Markdown 的更多内容请参见之前的文章:
[Markdown实用技巧-快速入门](https://www.gcssloop.com/markdown/markdown-start)
[Markdown实用技巧-基础语法](https://www.gcssloop.com/markdown/markdown-grammar)
[Markdown实用技巧-链接和图片](https://www.gcssloop.com/markdown/markdown-links)
## 结语
最后稍微夹带一点个人建议,个人觉得 Typora 除了界面简洁之外,最大的优点就是快捷键方便了,然而,快捷键那么多,要不要专一去记呢?
个人的建议是不要专一去记忆这些快捷键,用到的时候打开菜单看一眼快捷键是什么,之后用快捷键按出来,这样用过几次之后自然就会记住常用的快捷键,而且会记得很牢固,不然每个应用都有快捷键,一个一个的记多麻烦。
本文中涉及到的两个软件下载地址:
[**Typora**](http://www.typora.io/)
[**iPic**](https://itunes.apple.com/cn/app/id1101244278?ls=1&mt=12)
**最后留一个课后作业,关注会长的 [新浪微博](http://weibo.com/GcsSloop) 。**
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300" style="display:inline;" /></a>
================================================
FILE: Course/Markdown/markdown-grammar.md
================================================
# Markdown 基础语法
为保证语法兼容性,本文只介绍基础语法,扩展语法等其它内容,会在后续的文章中单独介绍。
**注意:所有的标记符号均使用英文,中文无效。**
****
## 标题
Markdown 支持多种标题格式。
利用 `=` (等号)和 `-`(减号)可以定义一级标题和二级标题,(任何数量的 `=` 和 `-` 都有效果) :
<table>
<tr>
<th>Markdown</th>
<th>预览</th>
</tr>
<tr>
<td>一级标题<br/>====</td>
<td><h1>一级标题</h1></td>
</tr>
<tr>
<td>二级标题<br/>----</td>
<td><h2>二级标题</h2></td>
</tr>
</table>
通过在行首插入 1 到 6 个 # ,来定义对应的 1 到 6 阶 标题,个人推荐使用这种,例如:
<table>
<tr>
<th>Markdown</th>
<th>预览</th>
</tr>
<tr>
<td># 一级标题</td>
<td><h1>一级标题</h1></td>
</tr>
<tr>
<td>## 二级标题</td>
<td><h2>二级标题</h2></td>
</tr>
<tr>
<td>### 三级标题</td>
<td><h3>三级标题</h3></td>
</tr>
<tr>
<td>#### 四级标题</td>
<td><h4>四级标题</h4></td>
</tr>
<tr>
<td>##### 五级标题</td>
<td><h5>五级标题</h5></td>
</tr>
<tr>
<td>###### 六级标题</td>
<td><h6>六级标题</h6></td>
</tr>
</table>
****
## 段落和换行
在 Markdown 中段落由一行或者多行文本组成,相邻的两行文字会被视为同一段落,如果存在空行则被视为不同段落( Markdown 对空行的定义是看起来是空行就是空行,即使空行中存在 `空格` `TAB` `回车` 等不可见字符,同样会被视为空行)。
Markdown支持段内换行,如果你想进行段落内换行可以在上一行结尾插入两个以上的空格后再回车。
<table>
<tr>
<th>Markdown</th>
<th>预览</th>
</tr>
<tr>
<td>
第一行<br/>
相邻被视为同一段落。
</td>
<td>
<p>
第一行 相邻被视为同一段落。
</p>
</td>
</tr>
<tr>
<td>
第一行[空格][空格]<br/>
上一行结尾存在两个空格,段内换行
</td>
<td>
<p>
第一行<br/>
上一行结尾存在两个空格,段内换行。
</p>
</td>
</tr>
<tr>
<td>
第一行<br/>
<br/>
两行之间存在空行,视为不同段落。
</td>
<td>
<p>
第一行
</p>
<p>
两行之间存在空行,视为不同段落。
</p>
</td>
</tr>
</table>
******
## 强调
删除线的 `~` 符号一般位于键盘左上角位置。
<table>
<tr>
<th>Markdown</th>
<th>预览</th>
</tr>
<tr>
<td>*倾斜*</td>
<td>
<i>倾斜</i>
</td>
</tr>
<tr>
<td>**粗体**</td>
<td><b>粗体</b></td>
</tr>
<tr>
<td>~~删除线~~</td>
<td><s>删除线</s></td>
</tr>
<tr>
<td>
> 引用
</td>
<td>
<blockquote>引用</blockquote>
</td>
</tr>
</table>
******
## 链接和图片
为了规避某些平台的防盗链机制,图片推荐使用图床,否则在不同平台上发布需要重新上传很麻烦的,图床最好选大平台的图床,一时半会不会倒闭的那种,个人目前主要用的是微博图床 [Chrome插件-微博图床](https://chrome.google.com/webstore/detail/%E6%96%B0%E6%B5%AA%E5%BE%AE%E5%8D%9A%E5%9B%BE%E5%BA%8A/fdfdnfpdplfbbnemmmoklbfjbhecpnhf?hl=zh-CN) 以及 [Alfred脚本-微博图床](https://imciel.com/2016/07/17/weibo-picture-upload-alfred-workflow/)
<table style="word-break:break-all;">
<tr>
<th width="200">Markdown</th>
<th width="200">预览</th>
</tr>
<tr>
<td>[GcsSloop](http://www.gcssloop.com)</td>
<td><a href="http://www.gcssloop.com" target="_blank">GcsSloop</a></td>
</tr>
<tr class="alternate">
<td></td>
<td><img src="http://www.gcssloop.com/assets/siteinfo/friends/gcssloop.jpg" alt="GcsSloop Blog" /></td>
</tr>
</table>
******
## 列表
无序列表前面可以用 `*` `+` `-` 等,结果是相同的。
有序列表的数字即便不按照顺序排列,结果仍是有序的。
<table>
<tr>
<th>Markdown</th>
<th>预览</th>
</tr>
<tr>
<td>
* 项目<br />
* 项目<br />
* 项目<br />
* 子项目<br />
* 子项目<br />
* 项目
</td>
<td>
<ul>
<li>项目</li>
<li>项目</li>
<li>项目
<ul>
<li>子项目</li>
<li>子项目</li>
</ul>
</li>
<li>项目</li>
</ul>
</td>
</tr>
<tr class="alternate">
<td>
1. 项目<br />
2. 项目<br />
3. 项目<br />
1. 子项目</span><br />
2. 子项目<br />
4. 项目
</td>
<td>
<ol>
<li>项目</li>
<li>项目</li>
<li>项目
<ol>
<li>子项目</li>
<li>子项目</li>
</ol>
</li>
<li>项目</li>
</ol>
</td>
</tr>
</table>
****
## 下划线和特殊符号
由于 Markdown 使用一些特殊符号进行标记,当我们想要在文档中使用这些特殊符号并防止被 Markdown 转换的时候,可以使用 `\` (转义符) 将这些特殊符号进行转义。
<table>
<tr>
<th>Markdown</th>
<th>预览</th>
</tr>
<tr>
<td>
在一行中用三个以上的星号、减号、下划线来建立一个分隔线<br />
---
</td>
<td>
<hr />
</td>
</tr>
<tr class="alternate">
<td>可以利用反斜杠(转义字符)来插入一些在语法中有特殊意义的符号<br />
\*Hi\*
</td>
<td>*Hi*</td>
</tr>
</table>
****
## 代码
### 1.行内代码
行内代码可以使用反引号来标记(反引号一般位于键盘左上角,要用英文):
```
一句话 `行内代码` 一句话。
```
**预览:**
一句话 `行内代码` 一句话。
### 2.多行代码
多行代码使用 3 个反引号来标记(反引号一般位于键盘左上角,要用英文) ,在第一个 ````` 后面可以跟语言类型,没有语言类型可以省略不写:
```
``` java
// 我是注释
int a = 5;
```
```
**预览:**
``` java
// 我是注释
int a = 5;
```
****
## 表格
**扩展的 Markdown 支持手写表格**,格式也非常简单,第二行分割线部分可以使用 `:` 来控制内容状态。
**注意,Markdown 标准(原生)语法中没有表格支持,但现在多数平台已经支持了该语法,如 GitHub,CSDN,简书 等均支持,所以写在这里:**
**Markdown:**
```
| 默认 | 靠右 | 居中 | 靠左 |
| ---- | ---: | :--: | :--- |
| 内容 | 内容 | 内容 | 内容 |
| 内容 | 内容 | 条目 | 内容 |
```
**预览:**
| 默认 | 靠右 | 居中 | 靠左 |
| ---- | ---: | :--: | :--- |
| 内容 | 内容 | 内容 | 内容 |
| 内容 | 内容 | 条目 | 内容 |
## 参考资料
[Markdown-语法说明](http://www.markdown.cn/)
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300" style="display:inline;" /></a>
================================================
FILE: Course/Markdown/markdown-html.md
================================================
# Markdown 网页格式兼容
Markdown 作为一种标记型语言,在大多数情况下都是需要转换为 HTML 格式的,所以 Markdown 理论上是兼容 HTML 语法的,在 Markdown 所提供的标记无法满足我们需要的时候,可以尝试使用 HTML 相关语法来实现。
================================================
FILE: Course/Markdown/markdown-link.md
================================================
# Markdown实用技巧-链接和图片
博客地址: http://www.gcssloop.com/markdown/markdown-links
Sloop 喝过半杯咖啡,涨红的脸色渐渐复了原,旁人便又问道,“ Sloop,你当真会写文章么?” Sloop 看着问他的人,显出不屑置辩的神气。他们便接着说道,“你怎的连半个赞也捞不到呢?” Sloop 立刻显出颓唐不安模样,脸上笼上了一层灰色,嘴里说些话;这回可是全是技术名词之类,一些不懂了。在这时候,众人也都哄笑起来:办公室内外充满了快活的空气。
在这些时候,我可以附和着笑,老板是决不责备的。而且老板见了 Sloop,也每每这样问他,引人发笑。Sloop 自己知道不能和他们谈天,便只好向实习生说话。有一回对我说道,“你会用 Markdown 么?” 我略略点一点头。他说,“会用 Markdown,……我便考你一考。Markdown 的链接,你是怎样写的?” 我想,码农一样的人,也配考我么?便回过脸去,不再理会。Sloop 等了许久,很恳切的说道,“不能写罢?……我教给你,记着!这些写法应该记着。将来做程序员的时候,写文档要用。”我暗想我和程序员的等级还很远呢,而且我们这里的程序员也从不写文档;又好笑,又不耐烦,懒懒的答他道,“谁要你教,不是一对方括号后面跟一对圆括号么?” Sloop 显出极高兴的样子,将两个指头的长指甲敲着电脑,点头说,“对呀对呀!……链接有4样基本写法,你知道么?” 我愈不耐烦了,努着嘴走远。Sloop 刚想打开编辑器,给我演示,见我毫不热心,便又叹一口气,显出极惋惜的样子。
*****
## 前言
**本文是适用于对 Markdown 有一定了解的魔法师,以帮助他们挖掘更多关于 Markdown 的可能性,例如:链接的不同类型以及使用方式,如何在新标签页打开链接,如何控制图片大小等,对 Markdown 还不了解的魔法师请参考 [Markdown快速入门][markdown-start] 和 [Markdown基础语法][markdown-grammar] 。**
**注意:以下的部分语法不属于标准语法,存在不兼容的问题,不能保证所有平台都能够使用。对于非标准语法(拓展语法)我会进行标注说明。**
*****
## 行内式链接:
```markdown
博客地址: [GcsSloop](http://www.gcssloop.com)
博客地址: [GcsSloop](http://www.gcssloop.com "GcsSloop的博客")
```
博客地址: [GcsSloop](http://www.gcssloop.com)
博客地址: [GcsSloop](http://www.gcssloop.com "GcsSloop的博客")
*****
## 参考式链接:
```markdown
[GcsSloop的博客][gcssloop]
[gcssloop]: http://www.gcssloop.com
// 或者
[gcssloop]: http://www.gcssloop.com "点击访问GcsSloop的博客"
```
[GcsSloop的博客][gcssloop]
**为什么要使用参考式呢?**
在写文章的时候很可能会在文章不同的地方引用同一篇文章,使用参考式可以少写一点字符。
更重要的是,参考文章的链接可能会改变,如果将参考链接统一写在文末的话,改起来会更容易。
*****
## 自动链接:
```markdown
<http://www.gcssloop.com>
```
<http://www.gcssloop.com>
*****
## 相对链接:
**如果你的内容是发布在 GitHub 或者自己的个人网站上,那么相对链接是一个很好用的东西**,例如引用本站的一张图片可以这样写:
```markdown

```

**相对链接优点:**
* 线下预览和线上效果相同,不受网络影响。
* 避免了分别上传图片导致的麻烦。
* GitHub复制仓库更方便,改仓库名字不会导致资源链接失效。
**相对链接缺点:**
* 不适用于需要在多平台发布的文章。
* 某些本地编辑器不支持读取相对链接。
*****
## 图片链接:
图片链接其实就是把图片和链接嵌套在一起:
> 顺便为 DiyCode 打一个小广告,欢迎更多小伙伴的加入。
```markdown
[](http://www.diycode.cc/wiki/encouragement)
```
[](http://www.diycode.cc/wiki/encouragement){:target="_blank"}
*****
IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
**注意:从这里开始往下的部分,属于拓展语法,可能存在某些平台(编辑器)无法识别的问题,请亲自测试后再使用。**
IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
## 在新标签页打开:
Markdown 的默认链接方式都是在当前标签页打开的,这就会导致打开链接之后原始页面被覆盖掉,如果想要让链接在新标签页打开可以在后面添加上 `{:target="_blank"}` 或者使用 HTML 语法。
```
[GcsSloop](http://www.gcssloop.com){:target="_blank"}
[GcsSloop的博客][gcssloop]{:target="_blank"}
<http://www.gcssloop.com>{:target="_blank"}
<a href="http://www.gcssloop.com/info/about" target="_blank">关于GcsSloop</a>
```
[GcsSloop](http://www.gcssloop.com){:target="_blank"} <br/>
[GcsSloop的博客][gcssloop]{:target="_blank"} <br/>
<http://www.gcssloop.com>{:target="_blank"} <br/>
<a href="http://www.gcssloop.com/info/about" target="_blank">关于GcsSloop</a>
注意: 部分平台可能不识别 `{:target="_blank"}` 标签,例如你正在看的这个 GitHub 就不识别。。
*****
## 注释(脚注):
注释一般用于解释一些专业名词或者难以理解的内容,由于注释的解释部分一般放在文末,所以又称为脚注:
```
GcsSloop[^1]是一个超级魔法师[^2] 。
[^1]: GcsSloop:非著名程序员。
[^2]: 魔法师:会魔法的人类
```
GcsSloop[^1]是一个超级魔法师[^2] 。
注意:脚注不论在何处定义,最终都是显示在文末。部分平台不识别该语法,GitHub 依旧不识别。
*****
## 控制图片大小
使用 `{:width="300" height="100"}` 或者 HTML 格式可以控制图片显示大小,图片有宽度(width)和高度(height)两个属性,如果只指定了一个,另一个会按照比例缩放。
```
{:width="300"}
<img src="http://ww1.sinaimg.cn/large/005Xtdi2jw1f9k7b8a6vmj312w0rg143.jpg" width="300"/>
```
{:width="300"}
右键,新标签页打开图片,你会发现原图其实挺大的。
注意: 部分平台可能不识别 `{:width="300" height="100"}` 标签,你正在看的这个 GitHub 依旧不识别。
*****
## 总结:
Markdown 存在很多的变种,对其语法进行了不同程度的拓展,使其更加的强大,但是使用拓展语法之前请三思。个人建议如下:
* 如果文章(文档)只在单一平台发布,使用任何该平台支持的拓展语法都没问题。
* 如果文章(文档)需要在多个平台发布,尽量使用标准语法,使用拓展语法之前请注意测试平台兼容性。
* 图片尽量使用图床管理,而且要进行本地备份。
*****
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300" style="display:inline;" /></a>
## 参考链接:
[Markdown-基础语法](http://www.markdown.cn/)
[Markdown语法:在新窗口新标签页中打开](http://yinping4256.github.io/cn/Markdown%E8%AF%AD%E6%B3%95%E5%9C%A8%E6%96%B0%E7%AA%97%E5%8F%A3%E6%96%B0%E6%A0%87%E7%AD%BE%E9%A1%B5%E4%B8%AD%E6%89%93%E5%BC%80/)
### 注释:
[^1]: GcsSloop:非著名程序员。
[^2]: 魔法师:会魔法的人类
[markdown-start]: http://www.gcssloop.com/markdown/markdown-star "Markdown实用技巧-快速入门"
[markdown-grammar]: http://www.gcssloop.com/markdown/markdown-grammar "Markdown实用技巧-基础语法"
[gcssloop]: http://www.gcssloop.com "点击访问GcsSloop的博客"
================================================
FILE: Course/Markdown/markdown-start.md
================================================
# Markdown 快速入门
自从接触了 Markdown 之后,就一直用 Markdown 作为自己的主要书写工具,不论是平时做一些简单的纪录,还是用来写博客,写文档都是非常方便。你现在看到的这篇文章就是用 Markdown 进行书写的。
> 我最早因为 GitHub 而了解到 Markdown,当是支持 Markdown 的平台并不多,现在发现很多平台都已经开始支持 Markdown了,不论是老牌的 CSDN 还是比较新的 简书、掘金、DiyCode 等都支持使用Markdown进行写作,借此趋势,赶紧向还不了解 Markdown 的魔法师强势安利一波。
>
> **如果你已经开始使用 Markdown了,那么本文作用对你可以能并不大,请看后续文章。**
****
## 什么是 Markdown ?
**Markdown 是一种轻量级标记语言,创始人为約翰·格魯伯(John Gruber)。 它允许人们“使用易读易写的纯文本格式编写文档,然后转换成有效的XHTML(或者HTML)文档”。**
相比于 HTML (~~How To Make Love~~ 大雾), **Markdown 更加精简,更加注重内容,其主要宗旨是「易读易写」** 一般 Markdown 最终都是要转换为 HTML 的,可用于书写博客或者网页,但借助某些工具,可以讲 Markdown 转换为 pdf,word,Latex 等其他常见的文件格式。
****
## 为什么选择 Markdown ?
**选择 Markdown 但理由只有一个:方便,节省时间!**
至于为什么这样说,请看下面内容:
* **语法简洁**,没有任何编程基础的人十几分钟语言即可入门。
* **注重内容**,专注于内容编写,不再因为格式拍版而苦恼 (word格式刷工具哭晕在厕所)。
* **易阅读性**,即便是没有经过转换的 Markdown 文件,大部分文字内容仍可阅读。
* **易编辑性**,任何文本编辑器都能编辑 Markdown 文件。
* **跨平台性**,任何平台均能打开 Markdown 文件,由于是纯文本文件,不存在格式兼容的问题。
* **导出方便**,支持导出为 HTML,PDF,Word(.docx),LaTex 等常见格式(需要工具支持)。
在 Windows 上编写的文档,非常方便的就能在 Mac 上继续编辑,方便数据迁移,降低沟通成本。
****
## Markdown 存在的问题
前面吹嘘了 Markdown 的那么多优点,下面就说一下其中的不足:
* **图片问题**,很多人都觉得 Markdown 文件插入图片麻烦,还要自己上传找链接。
* **语法兼容**,基础语法是兼容的,但不同工具(平台)的扩展语法不兼容(由于没有统一标准)。
* **细节控制**,Markdown只提供最基础的格式,其显示样式主要由CSS控制,很难针对性的控制部分内容。
以上应该是 Markdown 最常见的一些麻烦,不过不必担心,**后续文章会教大家来如何解决这些问题,取其精华,去其糟粕,让 Markdown 运用得心应手**。
****
## Markdown 编辑器推荐
俗话说,工欲善其事,必先利其器,虽然 Markdown 用任何文本编辑器都能打开编辑,但仍需要专业工具进行转化,常见 Markdown 编辑器我基本上都尝试用过,在此简单推荐几种,大家找适合自己的就行。
**仅推荐本地编辑器,在线编辑器根据需要自己选择,很多平台都已经支持直接用 Markdown 进行编辑了。**
| 编辑器 | 支持平台 | 支持导出格式 |
| ---------------------------------------- | --------------------------- | ---------------------------------------- |
| [**Typora**](http://www.typora.io/) <br/> 正在开发, 界面简洁, 对 HTML 语法支持较弱, 支持导出文件类型较多。 | Mac、<br> Windows、<br> Linux | HTML、 <br>Word(.docx)、 <br>PDF、 <br>LaTex 等 |
| [**EME**](https://eme.moe/) <br/> 正在开发, 界面简洁,对 HTML 语法支持友好,支持导出文件类型较少。 | Mac、 <br>Windows、 <br>Linux | 仅 PDF |
| [**Mou**](http://25.io/mou/) <br/> 停止开发, 界面简洁, 对 HTML 语法支持友好, 但支持导出文件类型较少。 | Mac | HTML、 <br>PDF |
| [**Sublime Text**](http://www.sublimetext.com/3) <br/> 神兵利器,需要安装插件才能使用, 相对比较麻烦, 适合高级魔法师。 | Mac、 <br>Windows、 <br>Linux | 多种格式(插件) |
| [**Atom**](https://atom.io/) <br/> 神兵利器, 支持多种常见的编程语言, 如果仅仅是为了写 Markdown 不推荐安装。 | Mac、 <br>Windows、 <br>Linux | HTML、 <br>PDF(插件) |
****
## 快速入门
本文是为了帮助还不了解 Markdown 的魔法师有一个简单的认知,所以快速入门只说最基本的一些内容。
### 标题
通过在行首插入 1 到 6 个 `#` ,来定义对应的 1 到 6 阶 标题:
<table>
<tr>
<th>Markdown</th>
<th>预览</th>
</tr>
<tr>
<td># 一级标题</td>
<td><h1>一级标题</h1></td>
</tr>
<tr>
<td>## 二级标题</td>
<td><h2>二级标题</h2></td>
</tr>
<tr>
<td>### 三级标题</td>
<td><h3>三级标题</h3></td>
</tr>
</table>
### 分段
在 Markdown 中段落由一行或者多行文本组成,相邻的两行文字会被视为同一段落,如果存在空行则被视为不同段落( Markdown 对空行的定义是看起来是空行就是空行,即使空行中存在 `空格` `TAB` `回车` 等不可见字符,同样会被视为空行)。
<table>
<tr>
<th>Markdown</th>
<th>预览</th>
</tr>
<tr>
<td>
第一行<br/>
相邻被视为同一段落。
</td>
<td>
<p>
第一行 相邻被视为同一段落。
</p>
</td>
</tr>
<tr>
<td>
第一行<br/>
<br/>
两行之间存在空行,视为不同段落。
</td>
<td>
<p>
第一行
</p>
<p>
两行之间存在空行,视为不同段落。
</p>
</td>
</tr>
</table>
### 链接和图片
<table style="word-break:break-all;">
<tr>
<th width="200">Markdown</th>
<th width="200">预览</th>
</tr>
<tr>
<td>[GcsSloop](http://www.gcssloop.com)</td>
<td><a href="http://www.gcssloop.com" target="_blank">GcsSloop</a></td>
</tr>
<tr class="alternate">
<td></td>
<td><img src="http://www.gcssloop.com/assets/siteinfo/friends/gcssloop.jpg" alt="GcsSloop Blog" /></td>
</tr>
</table>
知道了上面这些内容就已经算是入门了,可以用 Markdown 进行快乐的写作,如果想了解更多语法相关内容,请看下一篇 [Markdown实用技巧-基础语法][markdown-grammar]
## 参考资料
[Markdown-基础语法](http://www.markdown.cn/)
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300" style="display:inline;" /></a>
[markdown-grammar]: /markdown/markdown-grammar "Markdown实用技巧-语法"
================================================
FILE: Course/README.md
================================================
# 教程类
<p align="center">
<a href="https://github.com/GcsSloop/AndroidNote/blob/master/Course/HowToUsePlantUMLInAS.md" target="_blank"><img src="http://ww2.sinaimg.cn/large/005Xtdi2gw1f6h5jfxzvaj30rs0dwgot.jpg" width="285" /></a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/master/Course/HowToUsePlantUMLInAS%5BMac%5D.md" target="_blank"><img src="http://ww3.sinaimg.cn/large/005Xtdi2gw1f6h5k3m8wrj30rs0dwq66.jpg" width="285" /></a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/master/Course/ReleaseLibraryByJitPack.md" target="_blank"><img src="http://ww2.sinaimg.cn/large/005Xtdi2gw1f6h5lbuiraj30rs0dwtbm.jpg" width="285" /></a>
</p>
================================================
FILE: Course/ReleaseLibraryByJitPack.md
================================================
# 优雅的发布Android开源库(论JitPack的优越性)
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
### [JitPack地址](https://jitpack.io)
## 前言
自从谷歌宣布不支持Eclipse之后,大批Android程序猿情愿或者不情愿的迁移到了AndroidStudio,从此过上了使用Gradle构建程序的"优越"生活。
> 关于Gradle的坑,就不吐槽了,我怕一会儿控制不住情绪。今天我们就谈一下Gradle的优越性。
说到Gradle的优越性,其中有一点比较明显的就是依赖开源库更加方便了,基本上一两行代码就能搞定。免去了还要手动下载自己配置的痛苦。
然而,这也仅仅是对使用者而言,而对于发布这些开源库的人就苦逼了,主要是上传太痛苦。
目前来说,比较常见的 Android 开源库托管地址有以下几类:
类型 | 吐槽
------------- | ------------
Maven Central | 发布过程繁杂冗长, 每次发布成功都应该感谢一下上苍的厚爱。
jCenter | jCenter貌似稍微简单一点,但也不是省油的灯。
自定义仓库 | 一般的猿猿玩不起,企业内部可能会见到。
在这些托管地址上面发布过项目的都应该能理解其中的痛苦,不说了,让我哭会儿(我就是那个每次发布都折腾半天的“bug狂魔”,从未一次发布就成功过)。
然而,现在福音来了,JitPack可以帮助你简单快速的发布开源库。
## 在正式讲解之前我们先了解一下JitPack
**JitPack是什么?**
> **JitPack是一个自定义的Maven仓库。**
**JitPack安全吗?**
> **个人还是比较安全的,毕竟开源库都是给大家用的,源码都能分享出来,如果你是担心它在里面插入恶意代码的话,在AndroidStudio的 External Libraies里面能够看到反编译的依赖库的源码,可以查看一下。**
**JitPack好处都有啥!说对我就给他用(金坷垃,雾)**
> **省时间,省时间,省时间,省下的时间都够你修复好几个bug了。**
简单的了解了JitPack之后,开始本篇的正文。
# 如何在JitPack上发布你的Library
**首先,假设大家已经具备了以下条件:**
序号 | 条件
:---:|---------
1 | 会使用GitHub,能提交项目到GitHub上
2 | 使用AndroidStudio,且Gradle版本在2.4以上
在具备了这些条件之后,正式开始发布一个项目(以我的一个[工具仓库Sutil](https://github.com/GcsSloop/SUtil)为例)。
## 第 1 步: 新建一个Project
在AndroidStudio中新建一个Project用于发布项目,新建完成之后结果是这样子:

## 第 2 步: 在这个Project中添加一个Library
添加的这个Library就是我要发布的仓库,Library的名字无所谓,可以随便起(*我这里就叫library*)。添加完成之后是这样子:

### 图中的几个标注
序号 | 解释
:---:|-------
1 | 新添加的Library
2 | Library的build.gradle
3 | Library的plugin
**其中library的plugin是下面这样子:**
``` gradle
apply plugin: 'com.android.library'
```
## 第 3 步: 给你的项目添加配置(重点)
你需要对你的项目简单的配置一下:

**在你项目的根节点的 build.gradle(图示1) 中添加如下代码:**
``` gradle
buildscript {
dependencies {
// 重点就是下面这一行(上面两行是为了定位这一行的添加位置)
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
```
**[完整示例](https://github.com/GcsSloop/SUtil/blob/master/build.gradle)**
**在你要发布的library的 build.gradle(图示2) 中添加如下代码:**
``` gradle
apply plugin: 'com.github.dcendents.android-maven'
group='com.github.YourUsername'
```
**[完整示例](https://github.com/GcsSloop/SUtil/blob/master/library/build.gradle)**
## 第 4 步: 提交项目到GitHub仓库
这一步就不多啰嗦了,不论你是用命令行还是客户端都可以。
**为了提交更加快速,你可以删除无用的文件(文件夹),至于需要保留哪些文件你可以参考官方给出的[示例仓库](https://github.com/jitpack/android-example)**
## 第 5 步: Release你的仓库或者给你的仓库打一个Tag(重点)
### 1.点击图示进入Release界面

### 2.创建一个Release或Tag

### 3.填写基本信息

### 4.完成

## 第 6 步: 将你的仓库地址提交到JitPack(重点)
### 1.将你的仓库地址提交到[JitPack](https://jitpack.io)
**[JitPack地址戳这里](https://jitpack.io)**

序号 | 解释
:---:|--------
1 | 粘贴你的仓库地址
2 | 点击这里查看
3 | 版本号
4 | 点击这里提交该版本
5 | 提交完成后自动生成的日志
### 2.JitPack自动生成的配置信息
在上传完成之后,JitPack会自动生成引用该仓库的配置信息,如下:

**以上就是教程的全部内容,各位小伙伴可以回去愉快的发布自己的开源库了。**
### [JitPack地址](https://jitpack.io)
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width=300/> </a>
================================================
FILE: Course/jitpack-javadoc.md
================================================
# 用JitPack发布时附加文档和源码
很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反馈说**发布到JitPack上的开源库没有文档注释,使用起来很不方便**,这是我的失误,上一篇文章只是讲解了如何使用JitPack发布开源库,最终发布的只有arr(即编译好的动态链接库),不仅没有文档注释(Javadoc),也没有源码(sources),本次就教大家如何在发布同时添加上注释和源码。
**由于JitPack本身就是一个自定义Maven仓库,所以配置方式与Maven基本一样。**
### 配置项目的 build.gradle
项目的 build.gradle 配置和上一篇一样,没有变化。
```java
buildscript {
dependencies {
// 重点就是下面这一行(上面两行是为了定位这一行的添加位置)
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
```
### 配置 Library 的 build.gradle
完整示例(重点内容已经用注释标出):
```java
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven' // 添加这个
group='com.github.GcsSloop' // 指定group,com.github.<用户名>
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
minSdkVersion 7
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}
//---------------------------------------------
// 指定编码
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// 打包源码
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
failOnError false
source = android.sourceSets.main.java.sourceFiles
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
classpath += configurations.compile
}
// 制作文档(Javadoc)
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives sourcesJar
archives javadocJar
}
```
### 发布参照上一篇文章: [使用JitPack发布开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)
### 查看在线文档
如果你在JitPack配置了文档和源码支持,在引用同时就能看到源码的文档,不仅如此,你也可以在线查看。
查看地址是 `https://jitpack.io/com/github/USER/REPO/VERSION/javadoc/`
例如我的一个开源库: https://jitpack.io/com/github/GcsSloop/ViewSupport/v1.2.2/javadoc/
在线API文档样式:

================================================
FILE: CustomView/Advance/Code/CheckView.java
================================================
package com.sloop.canvas;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* <ul type="disc">
* <li>Author: Sloop</li>
* <li>Version: v1.0.0</li>
* <li>Copyright: Copyright (c) 2015</li>
* <li>Date: 2016/2/5</li>
* <li><a href="http://www.sloop.icoc.cc" target="_blank">作者网站</a> </li>
* <li><a href="http://weibo.com/5459430586" target="_blank">作者微博</a> </li>
* <li><a href="https://github.com/GcsSloop" target="_blank">作者GitHub</a> </li>
* </ul>
*/
public class CheckView extends View {
private static final int ANIM_NULL = 0; //动画状态-没有
private static final int ANIM_CHECK = 1; //动画状态-开启
private static final int ANIM_UNCHECK = 2; //动画状态-结束
private Context mContext; // 上下文
private int mWidth, mHeight; // 宽高
private Handler mHandler; // handler
private Paint mPaint;
private Bitmap okBitmap;
private int animCurrentPage = -1; // 当前页码
private int animMaxPage = 13; // 总页数
private int animDuration = 500; // 动画时长
private int animState = ANIM_NULL; // 动画状态
private boolean isCheck = false; // 是否只选中状态
public CheckView(Context context) {
super(context, null);
}
public CheckView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 初始化
* @param context
*/
private void init(Context context) {
mContext = context;
mPaint = new Paint();
mPaint.setColor(0xffFF5317);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
okBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.checkres);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (animCurrentPage < animMaxPage && animCurrentPage >= 0) {
invalidate();
if (animState == ANIM_NULL)
return;
if (animState == ANIM_CHECK) {
animCurrentPage++;
} else if (animState == ANIM_UNCHECK) {
animCurrentPage--;
}
this.sendEmptyMessageDelayed(0, animDuration / animMaxPage);
Log.e("AAA", "Count=" + animCurrentPage);
} else {
if (isCheck) {
animCurrentPage = animMaxPage - 1;
} else {
animCurrentPage = -1;
}
invalidate();
animState = ANIM_NULL;
}
}
};
}
/**
* View大小确定
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
/**
* 绘制内容
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 移动坐标系到画布中央
canvas.translate(mWidth / 2, mHeight / 2);
// 绘制背景圆形
canvas.drawCircle(0, 0, 240, mPaint);
// 得出图像边长
int sideLength = okBitmap.getHeight();
// 得到图像选区 和 实际绘制位置
Rect src = new Rect(sideLength * animCurrentPage, 0, sideLength * (animCurrentPage + 1), sideLength);
Rect dst = new Rect(-200, -200, 200, 200);
// 绘制
canvas.drawBitmap(okBitmap, src, dst, null);
}
/**
* 选择
*/
public void check() {
if (animState != ANIM_NULL || isCheck)
return;
animState = ANIM_CHECK;
animCurrentPage = 0;
mHandler.sendEmptyMessageDelayed(0, animDuration / animMaxPage);
isCheck = true;
}
/**
* 取消选择
*/
public void unCheck() {
if (animState != ANIM_NULL || (!isCheck))
return;
animState = ANIM_UNCHECK;
animCurrentPage = animMaxPage - 1;
mHandler.sendEmptyMessageDelayed(0, animDuration / animMaxPage);
isCheck = false;
}
/**
* 设置动画时长
* @param animDuration
*/
public void setAnimDuration(int animDuration) {
if (animDuration <= 0)
return;
this.animDuration = animDuration;
}
/**
* 设置背景圆形颜色
* @param color
*/
public void setBackgroundColor(int color){
mPaint.setColor(color);
}
}
================================================
FILE: CustomView/Advance/Code/CheckView.md
================================================
## CheckView源代码
[下载代码 ( 右键 -> 另存为 )](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Advance/Code/CheckView.java)
``` java
package com.sloop.canvas;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* <ul type="disc">
* <li>Author: Sloop</li>
* <li>Version: v1.0.0</li>
* <li>Copyright: Copyright (c) 2015</li>
* <li>Date: 2016/2/5</li>
* <li><a href="http://www.gcssloop.com/" target="_blank">作者网站</a> </li>
* <li><a href="http://weibo.com/5459430586" target="_blank">作者微博</a> </li>
* <li><a href="https://github.com/GcsSloop" target="_blank">作者GitHub</a> </li>
* </ul>
*/
public class CheckView extends View {
private static final int ANIM_NULL = 0; //动画状态-没有
private static final int ANIM_CHECK = 1; //动画状态-开启
private static final int ANIM_UNCHECK = 2; //动画状态-结束
private Context mContext; // 上下文
private int mWidth, mHeight; // 宽高
private Handler mHandler; // handler
private Paint mPaint;
private Bitmap okBitmap;
private int animCurrentPage = -1; // 当前页码
private int animMaxPage = 13; // 总页数
private int animDuration = 500; // 动画时长
private int animState = ANIM_NULL; // 动画状态
private boolean isCheck = false; // 是否只选中状态
public CheckView(Context context) {
super(context, null);
}
public CheckView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 初始化
* @param context
*/
private void init(Context context) {
mContext = context;
mPaint = new Paint();
mPaint.setColor(0xffFF5317);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
okBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.checkres);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (animCurrentPage < animMaxPage && animCurrentPage >= 0) {
invalidate();
if (animState == ANIM_NULL)
return;
if (animState == ANIM_CHECK) {
animCurrentPage++;
} else if (animState == ANIM_UNCHECK) {
animCurrentPage--;
}
this.sendEmptyMessageDelayed(0, animDuration / animMaxPage);
Log.e("AAA", "Count=" + animCurrentPage);
} else {
if (isCheck) {
animCurrentPage = animMaxPage - 1;
} else {
animCurrentPage = -1;
}
invalidate();
animState = ANIM_NULL;
}
}
};
}
/**
* View大小确定
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
/**
* 绘制内容
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 移动坐标系到画布中央
canvas.translate(mWidth / 2, mHeight / 2);
// 绘制背景圆形
canvas.drawCircle(0, 0, 240, mPaint);
// 得出图像边长
int sideLength = okBitmap.getHeight();
// 得到图像选区 和 实际绘制位置
Rect src = new Rect(sideLength * animCurrentPage, 0, sideLength * (animCurrentPage + 1), sideLength);
Rect dst = new Rect(-200, -200, 200, 200);
// 绘制
canvas.drawBitmap(okBitmap, src, dst, null);
}
/**
* 选择
*/
public void check() {
if (animState != ANIM_NULL || isCheck)
return;
animState = ANIM_CHECK;
animCurrentPage = 0;
mHandler.sendEmptyMessageDelayed(0, animDuration / animMaxPage);
isCheck = true;
}
/**
* 取消选择
*/
public void unCheck() {
if (animState != ANIM_NULL || (!isCheck))
return;
animState = ANIM_UNCHECK;
animCurrentPage = animMaxPage - 1;
mHandler.sendEmptyMessageDelayed(0, animDuration / animMaxPage);
isCheck = false;
}
/**
* 设置动画时长
* @param animDuration
*/
public void setAnimDuration(int animDuration) {
if (animDuration <= 0)
return;
this.animDuration = animDuration;
}
/**
* 设置背景圆形颜色
* @param color
*/
public void setBackgroundColor(int color){
mPaint.setColor(color);
}
}
```
================================================
FILE: CustomView/Advance/Code/SearchView.java
================================================
/**
* Author: GcsSloop
* <p>
* Created Date: 16/5/31
* <p>
* Copyright (C) 2016 GcsSloop.
* <p>
* GitHub: https://github.com/GcsSloop
*/
public class SearchView extends View {
// 画笔
private Paint mPaint;
// View 宽高
private int mViewWidth;
private int mViewHeight;
public SearchView(Context context) {
this(context,null);
}
public SearchView(Context context, AttributeSet attrs) {
super(context, attrs);
initAll();
}
public void initAll() {
initPaint();
initPath();
initListener();
initHandler();
initAnimator();
// 进入开始动画
mCurrentState = State.STARTING;
mStartingAnimator.start();
}
// 这个视图拥有的状态
public static enum State {
NONE,
STARTING,
SEARCHING,
ENDING
}
// 当前的状态(非常重要)
private State mCurrentState = State.NONE;
// 放大镜与外部圆环
private Path path_srarch;
private Path path_circle;
// 测量Path 并截取部分的工具
private PathMeasure mMeasure;
// 默认的动效周期 2s
private int defaultDuration = 2000;
// 控制各个过程的动画
private ValueAnimator mStartingAnimator;
private ValueAnimator mSearchingAnimator;
private ValueAnimator mEndingAnimator;
// 动画数值(用于控制动画状态,因为同一时间内只允许有一种状态出现,具体数值处理取决于当前状态)
private float mAnimatorValue = 0;
// 动效过程监听器
private ValueAnimator.AnimatorUpdateListener mUpdateListener;
private Animator.AnimatorListener mAnimatorListener;
// 用于控制动画状态转换
private Handler mAnimatorHandler;
// 判断是否已经搜索结束
private boolean isOver = false;
private int count = 0;
private void initPaint() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(15);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setAntiAlias(true);
}
private void initPath() {
path_srarch = new Path();
path_circle = new Path();
mMeasure = new PathMeasure();
// 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值
RectF oval1 = new RectF(-50, -50, 50, 50); // 放大镜圆环
path_srarch.addArc(oval1, 45, 359.9f);
RectF oval2 = new RectF(-100, -100, 100, 100); // 外部圆环
path_circle.addArc(oval2, 45, -359.9f);
float[] pos = new float[2];
mMeasure.setPath(path_circle, false); // 放大镜把手的位置
mMeasure.getPosTan(0, pos, null);
path_srarch.lineTo(pos[0], pos[1]); // 放大镜把手
Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);
}
private void initListener() {
mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimatorValue = (float) animation.getAnimatedValue();
invalidate();
}
};
mAnimatorListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
// getHandle发消息通知动画状态更新
mAnimatorHandler.sendEmptyMessage(0);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
}
private void initHandler() {
mAnimatorHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (mCurrentState) {
case STARTING:
// 从开始动画转换好搜索动画
isOver = false;
mCurrentState = State.SEARCHING;
mStartingAnimator.removeAllListeners();
mSearchingAnimator.start();
break;
case SEARCHING:
if (!isOver) { // 如果搜索未结束 则继续执行搜索动画
mSearchingAnimator.start();
Log.e("Update", "RESTART");
count++;
if (count>2){ // count大于2则进入结束状态
isOver = true;
}
} else { // 如果搜索已经结束 则进入结束动画
mCurrentState = State.ENDING;
mEndingAnimator.start();
}
break;
case ENDING:
// 从结束动画转变为无状态
mCurrentState = State.NONE;
break;
}
}
};
}
private void initAnimator() {
mStartingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
mSearchingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
mEndingAnimator = ValueAnimator.ofFloat(1, 0).setDuration(defaultDuration);
mStartingAnimator.addUpdateListener(mUpdateListener);
mSearchingAnimator.addUpdateListener(mUpdateListener);
mEndingAnimator.addUpdateListener(mUpdateListener);
mStartingAnimator.addListener(mAnimatorListener);
mSearchingAnimator.addListener(mAnimatorListener);
mEndingAnimator.addListener(mAnimatorListener);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawSearch(canvas);
}
private void drawSearch(Canvas canvas) {
mPaint.setColor(Color.WHITE);
canvas.translate(mViewWidth / 2, mViewHeight / 2);
canvas.drawColor(Color.parseColor("#0082D7"));
switch (mCurrentState) {
case NONE:
canvas.drawPath(path_srarch, mPaint);
break;
case STARTING:
mMeasure.setPath(path_srarch, false);
Path dst = new Path();
mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst, true);
canvas.drawPath(dst, mPaint);
break;
case SEARCHING:
mMeasure.setPath(path_circle, false);
Path dst2 = new Path();
float stop = mMeasure.getLength() * mAnimatorValue;
float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * 200f));
mMeasure.getSegment(start, stop, dst2, true);
canvas.drawPath(dst2, mPaint);
break;
case ENDING:
mMeasure.setPath(path_srarch, false);
Path dst3 = new Path();
mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst3, true);
canvas.drawPath(dst3, mPaint);
break;
}
}
}
================================================
FILE: CustomView/Advance/Code/SearchView.md
================================================
## SearchView 源代码
[下载代码 (右键 -> 另存为)](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Advance/Code/SearchView.java)
``` java
/**
* Author: GcsSloop
* <p>
* Created Date: 16/5/31
* <p>
* Copyright (C) 2016 GcsSloop.
* <p>
* GitHub: https://github.com/GcsSloop
*/
public class SearchView extends View {
// 画笔
private Paint mPaint;
// View 宽高
private int mViewWidth;
private int mViewHeight;
public SearchView(Context context) {
this(context,null);
}
public SearchView(Context context, AttributeSet attrs) {
super(context, attrs);
initAll();
}
public void initAll() {
initPaint();
initPath();
initListener();
initHandler();
initAnimator();
// 进入开始动画
mCurrentState = State.STARTING;
mStartingAnimator.start();
}
// 这个视图拥有的状态
public static enum State {
NONE,
STARTING,
SEARCHING,
ENDING
}
// 当前的状态(非常重要)
private State mCurrentState = State.NONE;
// 放大镜与外部圆环
private Path path_srarch;
private Path path_circle;
// 测量Path 并截取部分的工具
private PathMeasure mMeasure;
// 默认的动效周期 2s
private int defaultDuration = 2000;
// 控制各个过程的动画
private ValueAnimator mStartingAnimator;
private ValueAnimator mSearchingAnimator;
private ValueAnimator mEndingAnimator;
// 动画数值(用于控制动画状态,因为同一时间内只允许有一种状态出现,具体数值处理取决于当前状态)
private float mAnimatorValue = 0;
// 动效过程监听器
private ValueAnimator.AnimatorUpdateListener mUpdateListener;
private Animator.AnimatorListener mAnimatorListener;
// 用于控制动画状态转换
private Handler mAnimatorHandler;
// 判断是否已经搜索结束
private boolean isOver = false;
private int count = 0;
private void initPaint() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(15);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setAntiAlias(true);
}
private void initPath() {
path_srarch = new Path();
path_circle = new Path();
mMeasure = new PathMeasure();
// 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值
RectF oval1 = new RectF(-50, -50, 50, 50); // 放大镜圆环
path_srarch.addArc(oval1, 45, 359.9f);
RectF oval2 = new RectF(-100, -100, 100, 100); // 外部圆环
path_circle.addArc(oval2, 45, -359.9f);
float[] pos = new float[2];
mMeasure.setPath(path_circle, false); // 放大镜把手的位置
mMeasure.getPosTan(0, pos, null);
path_srarch.lineTo(pos[0], pos[1]); // 放大镜把手
Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);
}
private void initListener() {
mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimatorValue = (float) animation.getAnimatedValue();
invalidate();
}
};
mAnimatorListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
// getHandle发消息通知动画状态更新
mAnimatorHandler.sendEmptyMessage(0);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
}
private void initHandler() {
mAnimatorHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (mCurrentState) {
case STARTING:
// 从开始动画转换好搜索动画
isOver = false;
mCurrentState = State.SEARCHING;
mStartingAnimator.removeAllListeners();
mSearchingAnimator.start();
break;
case SEARCHING:
if (!isOver) { // 如果搜索未结束 则继续执行搜索动画
mSearchingAnimator.start();
Log.e("Update", "RESTART");
count++;
if (count>2){ // count大于2则进入结束状态
isOver = true;
}
} else { // 如果搜索已经结束 则进入结束动画
mCurrentState = State.ENDING;
mEndingAnimator.start();
}
break;
case ENDING:
// 从结束动画转变为无状态
mCurrentState = State.NONE;
break;
}
}
};
}
private void initAnimator() {
mStartingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
mSearchingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
mEndingAnimator = ValueAnimator.ofFloat(1, 0).setDuration(defaultDuration);
mStartingAnimator.addUpdateListener(mUpdateListener);
mSearchingAnimator.addUpdateListener(mUpdateListener);
mEndingAnimator.addUpdateListener(mUpdateListener);
mStartingAnimator.addListener(mAnimatorListener);
mSearchingAnimator.addListener(mAnimatorListener);
mEndingAnimator.addListener(mAnimatorListener);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawSearch(canvas);
}
private void drawSearch(Canvas canvas) {
mPaint.setColor(Color.WHITE);
canvas.translate(mViewWidth / 2, mViewHeight / 2);
canvas.drawColor(Color.parseColor("#0082D7"));
switch (mCurrentState) {
case NONE:
canvas.drawPath(path_srarch, mPaint);
break;
case STARTING:
mMeasure.setPath(path_srarch, false);
Path dst = new Path();
mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst, true);
canvas.drawPath(dst, mPaint);
break;
case SEARCHING:
mMeasure.setPath(path_circle, false);
Path dst2 = new Path();
float stop = mMeasure.getLength() * mAnimatorValue;
float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * 200f));
mMeasure.getSegment(start, stop, dst2, true);
canvas.drawPath(dst2, mPaint);
break;
case ENDING:
mMeasure.setPath(path_srarch, false);
Path dst3 = new Path();
mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst3, true);
canvas.drawPath(dst3, mPaint);
break;
}
}
}
```
================================================
FILE: CustomView/Advance/Code/SetPolyToPoly.java
================================================
package com.gcssloop.canvas;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.gcssloop.view.utils.CanvasAidUtils;
/**
* Author: GcsSloop
* <p>
* Created Date: 16/8/26
* <p>
* Copyright (C) 2016 GcsSloop.
* <p>
* GitHub: https://github.com/GcsSloop
*/
public class SetPolyToPoly extends View{
private static final String TAG = "SetPolyToPoly";
private int testPoint = 0;
private int triggerRadius = 180; // 触发半径为180px
private Bitmap mBitmap; // 要绘制的图片
private Matrix mPolyMatrix; // 测试setPolyToPoly用的Matrix
private float[] src = new float[8];
private float[] dst = new float[8];
private Paint pointPaint;
public SetPolyToPoly(Context context) {
this(context, null);
}
public SetPolyToPoly(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SetPolyToPoly(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initBitmapAndMatrix();
}
private void initBitmapAndMatrix() {
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.poly_test2);
float[] temp = {0, 0, // 左上
mBitmap.getWidth(), 0, // 右上
mBitmap.getWidth(), mBitmap.getHeight(), // 右下
0, mBitmap.getHeight()}; // 左下
src = temp.clone();
dst = temp.clone();
pointPaint = new Paint();
pointPaint.setAntiAlias(true);
pointPaint.setStrokeWidth(50);
pointPaint.setColor(0xffd19165);
pointPaint.setStrokeCap(Paint.Cap.ROUND);
mPolyMatrix = new Matrix();
mPolyMatrix.setPolyToPoly(src, 0, src, 0, 4);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
float tempX = event.getX();
float tempY = event.getY();
// 根据触控位置改变dst
for (int i=0; i<testPoint*2; i+=2 ) {
if (Math.abs(tempX - dst[i]) <= triggerRadius && Math.abs(tempY - dst[i+1]) <= triggerRadius){
dst[i] = tempX-100;
dst[i+1] = tempY-100;
break; // 防止两个点的位置重合
}
}
resetPolyMatrix(testPoint);
invalidate();
break;
}
return true;
}
public void resetPolyMatrix(int pointCount){
mPolyMatrix.reset();
// 核心要点
mPolyMatrix.setPolyToPoly(src, 0, dst, 0, pointCount);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.translate(100,100);
// 绘制坐标系
CanvasAidUtils.setCoordinateLen(900, 0, 1200, 0);
CanvasAidUtils.drawCoordinateSpace(canvas);
// 根据Matrix绘制一个变换后的图片
canvas.drawBitmap(mBitmap, mPolyMatrix, null);
float[] dst = new float[8];
mPolyMatrix.mapPoints(dst,src);
// 绘制触控点
for (int i=0; i<testPoint*2; i+=2 ) {
canvas.drawPoint(dst[i], dst[i+1],pointPaint);
}
}
public void setTestPoint(int testPoint) {
this.testPoint = testPoint > 4 || testPoint < 0 ? 4 : testPoint;
dst = src.clone();
resetPolyMatrix(this.testPoint);
invalidate();
}
}
================================================
FILE: CustomView/Advance/Code/SetPolyToPoly.md
================================================
# Matrix setPolyTOPoly 测试代码
## SetPolyToPoly.java
[下载代码 (右键 -> 另存为)](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Advance/Code/SetPolyToPoly.java)
```java
public class SetPolyToPoly extends View{
private static final String TAG = "SetPolyToPoly";
private int testPoint = 0;
private int triggerRadius = 180; // 触发半径为180px
private Bitmap mBitmap; // 要绘制的图片
private Matrix mPolyMatrix; // 测试setPolyToPoly用的Matrix
private float[] src = new float[8];
private float[] dst = new float[8];
private Paint pointPaint;
public SetPolyToPoly(Context context) {
this(context, null);
}
public SetPolyToPoly(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SetPolyToPoly(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initBitmapAndMatrix();
}
private void initBitmapAndMatrix() {
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.poly_test2);
float[] temp = {0, 0, // 左上
mBitmap.getWidth(), 0, // 右上
mBitmap.getWidth(), mBitmap.getHeight(), // 右下
0, mBitmap.getHeight()}; // 左下
src = temp.clone();
dst = temp.clone();
pointPaint = new Paint();
pointPaint.setAntiAlias(true);
pointPaint.setStrokeWidth(50);
pointPaint.setColor(0xffd19165);
pointPaint.setStrokeCap(Paint.Cap.ROUND);
mPolyMatrix = new Matrix();
mPolyMatrix.setPolyToPoly(src, 0, src, 0, 4);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
float tempX = event.getX();
float tempY = event.getY();
// 根据触控位置改变dst
for (int i=0; i<testPoint*2; i+=2 ) {
if (Math.abs(tempX - dst[i]) <= triggerRadius && Math.abs(tempY - dst[i+1]) <= triggerRadius){
dst[i] = tempX-100;
dst[i+1] = tempY-100;
break; // 防止两个点的位置重合
}
}
resetPolyMatrix(testPoint);
invalidate();
break;
}
return true;
}
public void resetPolyMatrix(int pointCount){
mPolyMatrix.reset();
// 核心要点
mPolyMatrix.setPolyToPoly(src, 0, dst, 0, pointCount);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.translate(100,100);
// 绘制坐标系
CanvasAidUtils.setCoordinateLen(900, 0, 1200, 0);
CanvasAidUtils.drawCoordinateSpace(canvas);
// 根据Matrix绘制一个变换后的图片
canvas.drawBitmap(mBitmap, mPolyMatrix, null);
float[] dst = new float[8];
mPolyMatrix.mapPoints(dst,src);
// 绘制触控点
for (int i=0; i<testPoint*2; i+=2 ) {
canvas.drawPoint(dst[i], dst[i+1],pointPaint);
}
}
public void setTestPoint(int testPoint) {
this.testPoint = testPoint > 4 || testPoint < 0 ? 4 : testPoint;
dst = src.clone();
resetPolyMatrix(this.testPoint);
invalidate();
}
}
```
*****
## 布局文件
``` xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:context="com.gcssloop.canvas.MainActivity">
<com.gcssloop.canvas.SetPolyToPoly
android:id="@+id/poly"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:padding="10dp"
android:gravity="center"
android:layout_height="60dp">
<RadioGroup
android:id="@+id/group"
android:orientation="horizontal"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:checked="true"
android:id="@+id/point0"
android:text="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton
android:id="@+id/point1"
android:text="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton
android:id="@+id/point2"
android:text="2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton
android:id="@+id/point3"
android:text="3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton
android:id="@+id/point4"
android:text="4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RadioGroup>
</LinearLayout>
</RelativeLayout>
```
*****
## MainActivity
``` java
setContentView(R.layout.activity_main);
final SetPolyToPoly poly = (SetPolyToPoly) findViewById(R.id.poly);
RadioGroup group = (RadioGroup) findViewById(R.id.group);
assert group != null;
group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (group.getCheckedRadioButtonId()){
case R.id.point0: poly.setTestPoint(0); break;
case R.id.point1: poly.setTestPoint(1); break;
case R.id.point2: poly.setTestPoint(2); break;
case R.id.point3: poly.setTestPoint(3); break;
case R.id.point4: poly.setTestPoint(4); break;
}
}
});
```
*****
## 依赖的库
绘制坐标系部分依赖了一个开源库。
* [ViewSupport](https://github.com/GcsSloop/ViewSupport )
================================================
FILE: CustomView/Advance/[01]CustomViewProcess.md
================================================
# 自定义View分类与流程
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md)
经历过前面三篇啰啰嗦嗦的基础篇之后,终于到了进阶篇,正式进入解析自定义View的阶段。
## 前言
**本章节为什么要叫进阶篇?(虽然讲的是基础内容),因为从本篇开始,将会逐渐揭开自定义View的神秘面纱,每一篇都将比上一篇内容更加深入,利用所学的知识能够制作更加炫酷自定义View,就像在台阶上一样,每一篇都更上一层,~~帮助大家一步步走向人生巅峰,出任CEO,迎娶白富美。~~ 误,是帮助大家更加了解那些炫酷的自定义View是如何制作的,达到举一反三的效果。**
自定义View绘制流程函数调用链(简化版)

## 一.自定义View分类
**我将自定义View分为了两类(sloop个人分类法,非官方):**
### 1.自定义ViewGroup
**自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout,包含有子View。**
> 例如:应用底部导航条中的条目,一般都是上面图标(ImageView),下面文字(TextView),那么这两个就可以用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片,使用起来会更加方便。
### 2.自定义View
**在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View,不包含子View。**
> 例如:制作一个支持自动加载网络图片的ImageView,制作图表等。
**PS: 自定义View在大多数情况下都有替代方案,利用图片或者组合动画来实现,但是使用后者可能会面临内存耗费过大,制作麻烦等诸多问题。**
*******
## 二.几个重要的函数
### 1.构造函数
构造函数是View的入口,可以用于**初始化一些的内容,和获取自定义属性**。
View的构造函数有四种重载分别如下:
``` java
public void SloopView(Context context) {}
public void SloopView(Context context, AttributeSet attrs) {}
public void SloopView(Context context, AttributeSet attrs, int defStyleAttr) {}
public void SloopView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {}
```
可以看出,关于View构造函数的参数有多有少,先排除几个不常用的,留下常用的再研究。
**有四个参数的构造函数在API21的时候才添加上,暂不考虑。**
有三个参数的构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或Activity所用的Theme中的默认Style,且只有在明确调用的时候才会生效,以系统中的ImageButton为例说明:
``` java
public ImageButton(Context context, AttributeSet attrs) {
//调用了三个参数的构造函数,明确指定第三个参数
this(context, attrs, com.android.internal.R.attr.imageButtonStyle);
}
public ImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
//此处调了四个参数的构造函数,无视即可
this(context, attrs, defStyleAttr, 0);
}
```
**注意:即使你在View中使用了Style这个属性也不会调用三个参数的构造函数,所调用的依旧是两个参数的构造函数。**
**由于三个参数的构造函数第三个参数一般不用,暂不考虑,第三个参数的具体用法会在以后用到的时候详细介绍。**
排除了两个之后,只剩下一个参数和两个参数的构造函数,他们的详情如下:
``` java
//一般在直接New一个View的时候调用。
public void SloopView(Context context) {}
//一般在layout文件中使用的时候会调用,关于它的所有属性(包括自定义属性)都会包含在attrs中传递进来。
public void SloopView(Context context, AttributeSet attrs) {}
```
**以下方法调用的是一个参数的构造函数:**
``` java
//在Avtivity中
SloopView view = new SloopView(this);
```
**以下方法调用的是<b>两个参数</b>的构造函数:**
``` xml
//在layout文件中 - 格式为: 包名.View名
<com.sloop.study.SloopView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
```
关于构造函数先讲这么多,关于如何自定义属性和使用attrs中的内容,在后面会详细讲解,目前只需要知道这两个构造函数在何时调用即可。
========
### 2.测量View大小(onMeasure)
**Q: 为什么要测量View大小?**
**A: View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量。**
测量View大小使用的是onMeasure函数,我们可以从onMeasure的两个参数中取出宽高的相关数据:
``` java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthsize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
int widthmode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
int heightsize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
int heightmode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
}
```
从上面可以看出 onMeasure 函数中有 widthMeasureSpec 和 heightMeasureSpec 这两个 int 类型的参数, 毫无疑问他们是和宽高相关的, **但它们其实不是宽和高, 而是由宽、高和各自方向上对应的测量模式来合成的一个值:**
**测量模式一共有三种, 被定义在 Android 中的 View 类的一个内部类View.MeasureSpec中:**
| 模式 | 二进制数值 | 描述 |
| ----------- | :---: | -------------------------------------- |
| UNSPECIFIED | 00 | 默认值,父控件没有给子view任何限制,子View可以设置为任意大小。 |
| EXACTLY | 01 | 表示父控件已经确切的指定了子View的大小。 |
| AT_MOST | 10 | 表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。 |
**在int类型的32位二进制位中,31-30这两位表示测量模式,29~0这三十位表示宽和高的实际值,实际上如下:**
以数值1080(二进制为: 1111011000)为例(其中模式和实际数值是连在一起的,为了展示我将他们分开了):
| 模式名称 | 模式数值 | 实际数值 |
| ----------- | ---: | ------------------------------ |
| UNSPECIFIED | 00 | 000000000000000000001111011000 |
| EXACTLY | 01 | 000000000000000000001111011000 |
| AT_MOST | 10 | 000000000000000000001111011000 |
**PS: 实际上关于上面的东西了解即可,在实际运用之中只需要记住有三种模式,用 MeasureSpec 的 getSize是获取数值, getMode是获取模式即可。**
#### 注意:
**如果对View的宽高进行修改了,不要调用*super.onMeasure(widthMeasureSpec,heightMeasureSpec);*要调用*setMeasuredDimension(widthsize,heightsize);* 这个函数。**
======
### 3.确定View大小(onSizeChanged)
这个函数在视图大小发生改变时调用。
**Q: 在测量完View并使用setMeasuredDimension函数之后View的大小基本上已经确定了,那么为什么还要再次确定View的大小呢?**
**A: 这是因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。**
onSizeChanged如下:
``` java
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
```
可以看出,它又四个参数,分别为 宽度,高度,上一次宽度,上一次高度。
这个函数比较简单,我们只需关注 宽度(w), 高度(h) 即可,这两个参数就是View最终的大小。
=========
### 4.确定子View布局位置(onLayout)
**确定布局的函数是onLayout,它用于确定子View的位置,在自定义ViewGroup中会用到,他调用的是子View的layout函数。**
在自定义ViewGroup中,onLayout一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。
``` java
child.layout(l, t, r, b);
```
四个参数分别为:
| 名称 | 说明 | 对应的函数 |
| ---- | ----------------- | ------------ |
| l | View左侧距父View左侧的距离 | getLeft(); |
| t | View顶部距父View顶部的距离 | getTop(); |
| r | View右侧距父View左侧的距离 | getRight(); |
| b | View底部距父View顶部的距离 | getBottom(); |
具体可以参考 [坐标系](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Base/%5B01%5DCoordinateSystem.md) 这篇文章。

PS:关于onLayout这个函数在讲解自定义ViewGroup的时候会详细讲解。
========
### 5.绘制内容(onDraw)
onDraw是实际绘制的部分,也就是我们真正关心的部分,使用的是Canvas绘图。
``` java
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
```
关于Canvas绘图是本章节的重点,会分几篇文章进行详细讲解,敬请期待OwO。
======
### 6.对外提供操作方法和监听回调
自定义完View之后,一般会对外暴露一些接口,用于控制View的状态等,或者监听View的变化.
本内容会在后续文章中以实例的方式进讲解。
************
## 三.重点知识梳理
### 自定义View分类
> PS :实际上ViewGroup是View的一个子类。
| 类别 | 继承自 | 特点 |
| --------- | ------------------- | ------- |
| View | View SurfaceView 等 | 不含子View |
| ViewGroup | ViewGroup xxLayout等 | 包含子View |
### 自定义View流程:
| 步骤 | 关键字 | 作用 |
| ---- | ------------- | ---------------------------- |
| 1 | 构造函数 | View初始化 |
| 2 | onMeasure | 测量View大小 |
| 3 | onSizeChanged | 确定View大小 |
| 4 | onLayout | 确定子View布局(自定义View包含子View时有用) |
| 5 | onDraw | 实际绘制内容 |
| 6 | 提供接口 | 控制View或监听View某些状态。 |
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width=300/> </a>
## 参考资料:
[View](http://developer.android.com/reference/android/view/View.html)<br/>
[ViewGroup](http://developer.android.com/reference/android/view/ViewGroup.html)<br/>
[View.MeasureSpec](http://developer.android.com/reference/android/view/View.MeasureSpec.html)<br/>
[onMeasure,MeasureSpec源码 流程 思路详解](http://blog.csdn.net/a396901990/article/details/36475213)<br/>
<br/>
[Android中自定义样式与View的构造函数中的第三个参数defStyle的意义](http://www.cnblogs.com/angeldevil/p/3479431.html) <br/>
[android view构造函数研究](http://blog.csdn.net/z103594643/article/details/6755017)<br/>
[Android View构造方法第三参数使用方法详解](http://blog.csdn.net/mybeta/article/details/39993449)<br/>
<br/>
[Android 自定义View onMeasure方法的实现](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1102/1891.html)<br/>
[Android API指南(二)自定义控件02之 onMeasure](http://wangkuiwu.github.io/2014/06/20/View-OnMeasure/)<br/>
[Android中View的绘制过程 onMeasure方法简述](http://www.cnblogs.com/mengdd/p/3332882.html)<br/>
<br/>
<br/>
================================================
FILE: CustomView/Advance/[02]Canvas_BasicGraphics.md
================================================
# Canvas之绘制基本形状
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md)
在上一篇[自定义View分类与流程](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B01%5DCustomViewProcess.md)中我们了解自定义View相关的基本知识,不过,这些东西依旧还是理论,并不能**拿来(zhuang)用(B)**, 这一次我们就了解一些**能(zhaung)用(B)**的东西。
在本篇文章中,我们先了解Canvas的基本用法,最后用一个小示例来结束本次教程。
## 一.Canvas简介
Canvas我们可以称之为画布,能够在上面绘制各种东西,是安卓平台2D图形绘制的基础,非常强大。
**一般来说,比较基础的东西有两大特点:<br/>
1.可操作性强:由于这些是构成上层的基础,所以可操作性必然十分强大。<br/>
2.比较难用:各种方法太过基础,想要完美的将这些操作组合起来有一定难度。**
不过不必担心,本系列文章不仅会介绍到Canvas的操作方法,还会简单介绍一些设计思路和技巧。
## 二.Canvas的常用操作速查表
| 操作类型 | 相关API | 备注 |
| ---------- | ---------------------------------------- | ---------------------------------------- |
| 绘制颜色 | drawColor, drawRGB, drawARGB | 使用单一颜色填充整个画布 |
| 绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧 |
| 绘制图片 | drawBitmap, drawPicture | 绘制位图和图片 |
| 绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字 |
| 绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数 |
| 顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
| 画布剪裁 | clipPath, clipRect | 设置画布的显示区域 |
| 画布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数 |
| 画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切 |
| Matrix(矩阵) | getMatrix, setMatrix, concat | 实际画布的位移,缩放等操作的都是图像矩阵Matrix,只不过Matrix比较难以理解和使用,故封装了一些常用的方法。 |
> PS: Canvas常用方法在上面表格中已经全部列出了,当然还存在一些其他的方法未列出,具体可以参考官方文档 [Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)
******
## 三.Canvas详解
本篇内容主要讲解如何利用Canvas绘制基本图形。
### 绘制颜色:
绘制颜色是填充整个画布,常用于绘制底色。
``` java
canvas.drawColor(Color.BLUE); //绘制蓝色
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2742437w3j30u01hcjrq.jpg" width = "300" />
> 关于颜色的更多资料请参考[基础篇_颜色](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B03%5DColor.md)
******
### 创建画笔:
要想绘制内容,首先需要先创建一个画笔,如下:
``` java
// 1.创建一个画笔
private Paint mPaint = new Paint();
// 2.初始化画笔
private void initPaint() {
mPaint.setColor(Color.BLACK); //设置画笔颜色
mPaint.setStyle(Paint.Style.FILL); //设置画笔模式为填充
mPaint.setStrokeWidth(10f); //设置画笔宽度为10px
}
// 3.在构造函数中初始化
public SloopView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
```
在创建完画笔之后,就可以在Canvas中绘制各种内容了。
******
### 绘制点:
可以绘制一个点,也可以绘制一组点,如下:
``` java
canvas.drawPoint(200, 200, mPaint); //在坐标(200,200)位置绘制一个点
canvas.drawPoints(new float[]{ //绘制一组点,坐标位置由float数组指定
500,500,
500,600,
500,700
},mPaint);
```
关于坐标原点默认在左上角,水平向右为x轴增大方向,竖直向下为y轴增大方向。
> 更多参考这里 [基础篇_坐标系](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B01%5DCoordinateSystem.md)
<img src="http://ww1.sinaimg.cn/large/005Xtdi2jw1f2743rkifnj30u01hc74n.jpg" width = "300" />
******
### 绘制直线:
绘制直线需要两个点,初始点和结束点,同样绘制直线也可以绘制一条或者绘制一组:
``` java
canvas.drawLine(300,300,500,600,mPaint); // 在坐标(300,300)(500,600)之间绘制一条直线
canvas.drawLines(new float[]{ // 绘制一组线 每四数字(两个点的坐标)确定一条线
100,200,200,200,
100,300,200,300
},mPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f2745k83ybj30u01hcq3d.jpg" width = "300" />
******
### 绘制矩形:
我们都知道,确定一个矩形最少需要四个数据,就是**对角线的两个点**的坐标值,这里一般采用**左上角和右下角**的两个点的坐标。
关于绘制矩形,Canvas提供了三种重载方法,第一种就是提供**四个数值(矩形左上角和右下角两个点的坐标)来确定一个矩形**进行绘制。
其余两种是先将矩形封装为**Rect或RectF**(实际上仍然是用两个坐标点来确定的矩形),然后传递给Canvas绘制,如下:
``` java
// 第一种
canvas.drawRect(100,100,800,400,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);
```
以上三种方法所绘制出来的结果是完全一样的。
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f27478692dj30u01hc3yy.jpg" width = "300" />
看到这里,相信很多观众会产生一个疑问,<b>为什么会有Rect和RectF两种?两者有什么区别吗?</b>
答案当然是存在区别的,**两者最大的区别就是精度不同,Rect是int(整形)的,而RectF是float(单精度浮点型)的**。除了精度不同,两种提供的方法也稍微存在差别,在这里我们暂时无需关注,想了解更多参见官方文档 [Rect](http://developer.android.com/reference/android/graphics/Rect.html) 和 [RectF](http://developer.android.com/reference/android/graphics/RectF.html)
******
### 绘制圆角矩形:
绘制圆角矩形也提供了两种重载方式,如下:
``` java
// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawRoundRect(rectF,30,30,mPaint);
// 第二种
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);
```
上面两种方法绘制效果也是一样的,但鉴于第二种方法在API21的时候才添加上,所以我们一般使用的都是第一种。
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2747s3c5zj30u01hcq3e.jpg" width = "300" />
下面简单解析一下圆角矩形的几个必要的参数的意思。
很明显可以看出,第二种方法前四个参数和第一种方法的RectF作用是一样的,都是为了确定一个矩形,最后一个参数Paint是画笔,无需多说,**与矩形相比,圆角矩形多出来了两个参数rx 和 ry**,这两个参数是干什么的呢?
稍微分析一下,既然是圆角矩形,他的角肯定是圆弧(圆形的一部分),**我们一般用什么确定一个圆形呢?**
答案是**圆心 和 半径,其中圆心用于确定位置,而半径用于确定大小**。<br/>
由于矩形位置已经确定,所以其边角位置也是确定的,那么确定位置的参数就可以省略,只需要用半径就能描述一个圆弧了。<br/>
但是,**半径只需要一个参数,但这里怎么会有两个呢?**<br/>
好吧,让你发现了,**这里圆角矩形的角实际上不是一个正圆的圆弧,而是椭圆的圆弧,这里的两个参数实际上是椭圆的两个半径**,他们看起来个如下图:<br/>

**红线标注的 rx 与 ry 就是两个半径,也就是相比绘制矩形多出来的那两个参数。**
我们了解到原理后,就可以为所欲为了,通过计算可知我们上次绘制的矩形宽度为700,高度为300,当你让 rx大于350(宽度的一半), ry大于150(高度的一半) 时奇迹就出现了, 你会发现圆角矩形变成了一个椭圆, 他们画出来是这样的 ( 为了方便确认我更改了画笔颜色, 同时绘制出了矩形和圆角矩形 ):
``` java
// 矩形
RectF rectF = new RectF(100,100,800,400);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);
// 绘制圆角矩形
mPaint.setColor(Color.BLUE);
canvas.drawRoundRect(rectF,700,400,mPaint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2748ugy2pj30u01hcwf1.jpg" width = "300" />
其中灰色部分是我们所选定的矩形,而里面的圆角矩形则变成了一个椭圆,<b>实际上在rx为宽度的一半,ry为高度的一半时,刚好是一个椭圆,通过上面我们分析的原理推算一下就能得到,而当rx大于宽度的一半,ry大于高度的一半时,实际上是无法计算出圆弧的,所以drawRoundRect对大于该数值的参数进行了限制(修正),凡是大于一半的参数均按照一半来处理。</b>
******
### 绘制椭圆:
相对于绘制圆角矩形,绘制椭圆就简单的多了,因为他只需要一个矩形作为参数:
``` java
// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawOval(rectF,mPaint);
// 第二种
canvas.drawOval(100,100,800,400,mPaint);
```
同样,以上两种方法效果完全一样,但一般使用第一种。
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f274afxbiyj30u01hczks.jpg" width = "300" />
绘制椭圆实际上就是绘制一个矩形的内切图形,原理如下,就不多说了:

PS: 如果你传递进来的是一个长宽相等的矩形(即正方形),那么绘制出来的实际上就是一个圆。
******
### 绘制圆:
绘制圆形也比较简单, 如下:
```
canvas.drawCircle(500,500,400,mPaint); // 绘制一个圆心坐标在(500,500),半径为400 的圆。
```
绘制圆形有四个参数,前两个是圆心坐标,第三个是半径,最后一个是画笔。
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f274c41kknj30u01hcdgf.jpg" width = "300" />
******
### 绘制圆弧:
绘制圆弧就比较神奇一点了,为了理解这个比较神奇的东西,我们先看一下它需要的几个参数:
``` java
// 第一种
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}
// 第二种
public void drawArc(float left, float top, float right, float bottom, float startAngle,
float sweepAngle, boolean useCenter, @NonNull Paint paint) {}
```
从上面可以看出,相比于绘制椭圆,绘制圆弧还多了三个参数:
``` java
startAngle // 开始角度
sweepAngle // 扫过角度
useCenter // 是否使用中心
```
通过字面意思我们基本能猜测出来前两个参数(startAngle, sweepAngel)的作用,就是确定角度的起始位置和扫过角度, 不过第三个参数是干嘛的?试一下就知道了,上代码:
```
RectF rectF = new RectF(100,100,800,400);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);
// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);
//-------------------------------------
RectF rectF2 = new RectF(100,600,800,900);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);
// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);
```
上述代码实际上是绘制了一个起始角度为0度,扫过90度的圆弧,两者的区别就是是否使用了中心点,结果如下:
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f274d1smwej30u01hc3z4.jpg" width = "300" />
可以发现使用了中心点之后绘制出来类似于一个扇形,而不使用中心点则是圆弧起始点和结束点之间的连线加上圆弧围成的图形。这样中心点这个参数的作用就很明显了,不必多说想必大家试一下就明白了。 另外可以关于角度可以参考一下这篇文章: [角度与弧度](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B02%5DAngleAndRadian.md)
相比于使用椭圆,我们还是使用正圆比较多的,使用正圆展示一下效果:
```
RectF rectF = new RectF(100,100,600,600);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);
// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);
//-------------------------------------
RectF rectF2 = new RectF(100,700,600,1200);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);
// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f274e3surgj30u01hc3z4.jpg" width = "300" />
******
### 简要介绍Paint
看了上面这么多,相信有一部分人会产生一个疑问,如果我想绘制一个圆,只要边不要里面的颜色怎么办?
很简单,绘制的**基本形状由Canvas确定**,但绘制出来的**颜色,具体效果则由Paint确定**。
如果你注意到了的话,在一开始我们设置画笔样式的时候是这样的:
``` java
mPaint.setStyle(Paint.Style.FILL); //设置画笔模式为填充
```
为了展示方便,容易看出效果,之前使用的模式一直为填充模式,实际上画笔有三种模式,如下:
``` java
STROKE //描边
FILL //填充
FILL_AND_STROKE //描边加填充
```
为了区分三者效果我们做如下实验:
```
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(40); //为了实验效果明显,特地设置描边宽度非常大
// 描边
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200,200,100,paint);
// 填充
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,500,100,paint);
// 描边加填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(200, 800, 100, paint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f274g6lxbpj30u01hcq3n.jpg" width = "300" />
一图胜千言,通过以上实验我们可以比较明显的看出三种模式的区别,如果只需要边缘不需要填充内容的话只需要设置模式为描边(STROKE)即可。
其实关于Paint的内容也是有不少的,这些只是冰山一角,在后续内容中会详细的讲解Paint。
******
## 小示例
### 简要介绍画布的操作:
> 画布操作详细内容会在下一篇文章中讲解, 不是本文重点,但以下示例中可能会用到,所以此处简要介绍一下。
| 相关操作 | 简要介绍 |
| --------- | ----------- |
| save | 保存当前画布状态 |
| restore | 回滚到上一次保存的状态 |
| translate | 相对于当前位置位移 |
| rotate | 旋转 |
### 制作一个饼状图
在展示百分比数据的时候经常会用到饼状图,像这样:

### 简单分析
其实根据我们上面的知识已经能自己制作一个饼状图了。不过制作东西最重要的不是制作结果,而是制作思路。
相信我贴上代码大家一看就立刻明白了,非常简单的东西。不过嘛,咱们还是想了解一下制作思路:
先分析饼状图的构成,非常明显,饼状图就是一个又一个的扇形构成的,每个扇形都有不同的颜色,对应的有名字,数据和百分比。
经以上信息可以得出饼状图的最基本数据应包括:<b>名字 数据值 百分比 对应的角度 颜色</b>。
<b>
用户关心的数据 : 名字 数据值 百分比<br/>
需要程序计算的数据: 百分比 对应的角度<br/>
其中颜色这一项可以用户指定也可以用程序指定(我们这里采用程序指定)。<br/>
</b>
### 封装数据:
``` java
public class PieData {
// 用户关心数据
private String name; // 名字
private float value; // 数值
private float percentage; // 百分比
// 非用户关心数据
private int color = 0; // 颜色
private float angle = 0; // 角度
public PieData(@NonNull String name, @NonNull float value) {
this.name = name;
this.value = value;
}
}
```
PS: 以上省略了get set方法
### 自定义View:
先按照自定义View流程梳理一遍(确定各个步骤应该做的事情):
| 步骤 | 关键字 | 作用 |
| :--: | ------------- | --------------------- |
| 1 | 构造函数 | 初始化(初始化画笔Paint) |
| 2 | onMeasure | 测量View的大小(暂时不用关心) |
| 3 | onSizeChanged | 确定View大小(记录当前View的宽高) |
| 4 | onLayout | 确定子View布局(无子View,不关心) |
| 5 | onDraw | 实际绘制内容(绘制饼状图) |
| 6 | 提供接口 | 提供接口(提供设置数据的接口) |
代码如下:
``` java
public class PieView extends View {
// 颜色表(注意: 此处定义颜色使用的是ARGB,带Alpha通道的)
private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
0xFFE6B800, 0xFF7CFC00};
// 饼状图初始绘制角度
private float mStartAngle = 0;
// 数据
private ArrayList<PieData> mData;
// 宽高
private int mWidth, mHeight;
// 画笔
private Paint mPaint = new Paint();
public PieView(Context context) {
this(context, null);
}
public PieView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null == mData)
return;
float currentStartAngle = mStartAngle; // 当前起始角度
canvas.translate(mWidth / 2, mHeight / 2); // 将画布坐标原点移动到中心位置
float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8); // 饼状图半径
RectF rect = new RectF(-r, -r, r, r); // 饼状图绘制区域
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
mPaint.setColor(pie.getColor());
canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint);
currentStartAngle += pie.getAngle();
}
}
// 设置起始角度
public void setStartAngle(int mStartAngle) {
this.mStartAngle = mStartAngle;
invalidate(); // 刷新
}
// 设置数据
public void setData(ArrayList<PieData> mData) {
this.mData = mData;
initData(mData);
invalidate(); // 刷新
}
// 初始化数据
private void initData(ArrayList<PieData> mData) {
if (null == mData || mData.size() == 0) // 数据有问题 直接返回
return;
float sumValue = 0;
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
sumValue += pie.getValue(); //计算数值和
int j = i % mColors.length; //设置颜色
pie.setColor(mColors[j]);
}
float sumAngle = 0;
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
float percentage = pie.getValue() / sumValue; // 百分比
float angle = percentage * 360; // 对应的角度
pie.setPercentage(percentage); // 记录百分比
pie.setAngle(angle); // 记录角度大小
sumAngle += angle;
Log.i("angle", "" + pie.getAngle());
}
}
}
```
**PS: 在更改了数据需要重绘界面时要调用invalidate()这个函数重新绘制。**
### 效果图
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f274gz06voj30u01hc3za.jpg" width = "300" />
> **PS: 这个饼状图并没有添加百分比等数据,仅作为示例使用。**
## 总结:
其实自定义View只要按照流程一步步的走,也是比较容易的。不过里面也有不少坑,这些坑还是自己踩过印象比较深,建议大家不要直接copy源码,自己手打体验一下。
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width=300/> </a>
## 参考资料:
[View](http://developer.android.com/reference/android/view/View.html)<br/>
[Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)<br/>
[Android Canvas绘图详解](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html)<br/>
<br/><br/><br/><br/><br/>
================================================
FILE: CustomView/Advance/[03]Canvas_Convert.md
================================================
# Canvas之画布操作
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md)
上一篇[Canvas之绘制基本形状](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B02%5DCanvas_BasicGraphics.md)中我们了解了如何使用Canvas绘制基本图形,本次了解一些基本的画布操作。
本来想把画布操作放到后面部分的,但是发现很多图形绘制都离不开画布操作,于是先讲解一下画布的基本操作方法。
## 一.Canvas的常用操作速查表
| 操作类型 | 相关API | 备注 |
| ---------- | ---------------------------------------- | ---------------------------------------- |
| 绘制颜色 | drawColor, drawRGB, drawARGB | 使用单一颜色填充整个画布 |
| 绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧 |
| 绘制图片 | drawBitmap, drawPicture | 绘制位图和图片 |
| 绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字 |
| 绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数 |
| 顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
| 画布剪裁 | clipPath, clipRect | 设置画布的显示区域 |
| 画布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数 |
| 画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切 |
| Matrix(矩阵) | getMatrix, setMatrix, concat | 实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。 |
******
## 二.Canvas基本操作
### 1.画布操作
#### 为什么要有画布操作?
画布操作可以帮助我们用更加容易理解的方式制作图形。
例如: 从坐标原点为起点,绘制一个长度为20dp,与水平线夹角为30度的线段怎么做?
按照我们通常的想法(*被常年训练出来的数学思维*),就是先使用三角函数计算出线段结束点的坐标,然后调用drawLine即可。
然而这是否是被固有思维禁锢了?
假设我们先绘制一个长度为20dp的水平线,然后将这条水平线旋转30度,则最终看起来效果是相同的,而且不用进行三角函数计算,这样是否更加简单了一点呢?
**合理的使用画布操作可以帮助你用更容易理解的方式创作你想要的效果,这也是画布操作存在的原因。**
**PS: 所有的画布操作都只影响后续的绘制,对之前已经绘制过的内容没有影响。**
*****
#### ⑴位移(translate)
translate是坐标系的移动,可以为图形绘制选择一个合适的坐标系。
**请注意,位移是基于当前位置移动,而不是每次基于屏幕左上角的(0,0)点移动**,如下:
``` java
// 省略了创建画笔的代码
// 在坐标原点绘制一个黑色圆形
mPaint.setColor(Color.BLACK);
canvas.translate(200,200);
canvas.drawCircle(0,0,100,mPaint);
// 在坐标原点绘制一个蓝色圆形
mPaint.setColor(Color.BLUE);
canvas.translate(200,200);
canvas.drawCircle(0,0,100,mPaint);
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f2f1ph46qaj30u01hcgm3.jpg" width="300"/>
我们首先将坐标系移动一段距离绘制一个圆形,之后再移动一段距离绘制一个圆形,<b>两次移动是可叠加的</b>。
*****
#### ⑵缩放(scale)
缩放提供了两个方法,如下:
``` java
public void scale (float sx, float sy)
public final void scale (float sx, float sy, float px, float py)
```
这两个方法中前两个参数是相同的分别为x轴和y轴的缩放比例。而第二种方法比前一种多了两个参数,用来控制缩放中心位置的。
缩放比例(sx,sy)取值范围详解:
| 取值范围(n) | 说明 |
| ----------- | ---------------------------------------------- |
| (-∞, -1) | 先根据缩放中心放大n倍,再根据中心轴进行翻转 |
| -1 | 根据缩放中心轴进行翻转 |
| (-1, 0) | 先根据缩放中心缩小到n,再根据中心轴进行翻转 |
| 0 | 不会显示,若sx为0,则宽度为0,不会显示,sy同理 |
| (0, 1) | 根据缩放中心缩小到n |
| 1 | 没有变化 |
| (1, +∞) | 根据缩放中心放大n倍 |
如果在缩放时稍微注意一下就会发现<b>缩放的中心默认为坐标原点,而缩放中心轴就是坐标轴</b>,如下:
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.scale(0.5f,0.5f); // 画布缩放
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
```
(为了更加直观,我添加了一个坐标系,可以比较明显的看出,缩放中心就是坐标原点)
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f2f1vphdjjj30u01hct9r.jpg" width="300" />
接下来我们使用第二种方法让缩放中心位置稍微改变一下,如下:
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.scale(0.5f,0.5f,200,0); // 画布缩放 <-- 缩放中心向右偏移了200个单位
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
```
(图中用箭头指示的就是缩放中心。)
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2f1w7kv8dj30u01hct9s.jpg" width="300" />
前面两个示例缩放的数值都是正数,按照表格中的说明,**当缩放比例为负数的时候会根据缩放中心轴进行翻转**,下面我们就来实验一下:
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.scale(-0.5f,-0.5f); // 画布缩放
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
```
<img src="http://ww1.sinaimg.cn/large/005Xtdi2jw1f2f1x76o6qj30u01hc0tu.jpg" width="300" />
> 为了效果明显,这次我不仅添加了坐标系而且对矩形中几个重要的点进行了标注,具有相同字母标注的点是一一对应的。
由于本次未对缩放中心进行偏移,所有默认的缩放中心就是坐标原点,中心轴就是x轴和y轴。
本次缩放可以看做是先根据缩放中心(坐标原点)缩放到原来的0.5倍,然后分别按照x轴和y轴进行翻转。
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.scale(-0.5f,-0.5f,200,0); // 画布缩放 <-- 缩放中心向右偏移了200个单位
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f2f1xth4p6j30u01hc0u4.jpg" width="300" />
> 添加了这么多的辅助内容,希望大家能够看懂。
本次对缩放中心点y轴坐标进行了偏移,故中心轴也向右偏移了。
<b>PS:和位移(translate)一样,缩放也是可以叠加的。</b>
``` java
canvas.scale(0.5f,0.5f);
canvas.scale(0.5f,0.1f);
```
调用两次缩放则 x轴实际缩放为0.5x0.5=0.25 y轴实际缩放为0.5x0.1=0.05
下面我们利用这一特性制作一个有趣的图形。
> 注意设置画笔模式为描边(STROKE)
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(-400,-400,400,400); // 矩形区域
for (int i=0; i<=20; i++)
{
canvas.scale(0.9f,0.9f);
canvas.drawRect(rect,mPaint);
}
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2f1yfn22xj30u01hcta9.jpg" width="300" />
*****
#### ⑶旋转(rotate)
旋转提供了两种方法:
``` java
public void rotate (float degrees)
public final void rotate (float degrees, float px, float py)
```
和缩放一样,第二种方法多出来的两个参数依旧是控制旋转中心点的。
默认的旋转中心依旧是坐标原点:
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.rotate(180); // 旋转180度 <-- 默认旋转中心为原点
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f2f1yws38nj30u01hcmy8.jpg" width="300" />
改变旋转中心位置:
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.rotate(180,200,0); // 旋转180度 <-- 旋转中心向右偏移200个单位
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f2f1zcmwb2j30u01hcmy9.jpg" width="300" />
<b>好吧,旋转也是可叠加的</b>
``` java
canvas.rotate(180);
canvas.rotate(20);
```
调用两次旋转,则实际的旋转角度为180+20=200度。
为了演示这一个效果,我做了一个不明觉厉的东西:
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
canvas.drawCircle(0,0,400,mPaint); // 绘制两个圆形
canvas.drawCircle(0,0,380,mPaint);
for (int i=0; i<=360; i+=10){ // 绘制圆形之间的连接线
canvas.drawLine(0,380,0,400,mPaint);
canvas.rotate(10);
}
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f2f1zsnj00j30u01hc75a.jpg" width="300" />
*****
#### ⑷错切(skew)
skew这里翻译为错切,错切是特殊类型的线性变换。
错切只提供了一种方法:
``` java
public void skew (float sx, float sy)
```
<b>参数含义:<br/>
float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值,<br/>
float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值.</b>
变换后:
```
X = x + sx * y
Y = sy * x + y
```
示例:
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,0,200,200); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.skew(1,0); // 水平错切 <- 45度
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2f20h7i23j30u01hcdgq.jpg" width="300" />
<b>如你所想,错切也是可叠加的,不过请注意,调用次序不同绘制结果也会不同</b>
``` java
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,0,200,200); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.skew(1,0); // 水平错切
canvas.skew(0,1); // 垂直错切
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f2f20w0rffj30u01hcgm8.jpg" width="300" />
*****
#### ⑸快照(save)和回滚(restore)
<b>
Q: 为什么存在快照与回滚<br/>
A:画布的操作是不可逆的,而且很多画布操作会影响后续的步骤,例如第一个例子,两个圆形都是在坐标原点绘制的,而因为坐标系的移动绘制出来的实际位置不同。所以会对画布的一些状态进行保存和回滚。
</b>
<b>与之相关的API:</b>
| 相关API | 简介 |
| -------------- | ------------------------------ |
| save | 把当前的画布的状态进行保存,然后放入特定的栈中 |
| saveLayerXxx | 新建一个图层,并放入特定的栈中 |
| restore | 把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布 |
| restoreToCount | 弹出指定位置及其以上所有的状态,并按照指定位置的状态进行恢复 |
| getSaveCount | 获取栈中内容的数量(即保存次数) |
下面对其中的一些概念和方法进行分析:
##### 状态栈:
其实这个栈我也不知道叫什么名字,暂时叫做状态栈吧,它看起来像下面这样:

这个栈可以存储画布状态和图层状态。
<b>Q:什么是画布和图层?<br/>
A:实际上我们看到的画布是由多个图层构成的,如下图(图片来自网络):<br/>

实际上我们之前讲解的绘制操作和画布操作都是在默认图层上进行的。<br/>
在通常情况下,使用默认图层就可满足需求,但是如果需要绘制比较复杂的内容,如地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)等,则分图层绘制比较好一些。<br/>
你可以把这些图层看做是一层一层的玻璃板,你在每层的玻璃板上绘制内容,然后把这些玻璃板叠在一起看就是最终效果。
</b>
##### SaveFlags
| 数据类型 | 名称 | 简介 |
| ---- | -------------------------- | ---------------------------------------- |
| int | ALL_SAVE_FLAG | 默认,保存全部状态 |
| int | CLIP_SAVE_FLAG | 保存剪辑区 |
| int | CLIP_TO_LAYER_SAVE_FLAG | 剪裁区作为图层保存 |
| int | FULL_COLOR_LAYER_SAVE_FLAG | 保存图层的全部色彩通道 |
| int | HAS_ALPHA_LAYER_SAVE_FLAG | 保存图层的alpha(不透明度)通道 |
| int | MATRIX_SAVE_FLAG | 保存Matrix信息(translate, rotate, scale, skew) |
##### save
save 有两种方法:
``` java
// 保存全部状态
public int save ()
// 根据saveFlags参数保存一部分状态
public int save (int saveFlags)
```
可以看到第二种方法比第一种多了一个saveFlags参数,使用这个参数可以只保存一部分状态,更加灵活,这个saveFlags参数具体可参考上面表格中的内容。
每调用一次save方法,都会在栈顶添加一条状态信息,以上面状态栈图片为例,再调用一次save则会在第5次上面载添加一条状态。
#### saveLayerXxx
saveLayerXxx有比较多的方法:
``` java
// 无图层alpha(不透明度)通道
public int saveLayer (RectF bounds, Paint paint)
public int saveLayer (RectF bounds, Paint paint, int saveFlags)
public int saveLayer (float left, float top, float right, float bottom, Paint paint)
public int saveLayer (float left, float top, float right, float bottom, Paint paint, int saveFlags)
// 有图层alpha(不透明度)通道
public int saveLayerAlpha (RectF bounds, int alpha)
public int saveLayerAlpha (RectF bounds, int alpha, int saveFlags)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha, int saveFlags)
```
<b>注意:saveLayerXxx方法会让你花费更多的时间去渲染图像(图层多了相互之间叠加会导致计算量成倍增长),使用前请谨慎,如果可能,尽量避免使用。</b>
使用saveLayerXxx方法,也会将图层状态也放入状态栈中,同样使用restore方法进行恢复。
这个暂时不过多讲述,如果以后用到详细讲解。(因为这里面东西也有不少啊QAQ)
##### restore
状态回滚,就是从栈顶取出一个状态然后根据内容进行恢复。
同样以上面状态栈图片为例,调用一次restore方法则将状态栈中第5次取出,根据里面保存的状态进行状态恢复。
##### restoreToCount
弹出指定位置以及以上所有状态,并根据指定位置状态进行恢复。
以上面状态栈图片为例,如果调用restoreToCount(2) 则会弹出 2 3 4 5 的状态,并根据第2次保存的状态进行恢复。
##### getSaveCount
获取保存的次数,即状态栈中保存状态的数量,以上面状态栈图片为例,使用该函数的返回值为5。
不过请注意,该函数的最小返回值为1,即使弹出了所有的状态,返回值依旧为1,代表默认状态。
##### 常用格式
虽然关于状态的保存和回滚啰嗦了不少,不过大多数情况下只需要记住下面的步骤就可以了:
``` java
save(); //保存状态
... //具体操作
restore(); //回滚到之前的状态
```
这种方式也是最简单和最容易理解的使用方法。
******
## 三.总结
如本文一开始所说,合理的使用画布操作可以帮助你用更容易理解的方式创作你想要的效果。
(,,• ₃ •,,)
<b>PS: 由于本人英文水平有限,某些地方可能存在误解或词语翻译不准确,如果你对此有疑问可以提交Issues进行反馈。</b>
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300"/> </a>
******
## 四.参考资料
[Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)<br/>
[canvas变换与操作](http://blog.csdn.net/harvic880925/article/details/39080931)<br/>
[Canvas之translate、scale、rotate、skew方法讲解](http://blog.csdn.net/tianjian4592/article/details/45234419)<br/>
[Canvas的save(),saveLayer()和restore()浅谈](http://www.cnblogs.com/liangstudyhome/p/4143498.html)<br/>
[Graphics->Layers](http://www.programgo.com/article/72302404062/;jsessionid=8E62016408BFFB21D46F9C878A49D8EE)<br/>
[]()<br/>
[]()<br/>
================================================
FILE: CustomView/Advance/[04]Canvas_PictureText.md
================================================
# Canvas之图片文字
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md)
在上一篇文章[Canvas之画布操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B03%5DCanvas_Convert.md)中我们了解了画布的一些基本操作方法,本次了解一些绘制图片文字相关的内容。如果你对前几篇文章讲述的内容熟练掌握的话,那么恭喜你,本篇结束之后,大部分的自定义View已经难不倒你了,当然了,这并不是终点,接下来还会有更加炫酷的技能。
## 一.Canvas的常用操作速查表
| 操作类型 | 相关API | 备注 |
| ---------- | ---------------------------------------- | ---------------------------------------- |
| 绘制颜色 | drawColor, drawRGB, drawARGB | 使用单一颜色填充整个画布 |
| 绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧 |
| 绘制图片 | drawBitmap, drawPicture | 绘制位图和图片 |
| 绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字 |
| 绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数 |
| 顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
| 画布剪裁 | clipPath, clipRect | 设置画布的显示区域 |
| 画布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数 |
| 画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切 |
| Matrix(矩阵) | getMatrix, setMatrix, concat | 实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。 |
******
# 二.Canvas基本操作详解
## 1.绘制图片
绘制有两种方法,drawPicture(矢量图) 和 drawBitmap(位图),接下来我们一一了解。
### (1)drawPicture
**使用Picture前请关闭硬件加速,以免引起不必要的问题!<br/>使用Picture前请关闭硬件加速,以免引起不必要的问题!<br/>使用Picture前请关闭硬件加速,以免引起不必要的问题!**
**在AndroidMenifest文件中application节点下添上 android:hardwareAccelerated="false"以关闭整个应用的硬件加速。 <br/>更多请参考这里:[Android的硬件加速及可能导致的问题](https://github.com/GcsSloop/AndroidNote/issues/7)**
关于drawPicture一开始还是挺让人费解的,不过嘛,我们接下来慢慢研究一下它的用途。
既然是drawPicture就要了解一下什么是Picture。 顾名思义,Picture的意思是图片。
不过嘛,我觉得这么用图片这个名词解释Picture是不合适的,为何这么说?请看其官方文档对[Picture](http://developer.android.com/reference/android/graphics/Picture.html)的解释:
<i>
A Picture records drawing calls (via the canvas returned by beginRecording) and can then play them back into Canvas (via draw(Canvas) or drawPicture(Picture)).For most content (e.g. text, lines, rectangles), drawing a sequence from a picture can be faster than the equivalent API calls, since the picture performs its playback without incurring any method-call overhead.
</i>
好吧,我知道很多人对这段鸟语是看不懂的,至于为什么要放在这里,仅仅是为了显得更加专业(偷笑)。
**下面我就对这段不明觉厉的鸟语用通俗的话翻译一下:**
某一天小萌想在朋友面前显摆一下,于是在单杠上来了一个后空翻,动作姿势请参照下图:
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f2kua0sxg0j30bo0b4aba.jpg" width=300 />
朋友都说 恩,很不错。 想再看一遍 (〃ω〃)。ヽ(〃∀〃)ノ。⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
于是小萌又来了一遍,如是几次之后,小萌累的吐血三升。
于是小萌机智的想,我何不能用手机将我是飒爽英姿录下来呢,直接保存成为**后空翻.avi** 下次想显摆的时候直接拿出手机,点一下播放就行了,省时省力。
小萌被自己的机智深深的折服了,然后Picture就诞生啦。(╯‵□′)╯︵┻━┻掀桌,坑爹呢,这剧情跳跃也忒大了吧。
**好吧,言归正传,这次我们了解的Picture和上文中的录像功能是类似的,只不过我们Picture录的是Canvas中绘制的内容。**
我们把Canvas绘制点,线,矩形等诸多操作用Picture录制下来,下次需要的时候拿来就能用,使用Picture相比于再次调用绘图API,开销是比较小的,也就是说对于重复的操作可以更加省时省力。
**PS:你可以把Picture看作是一个录制Canvas操作的录像机。**
了解了Picture的概念之后,我们再了解一下Picture的相关方法。
| 相关方法 | 简介 |
| ---------------------------------------- | ---------------------------------------- |
| public int getWidth () | 获取宽度 |
| public int getHeight () | 获取高度 |
| public Canvas beginRecording (int width, int height) | 开始录制 (返回一个Canvas,在Canvas中所有的绘制都会存储在Picture中) |
| public void endRecording () | 结束录制 |
| public void draw (Canvas canvas) | 将Picture中内容绘制到Canvas中 |
| public static Picture createFromStream (InputStream stream) | (已废弃)通过输入流创建一个Picture |
| public void writeToStream (OutputStream stream) | (已废弃)将Picture中内容写出到输出流中 |
上面表格中基本上已经列出了Picture的所有方法,其中getWidth和getHeight没什么好说的,最后两个已经废弃也自然就不用关注了,排除了这些方法之后,只剩三个方法了,接下来我们就比较详细的了解一下:
**很明显,beginRecording 和 endRecording 是成对使用的,一个开始录制,一个是结束录制,两者之间的操作将会存储在Picture中。**
#### 使用示例:
**准备工作:**
录制内容,即将一些Canvas操作用Picture存储起来,录制的内容是不会直接显示在屏幕上的,只是存储起来了而已。
``` java
// 1.创建Picture
private Picture mPicture = new Picture();
---------------------------------------------------------------
// 2.录制内容方法
private void recording() {
// 开始录制 (接收返回值Canvas)
Canvas canvas = mPicture.beginRecording(500, 500);
// 创建一个画笔
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.FILL);
// 在Canvas中具体操作
// 位移
canvas.translate(250,250);
// 绘制一个圆
canvas.drawCircle(0,0,100,paint);
mPicture.endRecording();
}
---------------------------------------------------------------
// 3.在使用前调用(我在构造函数中调用了)
public Canvas3(Context context, AttributeSet attrs) {
super(context, attrs);
recording(); // 调用录制
}
```
**具体使用:**
Picture虽然方法就那么几个,但是具体使用起来还是分很多情况的,由于录制的内容不会直接显示,就像存储的视频不点击播放不会自动播放一样,同样,想要将Picture中的内容显示出来就需要手动调用播放(绘制),将Picture中的内容绘制出来可以有以下几种方法:
| 序号 | 简介 |
| ---- | ---------------------------------------- |
| 1 | 使用Picture提供的draw方法绘制。 |
| 2 | 使用Canvas提供的drawPicture方法绘制。 |
| 3 | 将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。 |
以上几种方法主要区别:
| 主要区别 | 分类 | 简介 |
| ------------ | --------------------- | ------------------------------------ |
| 是否对Canvas有影响 | 1有影响<br/>2,3不影响 | 此处指绘制完成后是否会影响Canvas的状态(Matrix clip等) |
| 可操作性强弱 | 1可操作性较弱<br/>2,3可操作性较强 | 此处的可操作性可以简单理解为对绘制结果可控程度。 |
几种方法简介和主要区别基本就这么多了,接下来对于各种使用方法一一详细介绍:
**1.使用Picture提供的draw方法绘制:**
``` java
// 将Picture中的内容绘制在Canvas上
mPicture.draw(canvas);
```
<img src="http://ww1.sinaimg.cn/large/005Xtdi2jw1f2kwz9956lj30u01hcdg9.jpg" width = "300" />
**PS:这种方法在比较低版本的系统上绘制后可能会影响Canvas状态,所以这种方法一般不会使用。**
**2.使用Canvas提供的drawPicture方法绘制**
drawPicture有三种方法:
``` java
public void drawPicture (Picture picture)
public void drawPicture (Picture picture, Rect dst)
public void drawPicture (Picture picture, RectF dst)
```
和使用Picture的draw方法不同,Canvas的drawPicture不会影响Canvas状态。
**简单示例:**
``` java
canvas.drawPicture(mPicture,new RectF(0,0,mPicture.getWidth(),200));
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f2kwzseqawj30u01hc74o.jpg" width = "300"/>
**PS:对照上一张图片,可以比较明显的看出,绘制的内容根据选区进行了缩放。 **
**3.将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。**
```
// 包装成为Drawable
PictureDrawable drawable = new PictureDrawable(mPicture);
// 设置绘制区域 -- 注意此处所绘制的实际内容不会缩放
drawable.setBounds(0,0,250,mPicture.getHeight());
// 绘制
drawable.draw(canvas);
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f2kx0bquw3j30u01hcdg8.jpg" width = "300" />
**PS:此处setBounds是设置在画布上的绘制区域,并非根据该区域进行缩放,也不是剪裁Picture,每次都从Picture的左上角开始绘制。**
> **注意:在使用Picture之前请关闭硬件加速,以免引起不必要的问题,如何关闭请参考这里: [Android的硬件加速及可能导致的问题](https://github.com/GcsSloop/AndroidNote/issues/7)**
### (2)drawBitmap
> 其实一开始知道要讲Bitmap我是拒绝的,为什么呢?因为Bitmap就是很多问题的根源啊有木有,Bitmap可能导致内存不足,内存泄露,ListView中的复用混乱等诸多问题。想完美的掌控Bitmap还真不是一件容易的事情。限于篇幅**本文对于Bitmap不会过多的展开,只讲解一些常用的功能**,关于Bitmap详细内容,以后开专题讲解QAQ。
既然要绘制Bitmap,就要先获取一个Bitmap,那么如何获取呢?
**获取Bitmap方式:**
| 序号 | 获取方式 | 备注 |
| ---- | ------------------ | ---------------------------------------- |
| 1 | 通过Bitmap创建 | 复制一个已有的Bitmap(_新Bitmap状态和原有的一致_) 或者 创建一个空白的Bitmap(_内容可改变_) |
| 2 | 通过BitmapDrawable获取 | 从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap |
| 3 | 通过BitmapFactory获取 | 从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap |
**通常来说,我们绘制Bitmap都是读取已有的图片转换为Bitmap绘制到Canvas上。**<br/>
很明显,第1种方式不能满足我们的要求,暂时排除。<br/>
第2种方式虽然也可满足我们的要求,但是我不推荐使用这种方式,至于为什么在后续详细讲解Drawable的时候会说明,暂时排除。<br/>
第3种方法我们会比较详细的说明一下如何从各个位置获取图片。<br/>
#### 通过BitmapFactory从不同位置获取Bitmap:
**资源文件(drawable/mipmap/raw):**
``` java
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),R.raw.bitmap);
```
**资源文件(assets):**
``` java
Bitmap bitmap=null;
try {
InputStream is = mContext.getAssets().open("bitmap.png");
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
```
**内存卡文件:**
``` java
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/bitmap.png");
```
**网络文件:**
``` java
// 此处省略了获取网络输入流的代码
Bitmap bitmap = BitmapFactory.decodeStream(is);
is.close();
```
既然已经获得到了Bitmap,那么就开始本文的重点了,将Bitmap绘制到画布上。
#### 绘制Bitmap:
依照惯例先预览一下drawBitmap的常用方法:
``` java
// 第一种
public void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint)
// 第二种
public void drawBitmap (Bitmap bitmap, float left, float top, Paint paint)
// 第三种
public void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
```
第一种方法中后两个参数(matrix, paint)是在绘制的时候对图片进行一些改变,如果只是需要将图片内容绘制出来只需要如下操作就可以了:
PS:图片左上角位置默认为坐标原点。
``` java
canvas.drawBitmap(bitmap,new Matrix(),new Paint());
```
> 关于Matrix和Paint暂时略过吧,一展开又是啰啰嗦嗦一大段,反正挖坑已经是常态了,大家应该也习惯了(PAP).
<img src="http://ww3.sinaimg.cn/large/005Xtdi2gw1f4ixnjrn83j30u01hcabc.jpg" width = "300" />
第二种方法就是在绘制时指定了图片左上角的坐标(距离坐标原点的距离):
> **注意:此处指定的是与坐标原点的距离,并非是与屏幕顶部和左侧的距离, 虽然默认状态下两者是重合的,但是也请注意分别两者的不同。**
``` java
canvas.drawBitmap(bitmap,200,500,new Paint());
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2gw1f4ixoug2x8j30u01hcgn4.jpg" width = "300" />
第三种方法比较有意思,上面多了两个矩形区域(src,dst),这两个矩形选区是干什么用的?
| 名称 | 作用 |
| ------------------- | ----------------- |
| Rect src | 指定绘制图片的区域 |
| Rect dst 或RectF dst | 指定图片在屏幕上显示(绘制)的区域 |
示例:
``` java
// 将画布坐标系移动到画布中央
canvas.translate(mWidth/2,mHeight/2);
// 指定图片绘制区域(左上角的四分之一)
Rect src = new Rect(0,0,bitmap.getWidth()/2,bitmap.getHeight()/2);
// 指定图片在屏幕上显示的区域
Rect dst = new Rect(0,0,200,400);
// 绘制图片
canvas.drawBitmap(bitmap,src,dst,null);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2gw1f4ixqgk8rwj30u01hc756.jpg" width = "300" />
**详解:**
上面是以绘制该图为例,用src指定了图片绘制部分的区域,即下图中红色方框标注的区域。

然后用dst指定了绘制在屏幕上的绘制,即下图中蓝色方框标注的区域,图片宽高会根据指定的区域自动进行缩放。
<img src="http://ww2.sinaimg.cn/large/005Xtdi2gw1f4ixr3skcjj30u01hc3za.jpg" width = "300" />
从上面可知,第三种方法可以绘制图片的一部分到画布上,这有什么用呢?
如果你看过某些游戏的资源文件,你可能会看到如下的图片(图片来自网络):

用一张图片包含了大量的素材,在绘制的时候每次只截取一部分进行绘制,这样可以大大的减少素材数量,而且素材管理起来也很方便。
然而这和我们有什么关系呢?我们又不做游戏开发。
确实,我们不做游戏开发,但是在某些时候我们需要制作一些炫酷的效果,这些效果因为太复杂了用代码很难实现或者渲染效率不高。这时候很多人就会想起帧动画,将动画分解成一张一张的图片然后使用帧动画制作出来,这种实现方式的确比较简单,但是一个动画效果的图片有十几到几十张,一个应用里面来几个这样炫酷的动画效果就会导致资源文件出现一大堆,想找其中的某一张资源图片简直就是灾难啊有木有。但是把同一个动画效果的所有资源图片整理到一张图片上,会大大的**减少资源文件数量,方便管理**,妈妈再也不怕我找不到资源文件了,**同时也节省了图片文件头、文件结束块以及调色板等占用的空间。**
**下面是利用drawBitmap第三种方法制作的一个简单示例:**
资源文件如下:

最终效果如下:

源码如下:
> PS:由于是示例代码,做的很粗糙,仅作为学习示例,不建议在任何实际项目中使用。
[_点击此处查看源码_](https://github.com/GcsSloop/AndroidNote/issues/10)
## 2.绘制文字
依旧预览一下相关常用方法:
``` java
// 第一类
public void drawText (String text, float x, float y, Paint paint)
public void drawText (String text, int start, int end, float x, float y, Paint paint)
public void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)
public void drawText (char[] text, int index, int count, float x, float y, Paint paint)
// 第二类
public void drawPosText (String text, float[] pos, Paint paint)
public void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)
// 第三类
public void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint)
public void drawTextOnPath (char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)
```
> PS 其中的CharSequence和String的区别可以到这里看看. [->戳这里<-](https://github.com/GcsSloop/AndroidNote/issues/16)
绘制文字部分大致可以分为三类:
第一类只能指定文本基线位置(基线x默认在字符串左侧,基线y默认在字符串下方)。<br/>
第二类可以分别指定每个文字的位置。<br/>
第三类是指定一个路径,根据路径绘制文字。<br/>
通过上面常用方法的参数也可看出,绘制文字也是需要画笔的,而且文字的大小,颜色,字体,对齐方式都是由画笔控制的。
不过嘛这里仅简单介绍几种常用方法(反正挖坑多了也不怕),具体在讲解Paint时再详细讲解。
**Paint文本相关常用方法表**
| 标题 | 相关方法 | 备注 |
| ---- | ------------------------- | ---------------------------------------- |
| 色彩 | setColor setARGB setAlpha | 设置颜色,透明度 |
| 大小 | setTextSize | 设置文本字体大小 |
| 字体 | setTypeface | 设置或清除字体样式 |
| 样式 | setStyle | 填充(FILL),描边(STROKE),填充加描边(FILL_AND_STROKE) |
| 对齐 | setTextAlign | 左对齐(LEFT),居中对齐(CENTER),右对齐(RIGHT) |
| 测量 | measureText | 测量文本大小(注意,请在设置完文本各项参数后调用) |
为了绘制文本,我们先创建一个文本画笔:
``` java
Paint textPaint = new Paint(); // 创建画笔
textPaint.setColor(Color.BLACK); // 设置颜色
textPaint.setStyle(Paint.Style.FILL); // 设置样式
textPaint.setTextSize(50); // 设置字体大小
```
### 第一类(drawText)
第一类可以指定文本开始的位置,可以截取文本中部分内容进行绘制。
其中x,y两个参数是指定文本绘制两个基线,示例:
``` java
// 文本(要绘制的内容)
String str = "ABCDEFGHIJK";
// 参数分别为 (文本 基线x 基线y 画笔)
canvas.drawText(str,200,500,textPaint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f3njfsi0l4j30dw0nuwey.jpg" width = "300" />
> PS: 图中字符串下方的红线是基线y,基线x未在图中画出。
当然啦,除了能指定绘制文本的起始位置,还能只取出文本中的一部分内容进行绘制。
截取文本中的一部分,对于String和CharSequence来说只指定字符串下标start和end位置(**注意:0<= start < end < str.length()**)
以上一个例子使用的字符串为例,它的下标是这样的(wait,我为啥要说这个,算了,不管了,就这样吧(๑•́ ₃ •̀๑)):
| 字符 | A | B | C | D | E | F | G | H | I | J | K |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
假设我们指定start为1,end为3,那么最终截取的字符串就是"BC"。
一般来说,**使用start和end指定的区间是前闭后开的,即包含start指定的下标,而不包含end指定的下标**,故[1,3)最后获取到的下标只有 下标1 和 下标2 的字符,就是"BC"。
示例:
``` java
// 文本(要绘制的内容)
String str = "ABCDEFGHIJK";
// 参数分别为 (字符串 开始截取位置 结束截取位置 基线x 基线y 画笔)
canvas.drawText(str,1,3,200,500,textPaint);
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2gw1f3njh66018j30dw0nuq3b.jpg" width = "300" />
另外,对于字符数组char[]我们截取字符串使用起始位置(index)和长度(count)来确定。
同样,我们指定index为1,count为3,那么最终截取到的字符串是"BCD"。
其实就是从下标位置为1处向后数3位就是截取到的字符串,示例:
``` java
// 字符数组(要绘制的内容)
char[] chars = "ABCDEFGHIJK".toCharArray();
// 参数为 (字符数组 起始坐标 截取长度 基线x 基线y 画笔)
canvas.drawText(chars,1,3,200,500,textPaint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f3njhnldb5j30dw0nu74o.jpg" width = "300" />
### 第二类(drawPosText)
通过和第一类比较,我们可以发现,第二类中没有指定x,y坐标的参数,而是出现了这样一个参数**float[] pos**。
好吧,这个名为pos的浮点型数组就是指定坐标的,至于为啥要用数组嘛,因为这家伙野心比较大,想给每个字符都指定一个位置。
示例:
``` java
String str = "SLOOP";
canvas.drawPosText(str,new float[]{
100,100, // 第一个字符位置
200,200, // 第二个字符位置
300,300, // ...
400,400,
500,500
},textPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f2kx9fl8c8j30u01hcglz.jpg" width = "300" />
不过嘛,虽然虽然这个方法也比较容易理解,但是关于这个方法我个人是不推荐使用的,因为坑比较多,主要有一下几点:
| 序号 | 反对理由 |
| ---- | --------------------------- |
| 1 | 必须指定所有字符位置,否则直接crash掉,反人类设计 |
| 2 | 性能不佳,在大量使用的时候可能导致卡顿 |
| 3 | 不支持emoji等特殊字符,不支持字形组合与分解 |
关于第二类的第二种方法:
``` java
public void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)
```
和上面一样,就是从字符数组中切出来一段进行绘制,相信以诸位看官的聪明才智一眼就看出来了,我这里就不多说了,真的不是我偷懒啊(ˉ▽ ̄~) ~~
### 第三类(drawTextOnPath)
第三类要用到path这个大杀器,作为一个厉害角色怎么能这么轻易露脸呢,先保持一下神秘感,也就是说,下回再和大家见面喽。
# 三.总结
学会了图片和文字绘制,对于大部分自定义View都能制作了,可以去看看这位大神制作的作品,尝试模仿一下[一个绚丽的loading动效分析与实现!](http://blog.csdn.net/tianjian4592/article/details/44538605)

(,,• ₃ •,,)
<b>PS: 由于本人英文水平有限,某些地方可能存在误解或词语翻译不准确,如果你对此有疑问可以提交Issues进行反馈。</b>
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width=300/> </a>
## 参考资料
[Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)<br/>
[Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html)<br/>
[Paint](http://developer.android.com/reference/android/graphics/Paint.html)<br/>
[Android ApiDemo 笔记(一)Content与Graphics](http://blog.csdn.net/wufenglong/article/details/5596402)<br/>
================================================
FILE: CustomView/Advance/[05]Path_Basic.md
================================================
# Path之基本操作
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md)
在上一篇[Canvas之图片文字](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B04%5DCanvas_PictureText.md)中我们了解了如何使用Canvas中绘制图片文字,结合前几篇文章,Canvas的基本操作已经差不多完结了,然而Canvas不仅仅具有这些基本的操作,还可以更加炫酷,本次会了解到path(路径)这个Canvas中的神器,有了这个神器,就能创造出更多**炫(zhuang)酷(B)**的东东了。
******
# 一.Path常用方法表
> 为了兼容性(_偷懒_) 本表格中去除了部分API21(即安卓版本5.0)以上才添加的方法。
| 作用 | 相关方法 | 备注 |
| ----------- | ---------------------------------------- | ---------------------------------------- |
| 移动起点 | moveTo | 移动下一次操作的起点位置 |
| 设置终点 | setLastPoint | 重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveTo相同 |
| 连接直线 | lineTo | 添加上一个点到当前点之间的直线到Path |
| 闭合路径 | close | 连接第一个点连接到最后一个点,形成一个闭合区域 |
| 添加内容 | addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo | 添加(矩形, 圆角矩形, 椭圆, 圆, 路径, 圆弧) 到当前Path (注意addArc和arcTo的区别) |
| 是否为空 | isEmpty | 判断Path是否为空 |
| 是否为矩形 | isRect | 判断path是否是一个矩形 |
| 替换路径 | set | 用新的路径替换到当前路径所有内容 |
| 偏移路径 | offset | 对当前路径之前的操作进行偏移(不会影响之后的操作) |
| 贝塞尔曲线 | quadTo, cubicTo | 分别为二次和三次贝塞尔曲线的方法 |
| rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | **不带r的方法是基于原点的坐标系(偏移量), rXxx方法是基于当前点坐标系(偏移量)** |
| 填充模式 | setFillType, getFillType, isInverseFillType, toggleInverseFillType | 设置,获取,判断和切换填充模式 |
| 提示方法 | incReserve | 提示Path还有多少个点等待加入**(这个方法貌似会让Path优化存储结构)** |
| 布尔操作(API19) | op | 对两个Path进行布尔运算(即取交集、并集等操作) |
| 计算边界 | computeBounds | 计算Path的边界 |
| 重置路径 | reset, rewind | 清除Path中的内容<br/> **reset不保留内部数据结构,但会保留FillType.**<br/> **rewind会保留内部的数据结构,但不保留FillType** |
| 矩阵操作 | transform | 矩阵变换 |
# 二.Path详解
**请关闭硬件加速,以免引起不必要的问题!<br/>请关闭硬件加速,以免引起不必要的问题!<br/>请关闭硬件加速,以免引起不必要的问题!**
**在AndroidMainfest文件中application节点下添上 android:hardwareAccelerated="false"以关闭整个应用的硬件加速。 <br/>更多请参考这里:[Android的硬件加速及可能导致的问题](https://github.com/GcsSloop/AndroidNote/issues/7)**
## Path作用
本次特地开了一篇详细讲解Path,为什么要单独摘出来呢,这是因为Path在2D绘图中是一个很重要的东西。
在前面我们讲解的所有绘制都是简单图形(如 矩形 圆 圆弧等),而对于那些复杂一点的图形则没法去绘制(如绘制一个心形 正多边形 五角星等),而**使用Path不仅能够绘制简单图形,也可以绘制这些比较复杂的图形。另外,根据路径绘制文本和剪裁画布都会用到Path。**
关于Path的作用先简单地说这么多,具体的我们接下来慢慢研究。
## Path含义
**官方介绍:**
_The Path class encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves. It can be drawn with canvas.drawPath(path, paint), either filled or stroked (based on the paint's Style), or it can be used for clipping or to draw text on a path._
> 嗯,没错依旧是拿来装逼的,如果你看不懂的话,不用担心,其实并没有什么卵用。
**通俗解释(sloop个人版):**
**Path封装了由直线和曲线(二次,三次贝塞尔曲线)构成的几何路径。你能用Canvas中的drawPath来把这条路径画出来(同样支持Paint的不同绘制模式),也可以用于剪裁画布和根据路径绘制文字。我们有时会用Path来描述一个图像的轮廓,所以也会称为轮廓线(轮廓线仅是Path的一种使用方法,两者并不等价)**
另外路径有开放和封闭的区别。
| 图像 | 名称 | 备注 |
| ------------------------------------------------------------ | -------- | -------------------------- |
|  | 封闭路径 | 首尾相接形成了一个封闭区域 |
|  | 开放路径 | 没有首尾相接形成封闭区域 |
> 这个是我随便画的,仅为展示一下区别,请无视我灵魂画师一般的绘图水准。
**与Path相关的还有一些比较神奇的概念,不过暂且不说,等接下来需要用到的时候再详细说明。**
## Path使用方法详解
前面扯了一大堆概念性的东西。接下来就开始实战了,请诸位看官准备好瓜子、花生、爆米花,坐下来慢慢观看。

### 第1组: moveTo、 setLastPoint、 lineTo 和 close
由于Path的有些知识点无法单独来讲,所以本次采取了一次讲一组方法。
按照惯例,先创建画笔:
``` java
Paint mPaint = new Paint(); // 创建画笔
mPaint.setColor(Color.BLACK); // 画笔颜色 - 黑色
mPaint.setStyle(Paint.Style.STROKE); // 填充模式 - 描边
mPaint.setStrokeWidth(10); // 边框宽度 - 10
```
#### lineTo:
方法预览:
```
public void lineTo (float x, float y)
```
首先讲解的的LineTo,为啥先讲解这个呢?
是因为moveTo、 setLastPoint、 close都无法直接看到效果,借助有具现化效果的lineTo才能让这些方法现出原形。
lineTo很简单,只有一个方法,作用也很容易理解,line嘛,顾名思义就是一条线。
俗话(数学书上)说,两点确定一条直线,但是看参数明显只给了一个点的坐标吧(这不按常理出牌啊)。
再仔细一看,这个lineTo除了line外还有一个to呢,to翻译过来就是“至”,到某个地方的意思,**lineTo难道是指从某个点到参数坐标点之间连一条线?**
没错,你猜对了,但是这某个点又是哪里呢?
前面我们提到过Path可以用来描述一个图像的轮廓,图像的轮廓通常都是用一条线构成的,所以这里的某个点就是上次操作结束的点,如果没有进行过操作则默认点为坐标原点。
那么我们就来试一下:
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心(宽高数据在onSizeChanged中获取)
Path path = new Path(); // 创建Path
path.lineTo(200, 200); // lineTo
path.lineTo(200,0);
canvas.drawPath(path, mPaint); // 绘制Path
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f1ap1tu0w9j30u01hcjse.jpg" width = "270" height = "480"/>
在示例中我们调用了两次lineTo,**第一次由于之前没有过操作,所以默认点就是坐标原点O,结果就是坐标原点O到A(200,200)之间连直线(用蓝色圈1标注)。**
**第二次lineTo的时候,由于上次的结束位置是A(200,200),所以就是A(200,200)到B(200,0)之间的连线(用蓝色圈2标注)。**
#### moveTo 和 setLastPoint:
方法预览:
``` java
// moveTo
public void moveTo (float x, float y)
// setLastPoint
public void setLastPoint (float dx, float dy)
```
这两个方法虽然在作用上有相似之处,但实际上却是完全不同的两个东东,具体参照下表:
| 方法名 | 简介 | 是否影响之前的操作 | 是否影响之后操作 |
| ------------ | -------------- | --------- | -------- |
| moveTo | 移动下一次操作的起点位置 | 否 | 是 |
| setLastPoint | 设置之前操作的最后一个点位置 | 是 | 是 |
废话不多说,直接上代码:
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
Path path = new Path(); // 创建Path
path.lineTo(200, 200); // lineTo
path.moveTo(200,100); // moveTo
path.lineTo(200,0); // lineTo
canvas.drawPath(path, mPaint); // 绘制Path
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2gw1f1aqjptdtjj30u01hct9t.jpg" width = "270" height = "480"/>
这个和上面演示lineTo的方法类似,只不过在两个lineTo之间添加了一个moveTo。
**moveTo只改变下次操作的起点,在执行完第一次LineTo的时候,本来的默认点位置是A(200,200),但是moveTo将其改变成为了C(200,100),所以在第二次调用lineTo的时候就是连接C(200,100) 到 B(200,0) 之间的直线(用蓝色圈2标注)。**
下面是setLastPoint的示例:
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
Path path = new Path(); // 创建Path
path.lineTo(200, 200); // lineTo
path.setLastPoint(200,100); // setLastPoint
path.lineTo(200,0); // lineTo
canvas.drawPath(path, mPaint); // 绘制Path
```
<img src="http://ww1.sinaimg.cn/large/005Xtdi2gw1f1ari1l9g8j30u01hcab5.jpg" width = "270" height = "480"/>
**setLastPoint是重置上一次操作的最后一个点,在执行完第一次的lineTo的时候,最后一个点是A(200,200),而setLastPoint更改最后一个点为C(200,100),所以在实际执行的时候,第一次的lineTo就不是从原点O到A(200,200)的连线了,而变成了从原点O到C(200,100)之间的连线了。**
**在执行完第一次lineTo和setLastPoint后,最后一个点的位置是C(200,100),所以在第二次调用lineTo的时候就是C(200,100) 到 B(200,0) 之间的连线(用蓝色圈2标注)。**
#### close
方法预览:
``` java
public void close ()
```
close方法用于连接当前最后一个点和最初的一个点(如果两个点不重合的话),最终形成一个封闭的图形。
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
Path path = new Path(); // 创建Path
path.lineTo(200, 200); // lineTo
path.lineTo(200,0); // lineTo
path.close(); // close
canvas.drawPath(path, mPaint); // 绘制Path
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f1axmfeojzj30u01hcwfi.jpg" width = "270" height = "480"/>
很明显,两个lineTo分别代表第1和第2条线,而close在此处的作用就算连接了B(200,0)点和原点O之间的第3条线,使之形成一个封闭的图形。
**注意:close的作用是封闭路径,与连接当前最后一个点和第一个点并不等价。如果连接了最后一个点和第一个点仍然无法形成封闭图形,则close什么 也不做。**
### 第2组: addXxx与arcTo
这次内容主要是在Path中添加基本图形,重点区分addArc与arcTo。
#### 第一类(基本形状)
方法预览:
``` java
// 第一类(基本形状)
// 圆形
public void addCircle (float x, float y, float radius, Path.Direction dir)
// 椭圆
public void addOval (RectF oval, Path.Direction dir)
// 矩形
public void addRect (float left, float top, float right, float bottom, Path.Direction dir)
public void addRect (RectF rect, Path.Direction dir)
// 圆角矩形
public void addRoundRect (RectF rect, float[] radii, Path.Direction dir)
public void addRoundRect (RectF rect, float rx, float ry, Path.Direction dir)
```
**这一类就是在path中添加一个基本形状,基本形状部分和前面所讲的绘制基本形状并无太大差别,详情参考[Canvas(1)颜色与基本形状](https://github.com/GcsSloop/AndroidNote/blob/master/%E9%97%AE%E9%A2%98/Canvas/Canvas(1).md), 本次只将其中不同的部分摘出来详细讲解一下。**
**仔细观察一下第一类的方法,无一例外,在最后都有一个_Path.Direction_,这是一个什么神奇的东东?**
Direction的意思是 方向,趋势。 点进去看一下会发现Direction是一个枚举(Enum)类型,里面只有两个枚举常量,如下:
| 类型 | 解释 | 翻译 |
| ---- | ----------------- | ---- |
| CW | clockwise | 顺时针 |
| CCW | counter-clockwise | 逆时针 |
> **瞬间懵逼,我只是想添加一个基本的形状啊,搞什么顺时针和逆时针, (╯‵□′)╯︵┻━┻**
**稍安勿躁,┬─┬ ノ( ' - 'ノ) {摆好摆好) 既然存在肯定是有用的,先偷偷剧透一下这个顺时针和逆时针的作用。**
| 序号 | 作用 |
| ---- | ------------------------- |
| 1 | 在添加图形时确定闭合顺序(各个点的记录顺序) |
| 2 | 对图形的渲染结果有影响(是判断图形渲染的重要条件) |
这个先剧透这么多,至于对闭合顺序有啥影响,自相交图形的渲染等问题等请慢慢看下去
咱们先研究确定闭合顺序的问题,添加一个矩形试试看:
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
canvas.drawPath(path,mPaint);
```
<img src="http://ww1.sinaimg.cn/large/005Xtdi2gw1f1cmvjtuxcj30u01hcwgm.jpg" width = "270" height = "480"/>
**将上面代码的CW改为CCW再运行一次。接下来就是见证奇迹的时刻,两次运行结果一模一样,有木有很神奇!**
> **(╯°Д°)╯︵ ┻━┻(再TM掀一次)
> 坑人也不带这样的啊,一毛一样要它干嘛。**
**其实啊,这个东东是自带隐身技能的,想要让它现出原形,就要用到咱们刚刚学到的setLastPoint(重置当前最后一个点的位置)。**
```
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
path.setLastPoint(-300,300); // <-- 重置最后一个点的位置
canvas.drawPath(path,mPaint);
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2jw1f4bg7ly3n7j30u01hc78a.jpg" width = "270" height = "480"/>
可以明显看到,图形发生了奇怪的变化。为何会如此呢?
我们先分析一下,绘制一个矩形(仅绘制边线),实际上只需要进行四次lineTo操作就行了,也就是说,只需要知道4个点的坐标,然后使用moveTo到第一个点,之后依次lineTo就行了(从上面的测试可以看出,在实际绘制中也确实是这么干的)。
可是为什么要这么做呢?确定一个矩形最少需要两个点(对角线的两个点),根据这两个点的坐标直接算出四条边然后画出来不就行了,干嘛还要先计算出四个点坐标,之后再连直线呢?
这个就要涉及一些path的存储问题了,前面在path中的定义中说过,Path是封装了由直线和曲线(二次,三次贝塞尔曲线)构成的几何路径。其中曲线部分用的是贝塞尔曲线,稍后再讲。 然而除了曲线部分就只剩下直线了,对于直线的存储最简单的就是记录坐标点,然后直接连接各个点就行了。虽然记录矩形只需要两个点,但是如果只用两个点来记录一个矩形的话,就要额外增加一个标志位来记录这是一个矩形,显然对于存储和解析都是很不划算的事情,将矩形转换为直线,为的就是存储记录方便。
**扯了这么多,该回归正题了,就是我们的顺时针和逆时针在这里是干啥的?**
图形在实际记录中就是记录各个的点,对于一个图形来说肯定有多个点,既然有这么多的点,肯定就需要一个先后顺序,这里顺时针和逆时针就是用来确定记录这些点的顺序的。
对于上面这个矩形来说,我们采用的是顺时针(CW),所以记录的点的顺序就是 A -> B -> C -> D. 最后一个点就是D,我们这里使用setLastPoint改变最后一个点的位置实际上是改变了D的位置。
理解了上面的原理之后,设想如果我们将顺时针改为逆时针(CCW),则记录点的顺序应该就是 A -> D -> C -> B, 再使用setLastPoint则改变的是B的位置,我们试试看结果和我们的猜想是否一致:
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CCW);
path.setLastPoint(-300,300); // <-- 重置最后一个点的位置
canvas.drawPath(path,mPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f4bgkdk8efj30u01hctcu.jpg" width = "270" height = "480"/>
通过验证发现,发现结果和我们猜想的一样,但是还有一个潜藏的问题不晓得大家可否注意到。**我们用两个点的坐标确定了一个矩形,矩形起始点(A)就是我们指定的第一个点的坐标。**
**需要注意的是,交换坐标点的顺序可能就会影响到某些绘制内容哦,例如上面的例子,你可以尝试交换两个坐标点,或者指定另外两个点来作为参数,虽然指定的是同一个矩形,但实际绘制出来是不同的哦。**
**参数中点的顺序很重要!<br/>参数中点的顺序很重要!<br/>参数中点的顺序很重要!<br/>**
重要的话说三遍,本次是用矩形作为例子的,其他的几个图形基本上都包含了曲线,详情参见后续的贝塞尔曲线部分。
**关于顺时针和逆时针对图形填充结果的影响请等待后续文章,虽然只讲了一个Path,但也是内容颇多,放进一篇中就太长了,请见谅。**
#### 第二类(Path)
方法预览:
``` java
// 第二类(Path)
// path
public void addPath (Path src)
public void addPath (Path src, float dx, float dy)
public void addPath (Path src, Matrix matrix)
```
这个相对比较简单,也很容易理解,就是将两个Path合并成为一个。
第三个方法是将src添加到当前path之前先使用Matrix进行变换。
第二个方法比第一个方法多出来的两个参数是将src进行了位移之后再添加进当前path中。
示例:
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻转y坐标轴
Path path = new Path();
Path src = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
src.addCircle(0,0,100, Path.Direction.CW);
path.addPath(src,0,200);
mPaint.setColor(Color.BLACK); // 绘制合并后的路径
canvas.drawPath(path,mPaint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f4iylko7zzj30u01hc0tm.jpg" width = "270" height = "480"/>
首先我们新建的两个Path(矩形和圆形)中心都是坐标原点,我们在将包含圆形的path添加到包含矩形的path之前将其进行移动了一段距离,最终绘制出来的效果就如上面所示。
#### 第三类(addArc与arcTo)
方法预览:
``` java
// 第三类(addArc与arcTo)
// addArc
public void addArc (RectF oval, float startAngle, float sweepAngle)
// arcTo
public void arcTo (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
```
从名字就可以看出,这两个方法都是与圆弧相关的,作用都是添加一个圆弧到path中,但既然存在两个方法,两者之间肯定是有区别的:
| 名称 | 作用 | 区别 |
| ------ | ----------- | --------------------------------------- |
| addArc | 添加一个圆弧到path | 直接添加一个圆弧到path中 |
| arcTo | 添加一个圆弧到path | 添加一个圆弧到path,如果圆弧的起点和上次最后一个坐标点不相同,就连接两个点 |
可以看到addArc有1个方法(_实际上是两个的,但另一个重载方法是API21添加的_), 而arcTo有2个方法,其中一个最后多了一个布尔类型的变量forceMoveTo。
**forceMoveTo是什么作用呢?**
这个变量意思为“是否强制使用moveTo”,也就是说,是否使用moveTo将变量移动到圆弧的起点位移,也就意味着:
| forceMoveTo | 含义 | 等价方法 |
| ----------- | ---------------------------- | ---------------------------------------- |
| true | 将最后一个点移动到圆弧起点,即不连接最后一个点与圆弧起点 | public void addArc (RectF oval, float startAngle, float sweepAngle) |
| false | 不移动,而是连接最后一个点与圆弧起点 | public void arcTo (RectF oval, float startAngle, float sweepAngle) |
**示例(addArc):**
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻转y坐标轴
Path path = new Path();
path.lineTo(100,100);
RectF oval = new RectF(0,0,300,300);
path.addArc(oval,0,270);
// path.arcTo(oval,0,270,true); // <-- 和上面一句作用等价
canvas.drawPath(path,mPaint);
```
<img src="http://ww4.sinaimg.cn/large/005Xtdi2jw1f1ihr1s77jj30u01hcmzh.jpg" width = "270" height = "480"/>
**示例(arcTo):**
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻转y坐标轴
Path path = new Path();
path.lineTo(100,100);
RectF oval = new RectF(0,0,300,300);
path.arcTo(oval,0,270);
// path.arcTo(oval,0,270,false); // <-- 和上面一句作用等价
canvas.drawPath(path,mPaint);
```
<img src="http://ww1.sinaimg.cn/large/005Xtdi2jw1f1ihsfjexcj30u01hcjts.jpg" width = "270" height = "480"/>
从上面两张运行效果图可以清晰的看出来两者的区别,我就不再废话了。
### 第3组:isEmpty、 isRect、isConvex、 set 和 offset
这一组比较简单,稍微说一下就可以了。
#### isEmpty
方法预览:
``` java
public boolean isEmpty ()
```
判断path中是否包含内容。
``` java
Path path = new Path();
Log.e("1",path.isEmpty()+"");
path.lineTo(100,100);
Log.e("2",path.isEmpty()+"");
```
log输出结果:
```
03-02 14:22:54.770 12379-12379/com.sloop.canvas E/1: true
03-02 14:22:54.770 12379-12379/com.sloop.canvas E/2: false
```
#### isRect
方法预览:
``` java
public boolean isRect (RectF rect)
```
判断path是否是一个矩形,如果是一个矩形的话,会将矩形的信息存放进参数rect中。
``` java
path.lineTo(0,400);
path.lineTo(400,400);
path.lineTo(400,0);
path.lineTo(0,0);
RectF rect = new RectF();
boolean b = path.isRect(rect);
Log.e("Rect","isRect:"+b+"| left:"+rect.left+"| top:"+rect.top+"| right:"+rect.right+"| bottom:"+rect.bottom);
```
log 输出结果:
```
03-02 16:48:39.669 24179-24179/com.sloop.canvas E/Rect: isRect:true| left:0.0| top:0.0| right:400.0| bottom:400.0
```
#### set
方法预览:
``` java
public void set (Path src)
```
将新的path赋值到现有path。
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻转y坐标轴
Path path = new Path(); // path添加一个矩形
path.addRect(-200,-200,200,200, Path.Direction.CW);
Path src = new Path(); // src添加一个圆
src.addCircle(0,0,100, Path.Direction.CW);
path.set(src); // 大致相当于 path = src;
canvas.drawPath(path,mPaint);
```
<img src="http://ww2.sinaimg.cn/large/005Xtdi2jw1f1iv2lnnblj30u01hc3zc.jpg" width = "270" height = "480"/>
#### offset
方法预览:
```java
public void offset (float dx, float dy)
public void offset (float dx, float dy, Path dst)
```
这个的作用也很简单,就是对path进行一段平移,它和Canvas中的translate作用很像,但Canvas作用于整个画布,而path的offset只作用于当前path。
**但是第二个方法最后怎么会有一个path作为参数?**
其实第二个方法中最后的参数dst是存储平移后的path的。
| dst状态 | 效果 |
| ----------- | ------------------------------ |
| dst不为空 | 将当前path平移后的状态存入dst中,不会影响当前path |
| dst为空(null) | 平移将作用于当前path,相当于第一种方法 |
示例:
``` java
canvas.translate(mWidth / 2, mHeight / 2); // 移动坐标系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻转y坐标轴
Path path = new Path(); // path中添加一个圆形(圆心在坐标原点)
path.addCircle(0,0,100, Path.Direction.CW);
Path dst = new Path(); // dst中添加一个矩形
dst.addRect(-200,-200,200,200, Path.Direction.CW);
path.offset(300,0,dst); // 平移
canvas.drawPath(path,mPaint); // 绘制path
mPaint.setColor(Color.BLUE); // 更改画笔颜色
canvas.drawPath(dst,mPaint); // 绘制dst
```
<img src="http://ww3.sinaimg.cn/large/005Xtdi2gw1f1ix3vlwlwj30u01hcq3x.jpg" width = "270" height = "480"/>
从运行效果图可以看出,虽然我们在dst中添加了一个矩形,但是并没有表现出来,所以,当dst中存在内容时,dst中原有的内容会被清空,而存放平移后的path。
# 三.总结
本想一篇把path写完,但是万万没想到居然扯了这么多。本篇中讲解的是直线部分和一些常用方法,下一篇将着重讲解贝塞尔曲线和自相交图形渲染等相关问题,敬请期待哦。
学完本篇之后又解锁了新的境界,可以看看这位大神的文章[ Android雷达图(蜘蛛网图)绘制](http://blog.csdn.net/crazy__chen/article/details/50163693)

这个精小干练,非常适合新手练习使用,帮助大家更好的熟悉path的使用。
(,,• ₃ •,,)
<b>PS: 由于本人水平有限,某些地方可能存在误解或不准确,如果你对此有疑问可以提交Issues进行反馈。</b>
## About Me
### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a>
<a href="https://github.com/GcsSloop/AndroidNote/blob/magic-world/FINDME.md" target="_blank"> <img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width=300/> </a>
## 参考资料
[Path](http://developer.android.com/reference/android/graphics/Path.html)<br/>
[Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)<br/>
[android绘图之Path总结](http://ghui.me/post/2015/10/android-graphics-path/)<br/>
================================================
FILE: CustomView/Advance/[06]Path_Bezier.md
================================================
# Path之贝塞尔曲线
### 作者微博: [@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_Basic.md)中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线。
******
## 一.Path常用方法表
> 为了兼容性(_偷懒_) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法。忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊。宝宝此刻内心也是崩溃的。
| 作用 | 相关方法 | 备注 |
| ----------- | ---------------------------------------- | ---------------------------------------- |
| 移动起点 | moveTo | 移动下一次操作的起点位置 |
| 设置终点 | setLastPoint | 重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveTo相同 |
| 连接直线 | lineTo | 添加上一个点到当前点之间的直线到Path |
| 闭合路径 | close | 连接第一个点连接到最后一个点,形成一个闭合区域 |
| 添加内容 | addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo | 添加(矩形, 圆角矩形, 椭圆, 圆, 路径, 圆弧) 到当前Path (注意addArc和arcTo的区别) |
| 是否为空 | isEmpty | 判断Path是否为空 |
| 是否为矩形 | isRect | 判断path是否是一个矩形 |
| 替换路径 | set | 用新的路径替换到当前路径所有内容 |
| 偏移路径 | offset | 对当前路径之前的操作进行偏移(不会影响之后的操作) |
| 贝塞尔曲线 | quadTo, cubicTo | 分别为二次和三次贝塞尔曲线的方法 |
| rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | **不带r的方法是基于原点的坐标系(偏移量), rXxx方法是基于当前点坐标系(偏移量)** |
| 填充模式 | setFillType, getFillType, isInverseFillType, toggleInverseFillType | 设置,获取,判断和切换填充模式 |
| 提示方法 | incReserve | 提示Path还有多少个点等待加入**(这个方法貌似会让Path优化存储结构)** |
| 布尔操作(API19) | op | 对两个Path进行布尔运算(即取交集、并集等操作) |
| 计算边界 | computeBounds | 计算Path的边界 |
| 重置路径 | reset, rewind | 清除Path中的内容<br/> **reset不保留内部数据结构,但会保留FillType.**<br/> **rewind会保留内部的数据结构,但不保留FillType** |
| 矩阵操作 | transform | 矩阵变换 |
## 二.Path详解
上一次除了一些常用函数之外,讲解的基本上都是直线,本次需要了解其中的曲线部分,说到曲线,就不得不提大名鼎鼎的贝塞尔曲线。它的发明者是下面这个人(法国数学家PierreBézier)。

### 贝塞尔曲线能干什么?
贝塞尔曲线的运用是十分广泛的,可以说**贝塞尔曲线奠定了计算机绘图的基础(_因为它可以将任何复杂的图形用精确的数学语言进行描述_)**,在你不经意间就已经使用过它了。
你会使用Photoshop的话,你可能会注意到里面有一个**钢笔工具**,这个钢笔工具核心就是贝塞尔曲线。
你说你不会PS? 没关系,你如果看过前面的文章或者用过2D绘图,肯定绘制过圆,圆弧,圆角矩形等这些东西。这里面的圆弧部分全部都是贝塞尔曲线的运用。
#### 贝塞尔曲线作用十分广泛,简单举几个的栗子:
> * QQ小红点拖拽效果
* 一些炫酷的下拉刷新控件
* 阅读软件的翻书效果
* 一些平滑的折线图的制作
* 很多炫酷的动画效果
### 如何轻松入门贝塞尔曲线?
虽然贝塞尔曲线用途非常广泛,然而目前貌似并没有适合的中文教程,能够搜索出来Android关于贝塞尔曲线的中文文章基本可以分为以下几种:
* 科普型(只是让人了解贝塞尔,并没有实质性的内容)
* 装逼型(摆出来一大堆公式,引用一堆英文原文)
* 基础型(仅仅是讲解贝塞尔曲线的两个函数用法)
* 实战型(根据实例讲解其中贝塞尔曲线的运用)
以上几种类型中比较有用的就是基础型和实战型,但两者各有不足,本文会综合两者内容,从零开始学习贝塞尔曲线。
### 第一步.理解贝塞尔曲线的原理
此处理解贝塞尔曲线并非是学会公式的推导过程(不是推倒(ノ*・ω・)ノ),而是要了解贝塞尔曲线是如何生成的。
贝塞尔曲线是用一系列点来控制曲线状态的,我将这些点简单分为两类:
| 类型 | 作用 |
| ---- | ------------ |
| 数据点 | 确定曲线的起始和结束位置 |
| 控制点 | 确定曲线的弯曲程度 |
> 此处暂时仅作了解概念,接下来就会讲解其中详细的含义。
**一阶曲线原理:**
一阶曲线是没有控制点的,仅有两个数据点(A 和 B),最终效果一个线段。

> **上图表示的是一阶曲线生成过程中的某一个阶段,动态过程可以参照下图(本文中贝塞尔曲线相关的动态演示图片来自维基百科)。**

> **PS:一阶曲线其实就是前面讲解过的lineTo。**
**二阶曲线原理:**
二阶曲线由两个数据点(A 和 C),一个控制点(B)来描述曲线状态,大致如下:

上图中红色曲线部分就是传说中的二阶贝塞尔曲线,那么这条红色曲线是如何生成的呢?接下来我们就以其中的一个状态分析一下:

连接AB BC,并在AB上取点D,BC上取点E,使其满足条件:
<img src="http://chart.googleapis.com/chart?cht=tx&chl=%5Cfrac%7BAD%7D%7BAB%7D%20%3D%20%5Cfrac%7BBE%7D%7BBC%7D" style="border:none;" />

连接DE,取点F,使得:
<img src="http://chart.googleapis.com/chart?cht=tx&chl=%5Cfrac%7BAD%7D%7BAB%7D%20%3D%20%5Cfrac%7BBE%7D%7BBC%7D%20%3D%20%5Cfrac%7BDF%7D%7BDE%7D" style="border:none;" />
这样获取到的点F就是贝塞尔曲线上的一个点,动态过程如下:

> **PS: 二阶曲线对应的方法是quadTo**
**三阶曲线原理:**
三阶曲线由两个数据点(A 和 D),两个控制点(B 和 C)来描述曲线状态,如下:

三阶曲线计算过程与二阶类似,具体可以见下图动态效果:

> **PS: 三阶曲线对应的方法是cubicTo**
#### [贝塞尔曲线速查表](https://github.com/GcsSloop/AndroidNote/blob/master/QuickChart/Bezier.md)
#### 强烈推荐[点击这里](http://bezier.method.ac/)练习贝塞尔曲线,可以加深对贝塞尔曲线的理解程度。
### 第二步.了解贝塞尔曲线相关函数使用方法
#### 一阶曲线:
一阶曲线是一条线段,非常简单,可以参见上一篇文章[Path之基本操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/5%5DPath_Basic.md),此处就不详细讲解了。
#### 二阶曲线:
通过上面对二阶曲线的简单了解,我们知道二阶曲线是由两个数据点,一个控制点构成,接下来我们就用一个实例来演示二阶曲线是如何运用的。
首先,两个数据点是控制贝塞尔曲线开始和结束的位置,比较容易理解,而控制点则是控制贝塞尔的弯曲状态,相对来说比较难以理解,所以本示例重点在于理解贝塞尔曲线弯曲状态与控制点的关系,废话不多说,先上效果图:

> 为了更加容易看出控制点与曲线弯曲程度的关系,上图中绘制出了辅助点和辅助线,从上面的动态图可以看出,贝塞尔曲线在动态变化过程中有类似于橡皮筋一样的弹性效果,因此在制作一些弹性效果的时候很常用。
主要代码如下:
``` java
public class Bezier extends View {
private Paint mPaint;
private int centerX, centerY;
private PointF start, end, control;
public Bessel1(Context context) {
super(context);
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(8);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setTextSize(60);
start = new PointF(0,0);
end = new PointF(0,0);
control = new PointF(0,0);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w/2;
centerY = h/2;
// 初始化数据点和控制点的位置
start.x = centerX-200;
start.y = centerY;
end.x = centerX+200;
end.y = centerY;
control.x = centerX;
control.y = centerY-100;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 根据触摸位置更新控制点,并提示重绘
control.x = event.getX();
control.y = event.getY();
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制数据点和控制点
mPaint.setColor(Color.GRAY);
mPaint.setStrokeWidth(20);
canvas.drawPoint(start.x,start.y,mPaint);
canvas.drawPoint(end.x,end.y,mPaint);
canvas.drawPoint(control.x,control.y,mPaint);
// 绘制辅助线
mPaint.setStrokeWidth(4);
canvas.drawLine(start.x,start.y,control.x,control.y,mPaint);
canvas.drawLine(end.x,end.y,control.x,control.y,mPaint);
// 绘制贝塞尔曲线
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(8);
Path path = new Path();
path.moveTo(start.x,start.y);
path.quadTo(control.x,control.y,end.x,end.y);
canvas.drawPath(path, mPaint);
}
}
```
#### 三阶曲线:
三阶曲线由两个数据点和两个控制点来控制曲线状态。

代码:
``` java
public class Bezier2 extends View {
private Paint mPaint;
private int centerX, centerY;
private PointF start, end, control1, control2;
private boolean mode = true;
public Bezier2(Context context) {
this(context, null);
}
public Bezier2(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(8);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setTextSize(60);
start = new PointF(0, 0);
end = new PointF(0, 0);
control1 = new PointF(0, 0);
control2 = new PointF(0, 0);
}
public void setMode(boolean mode) {
this.mode = mode;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w / 2;
centerY = h / 2;
// 初始化数据点和控制点的位置
start.x = centerX - 200;
start.y = centerY;
end.x = centerX + 200;
end.y = centerY;
control1.x = centerX;
control1.y = centerY - 100;
control2.x = centerX;
control2.y = centerY - 100;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 根据触摸位置更新控制点,并提示重绘
if (mode) {
control1.x = event.getX();
control1.y = event.getY();
} else {
control2.x = event.getX();
control2.y = event.getY();
}
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//drawCoordinateSystem(canvas);
// 绘制数据点和控制点
mPaint.setColor(Color.GRAY);
mPaint.setStrokeWidth(20);
canvas.drawPoint(start.x, start.y, mPaint);
canvas.drawPoint(end.x, end.y, mPaint);
canvas.drawPoint(control1.x, control1.y, mPaint);
canvas.drawPoint(control2.x, control2.y, mPaint);
// 绘制辅助线
mPaint.setStrokeWidth(4);
canvas.drawLine(start.x, start.y, control1.x, control1.y, mPaint);
canvas.drawLine(control1.x, control1.y,control2.x, control2.y, mPaint);
canvas.drawLine(control2.x, control2.y,end.x, end.y, mPaint);
// 绘制贝塞尔曲线
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(8);
Path path = new Path();
path.moveTo(start.x, start.y);
path.cubicTo(control1.x, control1.y, control2.x,control2.y, end.x, end.y);
canvas.drawPath(path, mPaint);
}
}
```
> 三阶曲线相比于二阶曲线可以制作更加复杂的形状,但是对于高阶的曲线,用低阶的曲线组合也可达到相同的效果,就是传说中的**降阶**。因此我们对贝塞尔曲线的封装方法一般最高只到三阶曲线。
#### 降阶与升阶
| 类型 | 释义 | 变化 |
| ---- | -------------------------------- | -------------------------- |
| 降阶 | 在保持曲线形状与方向不变的情况下,减少控制点数量,即降低曲线阶数 | 方法变得简单,数据点变多,控制点可能减少,灵活性变弱 |
| 升阶 | 在保持曲线形状与方向不变的情况下,增加控制点数量,即升高曲线阶数 | 方法更加复杂,数据点不变,控制点增加,灵活性变强 |
### 第三步.贝塞尔曲线使用实例
**在制作这个实例之前,首先要明确一个内容,就是在什么情况下需要使用贝塞尔曲线?**
> 需要绘制不规则图形时? 当然不是!目前来说,我觉得使用贝塞尔曲线主要有以下几个方面(仅个人拙见,可能存在错误,欢迎指正)
| 序号 | 内容 | 用例 |
| ---- | ---------------------------- | -------------- |
| 1 | 事先不知道曲线状态,需要实时计算时 | 天气预报气温变化的平滑折线图 |
| 2 | 显示状态会根据用户操作改变时 | QQ小红点,仿真翻书效果 |
| 3 | 一些比较复杂的运动状态(配合PathMeasure使用) | 复杂运动状态的动画效果 |
至于只需要一个静态的曲线图形的情况,用图片岂不是更好,大量的计算会很不划算。
如果是显示SVG矢量图的话,已经有相关的解析工具了(内部依旧运用的有贝塞尔曲线),不需要手动计算。
**贝塞尔曲线的主要优点是可以实时控制曲线状态,并可以通过改变控制点的状态实时让曲线进行平滑的状态变化。**
### 接下来我们就用一个简单的示例让一个圆渐变成为心形:
#### 效果图:

#### 思路分析:
我们最终的需要的效果是将一个圆转变成一个心形,通过分析可知,圆可以由四段三阶贝塞尔曲线组合而成,如下:

心形也可以由四段的三阶的贝塞尔曲线组成,如下:

两者的差别仅仅在于数据点和控制点位置不同,因此只需要调整数据点和控制点的位置,就能将圆形变为心形。
#### 核心难点:
##### 1.如何得到数据点和控制点的位置?
关于使用绘制圆形的数据点与控制点早就已经有人详细的计算好了,可以参考stackoverflow的一个回答[How to create circle with Bézier curves?](http://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves)其中的数据只需要拿来用即可。
而对于心形的数据点和控制点,可以由圆形的部分数据点和控制点平移后得到,具体参数可以自己慢慢调整到一个满意的效果。
##### 2.如何达到渐变效果?
渐变其实就是每次对数据点和控制点稍微移动一点,然后重绘界面,在短时间多次的调整数据点与控制点,使其逐渐接近目标值,通过不断的重绘界面达到一种渐变的效果。过程可以参照下图动态效果:

#### 代码:
``` java
public class Bezier3 extends View {
private static final float C = 0.551915024494f; // 一个常量,用来计算绘制圆形贝塞尔曲线控制点的位置
private Paint mPaint;
private int mCenterX, mCenterY;
private PointF mCenter = new PointF(0,0);
private float mCircleRadius = 200; // 圆的半径
private float mDifference = mCircleRadius*C; // 圆形的控制点与数据点的差值
private float[] mData = new float[8]; // 顺时针记录绘制圆形的四个数据点
private float[] mCtrl = new float[16]; // 顺时针记录绘制圆形的八个控制点
private float mDuration = 1000; // 变化总时长
private float mCurrent = 0; // 当前已进行时长
private float mCount = 100; // 将时长总共划分多少份
private float mPiece = mDuration/mCount; // 每一份的时长
public Bezier3(Context context) {
this(context, null);
}
public Bezier3(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(8);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setTextSize(60);
// 初始化数据点
mData[0] = 0;
mData[1] = mCircleRadius;
mData[2] = mCircleRadius;
mData[3] = 0;
mData[4] = 0;
mData[5] = -mCircleRadius;
mData[6] = -mCircleRadius;
mData[7] = 0;
// 初始化控制点
mCtrl[0] = mData[0]+mDifference;
mCtrl[1] = mData[1];
mCtrl[2] = mData[2];
mCtrl[3] = mData[3]+mDifference;
mCtrl[4] = mData[2];
mCtrl[5] = mData[3]-mDifference;
mCtrl[6] = mData[4]+mDifference;
mCtrl[7] = mData[5];
mCtrl[8] = mData[4]-mDifference;
mCtrl[9] = mData[5];
mCtrl[10] = mData[6];
mCtrl[11] = mData[7]-mDifference;
mCtrl[12] = mData[6];
mCtrl[13] = mData[7]+mDifference;
mCtrl[14] = mData[0]-mDifference;
mCtrl[15] = mData[1];
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
gitextract_f641qz65/
├── AudioVideo/
│ ├── README.md
│ ├── SDL.md
│ ├── image/
│ │ └── 视频解码播放流程.gliffy
│ ├── 常见封装格式.md
│ ├── 常见封装格式概览.md
│ ├── 常见流媒体协议.md
│ ├── 常见音视频编码.md
│ └── 通用视频解码播放流程.md
├── ChaosCrystal/
│ ├── ADB常用命令.md
│ ├── AndroidStudio常用快捷键(Mac).md
│ ├── Android中dip、dp、sp、pt和px.md
│ ├── HowToViewAPISourceOnline.md
│ ├── README.md
│ └── 录屏与GIF制作.md
├── Course/
│ ├── HowToUsePlantUMLInAS.md
│ ├── HowToUsePlantUMLInAS[Mac].md
│ ├── Markdown/
│ │ ├── README.md
│ │ ├── markdown-editor.md
│ │ ├── markdown-grammar.md
│ │ ├── markdown-html.md
│ │ ├── markdown-link.md
│ │ └── markdown-start.md
│ ├── README.md
│ ├── ReleaseLibraryByJitPack.md
│ └── jitpack-javadoc.md
├── CustomView/
│ ├── Advance/
│ │ ├── Code/
│ │ │ ├── CheckView.java
│ │ │ ├── CheckView.md
│ │ │ ├── SearchView.java
│ │ │ ├── SearchView.md
│ │ │ ├── SetPolyToPoly.java
│ │ │ └── SetPolyToPoly.md
│ │ ├── [01]CustomViewProcess.md
│ │ ├── [02]Canvas_BasicGraphics.md
│ │ ├── [03]Canvas_Convert.md
│ │ ├── [04]Canvas_PictureText.md
│ │ ├── [05]Path_Basic.md
│ │ ├── [06]Path_Bezier.md
│ │ ├── [07]Path_Over.md
│ │ ├── [08]Path_Play.md
│ │ ├── [09]Matrix_Basic.md
│ │ ├── [10]Matrix_Method.md
│ │ ├── [11]Matrix_3D_Camera.md
│ │ ├── [12]Dispatch-TouchEvent-Theory.md
│ │ ├── [15]Dispatch-TouchEvent-Source.md
│ │ ├── [16]MotionEvent.md
│ │ ├── [17]touch-matrix-region.md
│ │ ├── [18]multi-touch.md
│ │ ├── [19]gesture-detector.md
│ │ └── [99]DrawText.md
│ ├── Base/
│ │ ├── [01]CoordinateSystem.md
│ │ ├── [02]AngleAndRadian.md
│ │ └── [03]Color.md
│ ├── CustomViewRule.md
│ └── README.md
├── Lecture/
│ ├── README.md
│ └── gdg-developer-growth-guide.md
├── OpenGL/
│ └── README.md
├── QuickChart/
│ ├── Bezier.md
│ ├── Canvas.md
│ ├── Matrix.md
│ ├── Path.md
│ └── README.md
├── README.md
└── SourceAnalysis/
├── AtomicFile.md
├── CircularArray.md
└── README.md
SYMBOL INDEX (32 symbols across 3 files)
FILE: CustomView/Advance/Code/CheckView.java
class CheckView (line 26) | public class CheckView extends View {
method CheckView (line 46) | public CheckView(Context context) {
method CheckView (line 51) | public CheckView(Context context, AttributeSet attrs) {
method init (line 60) | private void init(Context context) {
method onSizeChanged (line 108) | @Override
method onDraw (line 119) | @Override
method check (line 144) | public void check() {
method unCheck (line 156) | public void unCheck() {
method setAnimDuration (line 169) | public void setAnimDuration(int animDuration) {
method setBackgroundColor (line 179) | public void setBackgroundColor(int color){
FILE: CustomView/Advance/Code/SearchView.java
class SearchView (line 10) | public class SearchView extends View {
method SearchView (line 19) | public SearchView(Context context) {
method SearchView (line 23) | public SearchView(Context context, AttributeSet attrs) {
method initAll (line 28) | public void initAll() {
type State (line 47) | public static enum State {
method initPaint (line 89) | private void initPaint() {
method initPath (line 98) | private void initPath() {
method initListener (line 121) | private void initListener() {
method initHandler (line 154) | private void initHandler() {
method initAnimator (line 190) | private void initAnimator() {
method onSizeChanged (line 205) | @Override
method onDraw (line 212) | @Override
method drawSearch (line 219) | private void drawSearch(Canvas canvas) {
FILE: CustomView/Advance/Code/SetPolyToPoly.java
class SetPolyToPoly (line 24) | public class SetPolyToPoly extends View{
method SetPolyToPoly (line 38) | public SetPolyToPoly(Context context) {
method SetPolyToPoly (line 42) | public SetPolyToPoly(Context context, AttributeSet attrs) {
method SetPolyToPoly (line 46) | public SetPolyToPoly(Context context, AttributeSet attrs, int defStyle...
method initBitmapAndMatrix (line 51) | private void initBitmapAndMatrix() {
method onTouchEvent (line 72) | @Override
method resetPolyMatrix (line 97) | public void resetPolyMatrix(int pointCount){
method onDraw (line 103) | @Override
method setTestPoint (line 123) | public void setTestPoint(int testPoint) {
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (448K chars).
[
{
"path": "AudioVideo/README.md",
"chars": 49,
"preview": "# Audio/Video 杂记\n\n- [通用视频解码播放流程](通用视频解码播放流程.md)\n\n"
},
{
"path": "AudioVideo/SDL.md",
"chars": 1489,
"preview": "---\ntypora-copy-images-to: ./image\n---\n\n## SDL\n\n### 简介\n\nSDL(Simple DirectMedia Layer)库的作用就是封装了复杂的音视频底层交互工作,简化了音视频处理的难度。\n"
},
{
"path": "AudioVideo/image/视频解码播放流程.gliffy",
"chars": 23299,
"preview": "{\"contentType\":\"application/gliffy+json\",\"version\":\"1.1\",\"metadata\":{\"title\":\"untitled\",\"revision\":0,\"exportBorder\":fals"
},
{
"path": "AudioVideo/常见封装格式.md",
"chars": 1414,
"preview": "## 常见封装格式\n\n封装格式的主要作用是把视频码流和音频码流按照一定的格式存储在一个文件中。现如今流行的封装格式如下表所示:\n\n主要封装格式一览\n\n| 名称 | 推出机构 | 流媒体 | 支持的视频编码 "
},
{
"path": "AudioVideo/常见封装格式概览.md",
"chars": 1064,
"preview": "## 常见封装格式概览\n\n| 名称 | 推出机构 | 流媒体 | 支持的视频编码 | 支持的音频编码 |"
},
{
"path": "AudioVideo/常见流媒体协议.md",
"chars": 1495,
"preview": "## 常见流媒体协议\n\n流媒体协议是服务器与客户端之间通信遵循的规定。当前网络上主要的流媒体协议如表所示。\n\n| 名称 | 推出机构 | 传输层协议 | 客户端 | 目前使用领域 |\n| -"
},
{
"path": "AudioVideo/常见音视频编码.md",
"chars": 5017,
"preview": "## 常见音视频编码\n\n### 1. 视频编码\n\n视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。视频编码是视音频技"
},
{
"path": "AudioVideo/通用视频解码播放流程.md",
"chars": 302,
"preview": "---\ntypora-copy-images-to: ./image\n---\n\n## 通用视频解码播放流程\n\n**通用的网络视频播放流程:**\n\n1. 从网络数据流中获得视频数据流。\n2. 将视频数据流解析成压缩音频数据和压缩视频数据。\n3"
},
{
"path": "ChaosCrystal/ADB常用命令.md",
"chars": 687,
"preview": "# ADB常用命令\n\n命令 | 说明\n--------------------------------|-----------------------------------------"
},
{
"path": "ChaosCrystal/AndroidStudio常用快捷键(Mac).md",
"chars": 1141,
"preview": "# AndroidStudio常用快捷键(Mac) \n\n这里的快捷键是基于OSX个人定制版本的,具体请到 setting -> keymap 设置\n\n快捷键 | 作用\n---------------"
},
{
"path": "ChaosCrystal/Android中dip、dp、sp、pt和px.md",
"chars": 1845,
"preview": "# Android中dip、dp、sp、pt和px\n\n概念区别:\n\n单位 | 含义\n--- | ---\ndip | device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们"
},
{
"path": "ChaosCrystal/HowToViewAPISourceOnline.md",
"chars": 613,
"preview": "# 在线查看Android API源码的方法\n\n\n方法 | 推荐程度| 链接 | 备注\n---|---|---|---\n官方GitHub仓库 | ★☆☆☆☆ | [GitHub-Android](https://github.com/and"
},
{
"path": "ChaosCrystal/README.md",
"chars": 807,
"preview": "\n\n# 混沌水晶 \n\n混沌水晶本身并没有太大功效,但与其他物品合成之后可能产生质的变化。\n\n* [An"
},
{
"path": "ChaosCrystal/录屏与GIF制作.md",
"chars": 1883,
"preview": "# 录屏与GIF制作\n\n## 说明\n在实际工作或学习过程中,很多地方都需要展示一些过程,或者展示动态的效果,使用视频显得比较大了,而且也不利于分享,故使用GIF就是一种很好的方法,体积小,易于分享展示。\n\n## 制作GIF的方法\n> 此处大"
},
{
"path": "Course/HowToUsePlantUMLInAS.md",
"chars": 2275,
"preview": "# 在AndroidStudio中使用PlantUML\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n\n## 前言\n\n### 这是Windows平台设置教程,Mac平台戳这里:<a hr"
},
{
"path": "Course/HowToUsePlantUMLInAS[Mac].md",
"chars": 2434,
"preview": "# 在AndroidStudio中使用PlantUML(Mac)\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n\n## 前言\n\n### 这是Mac平台设置教程,Windows平台戳这里:"
},
{
"path": "Course/Markdown/README.md",
"chars": 440,
"preview": "# Markdown 实用技巧\n\n* [Markdown 快速入门](https://github.com/GcsSloop/AndroidNote/blob/master/Course/Markdown/markdown-start.md"
},
{
"path": "Course/Markdown/markdown-editor.md",
"chars": 4312,
"preview": "# Markdown实用技巧-编辑器(Typora)\n\n本次的安利对象是一个 Markdown 编辑器,是会长见过的最简单,最优雅的编辑器,先来看一下它的界面吧:\n\n\n### 作者微博: <a href=\"http://weibo.com/GcsSloop\" target=\"_blank\">@GcsSloop</a>\n### [JitPack"
},
{
"path": "Course/jitpack-javadoc.md",
"chars": 2325,
"preview": "# 用JitPack发布时附加文档和源码\n\n很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反馈说"
},
{
"path": "CustomView/Advance/Code/CheckView.java",
"chars": 4894,
"preview": "package com.sloop.canvas;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Bitma"
},
{
"path": "CustomView/Advance/Code/CheckView.md",
"chars": 5050,
"preview": "## CheckView源代码\n\n[下载代码 ( 右键 -> 另存为 )](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Advance/C"
},
{
"path": "CustomView/Advance/Code/SearchView.java",
"chars": 7221,
"preview": "/**\n * Author: GcsSloop\n * <p>\n * Created Date: 16/5/31\n * <p>\n * Copyright (C) 2016 GcsSloop.\n * <p>\n * GitHub: https:/"
},
{
"path": "CustomView/Advance/Code/SearchView.md",
"chars": 7377,
"preview": "## SearchView 源代码\n\n[下载代码 (右键 -> 另存为)](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Advance/C"
},
{
"path": "CustomView/Advance/Code/SetPolyToPoly.java",
"chars": 3715,
"preview": "package com.gcssloop.canvas;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Bi"
},
{
"path": "CustomView/Advance/Code/SetPolyToPoly.md",
"chars": 5983,
"preview": "# Matrix setPolyTOPoly 测试代码\n\n## SetPolyToPoly.java\n\n[下载代码 (右键 -> 另存为)](https://raw.githubusercontent.com/GcsSloop/Androi"
},
{
"path": "CustomView/Advance/[01]CustomViewProcess.md",
"chars": 7897,
"preview": "# 自定义View分类与流程\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote"
},
{
"path": "CustomView/Advance/[02]Canvas_BasicGraphics.md",
"chars": 15661,
"preview": "# Canvas之绘制基本形状\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNot"
},
{
"path": "CustomView/Advance/[03]Canvas_Convert.md",
"chars": 13856,
"preview": "# Canvas之画布操作\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/"
},
{
"path": "CustomView/Advance/[04]Canvas_PictureText.md",
"chars": 17497,
"preview": "# Canvas之图片文字\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/"
},
{
"path": "CustomView/Advance/[05]Path_Basic.md",
"chars": 19440,
"preview": "# Path之基本操作\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n\n### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/t"
},
{
"path": "CustomView/Advance/[06]Path_Bezier.md",
"chars": 17830,
"preview": "# Path之贝塞尔曲线\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/t"
},
{
"path": "CustomView/Advance/[07]Path_Over.md",
"chars": 13922,
"preview": "# Path之完结篇(伪)\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/"
},
{
"path": "CustomView/Advance/[08]Path_Play.md",
"chars": 18130,
"preview": "# Path之玩出花样(PathMeasure)\n\n可以看到,在经过 \n[Path之基本操作](http://www.gcssloop.com/customview/Path_Basic/)\n[Path之贝塞尔曲线](http://www."
},
{
"path": "CustomView/Advance/[09]Matrix_Basic.md",
"chars": 12189,
"preview": "本文内容偏向理论,和 [画布操作](http://www.gcssloop.com/customview/Canvas_Convert/) 有重叠的部分,本文会让你更加深入的了解其中的原理。\n\n本篇的主角Matrix,是一个一直在后台默默工"
},
{
"path": "CustomView/Advance/[10]Matrix_Method.md",
"chars": 19868,
"preview": "# Matrix详解\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### 相关文章: [自定义View目录](http://www.gcssloop.com/1970/01/Custo"
},
{
"path": "CustomView/Advance/[11]Matrix_3D_Camera.md",
"chars": 14872,
"preview": "# Matrix Camera\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### 相关文章: [自定义View目录](http://www.gcssloop.com/customvi"
},
{
"path": "CustomView/Advance/[12]Dispatch-TouchEvent-Theory.md",
"chars": 10030,
"preview": "# 事件分发机制原理\n\n\n之前讲解了很多与View绘图相关的知识,你可以在 [安卓自定义View教程目录](http://www.gcssloop.com/customview/CustomViewIndex) 中查看到这些文章,如果你理解"
},
{
"path": "CustomView/Advance/[15]Dispatch-TouchEvent-Source.md",
"chars": 19963,
"preview": "# 事件分发机制详解\n\n在上一篇文章 [事件分发机制原理][dispatch-touchevent-theory] 中简要分析了一下事件分发机制的原理,原理是十分简单的,一句话就能总结:**责任链模式,事件层层传递,直到被消费。** 虽然原"
},
{
"path": "CustomView/Advance/[16]MotionEvent.md",
"chars": 14253,
"preview": "# MotionEvent 详解\n\nAndroid MotionEvent 详解,之前用了两篇文章 [事件分发机制原理][customview/dispatch-touchevent-theory] 和 [事件分发机制详解][customv"
},
{
"path": "CustomView/Advance/[17]touch-matrix-region.md",
"chars": 16599,
"preview": "# 特殊控件的事件处理方案\n\n本文带大家了解 Android 特殊形状控件的事件处理方式,主要是利用了 Region 和 Matrix 的一些方法,超级实用的事件处理方案,相信看完本篇之后,任何奇葩控件的事件处理都会变得十分简单。\n\n不得不"
},
{
"path": "CustomView/Advance/[18]multi-touch.md",
"chars": 19919,
"preview": "# Android 多点触控详解\n\nAndroid 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带大家了解 Android 多点触控相关的一些知识。 \n\n**多点触控** ( "
},
{
"path": "CustomView/Advance/[19]gesture-detector.md",
"chars": 13909,
"preview": "# Android 手势检测(GestureDetector)\n\nAndroid 手势检测,主要是 GestureDetector 相关内容的用法和注意事项,本文依旧属于事件处理这一体系,部分内容会涉及到之前文章提及过的知识点,如果你没看过"
},
{
"path": "CustomView/Advance/[99]DrawText.md",
"chars": 8426,
"preview": "# drawText之坐标、居中、绘制多行\n\n本文用于讲解Canvas中关于DrawText相关的一些常见问题,如坐标,居中,和绘制多行。\n\n之前由于个人的疏忽以及对问题的想当然,没有进行验证,在 [【安卓自定义View进阶 - 图片文字】"
},
{
"path": "CustomView/Base/[01]CoordinateSystem.md",
"chars": 1584,
"preview": "# 安卓中的坐标系\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### [【本系列相关文章】](http://www.gcssloop.com/1970/01/CustomViewIn"
},
{
"path": "CustomView/Base/[02]AngleAndRadian.md",
"chars": 1933,
"preview": "# 角度与弧度\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n### [【本系列相关文章】](http://www.gcssloop.com/1970/01/CustomViewInde"
},
{
"path": "CustomView/Base/[03]Color.md",
"chars": 4225,
"preview": "# 颜色\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n\n### [【本系列相关文章】](http://www.gcssloop.com/1970/01/CustomViewIndex/"
},
{
"path": "CustomView/CustomViewRule.md",
"chars": 1410,
"preview": "# 自定义View基本法\n\n我们使用手机,是想要获取某些信息,而 View 是这些信息的直接展示界面,因为信息种类繁多,为了更好的展示这些信息, View 也必须有多种多样,Android 系统本身就给我们提供了不少类型的 View,但有时"
},
{
"path": "CustomView/README.md",
"chars": 4398,
"preview": "# 自定义View系列\n\n从零起步,从入门到懵逼的自定义View教程。\n\n## 基础篇\n\n<p align=\"center\">\n<a href=\"https://github.com/GcsSloop/AndroidNote/blob/ma"
},
{
"path": "Lecture/README.md",
"chars": 130,
"preview": "# 演讲稿\n\n* [程序员练级指北(郑州GDG-2016DevFest)](https://github.com/GcsSloop/AndroidNote/blob/master/Lecture/gdg-developer-growth-g"
},
{
"path": "Lecture/gdg-developer-growth-guide.md",
"chars": 6334,
"preview": "# 程序员练级指北\n\n\n\n之前非常有幸收到 [脉脉不得语](https://github.com/in"
},
{
"path": "OpenGL/README.md",
"chars": 114,
"preview": "# OpenGL\n\nOpenGL 全称 Open Graphics Library,是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。OpenGL 常用于CAD、虚拟实境、科学可视化程序和电子游戏开发。\n"
},
{
"path": "QuickChart/Bezier.md",
"chars": 785,
"preview": "# 贝塞尔曲线常用操作速查表\n\n> ### 贝塞尔曲线的操作方法均包含在Path中,详情可参考[Path之贝塞尔曲线](https://github.com/GcsSloop/AndroidNote/blob/master/CustomVi"
},
{
"path": "QuickChart/Canvas.md",
"chars": 4566,
"preview": "# Canvas常用操作速查表\n\n| 操作分类 | 相关API | 备注 |\n| "
},
{
"path": "QuickChart/Matrix.md",
"chars": 1161,
"preview": "# Matrix常用操作速查表\n\n| 方法类别 | 相关API | 摘要 |\n| -------- | -----"
},
{
"path": "QuickChart/Path.md",
"chars": 1798,
"preview": "# Path常用操作速查表\n\n> 为了兼容性(_偷懒_) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法。忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊。宝宝此刻内心也是崩溃的。\n\n作用 "
},
{
"path": "QuickChart/README.md",
"chars": 371,
"preview": "# 速查表\n\n\n* [Canvas常用操作速查表](https://github.com/GcsSloop/AndroidNote/blob/master/QuickChart/Canvas.md)\n* [Path常用操作速查表](http"
},
{
"path": "README.md",
"chars": 9005,
"preview": "\n\n### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)\n"
},
{
"path": "SourceAnalysis/AtomicFile.md",
"chars": 7627,
"preview": "# AtomicFile 源码解析\n\n\n\n## 什么是 AtomicFile ?\n\nAtomicFil"
},
{
"path": "SourceAnalysis/CircularArray.md",
"chars": 0,
"preview": ""
},
{
"path": "SourceAnalysis/README.md",
"chars": 110,
"preview": "# 源码解析\n\n* [AtomicFile 源码解析](https://github.com/GcsSloop/AndroidNote/blob/master/SourceAnalysis/AtomicFile.md)\n"
}
]
About this extraction
This page contains the full source code of the GcsSloop/AndroidNote GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (416.2 KB), approximately 169.9k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.