Date: Tue, 30 Aug 2016 04:16:19 +0800
Subject: [PATCH 013/227] Add Test Code
---
CustomView/Advance/Code/SetPolyToPoly.java | 129 +++++++++++++
CustomView/Advance/Code/SetPolyToPoly.md | 213 +++++++++++++++++++++
2 files changed, 342 insertions(+)
create mode 100644 CustomView/Advance/Code/SetPolyToPoly.java
create mode 100644 CustomView/Advance/Code/SetPolyToPoly.md
diff --git a/CustomView/Advance/Code/SetPolyToPoly.java b/CustomView/Advance/Code/SetPolyToPoly.java
new file mode 100644
index 00000000..d905ae91
--- /dev/null
+++ b/CustomView/Advance/Code/SetPolyToPoly.java
@@ -0,0 +1,129 @@
+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
+ *
+ * Created Date: 16/8/26
+ *
+ * Copyright (C) 2016 GcsSloop.
+ *
+ * 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 4 || testPoint < 0 ? 4 : testPoint;
+ dst = src.clone();
+ resetPolyMatrix(this.testPoint);
+ invalidate();
+ }
+}
diff --git a/CustomView/Advance/Code/SetPolyToPoly.md b/CustomView/Advance/Code/SetPolyToPoly.md
new file mode 100644
index 00000000..3988fac2
--- /dev/null
+++ b/CustomView/Advance/Code/SetPolyToPoly.md
@@ -0,0 +1,213 @@
+# Matrix setPolyTOPoly 测试代码
+
+## 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 4 || testPoint < 0 ? 4 : testPoint;
+ dst = src.clone();
+ resetPolyMatrix(this.testPoint);
+ invalidate();
+ }
+}
+```
+
+*****
+
+## 布局文件
+
+``` xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+*****
+
+## 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 )
+
+
From 5dfb11d0b6e281569b2f19386976d28b57494658 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Tue, 30 Aug 2016 04:19:35 +0800
Subject: [PATCH 014/227] Update
---
CustomView/Advance/[10]Matrix_Method.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CustomView/Advance/[10]Matrix_Method.md b/CustomView/Advance/[10]Matrix_Method.md
index d227a655..a46027af 100644
--- a/CustomView/Advance/[10]Matrix_Method.md
+++ b/CustomView/Advance/[10]Matrix_Method.md
@@ -560,6 +560,8 @@ if (1 == count) {
上面已经用图例比较详细的展示了不同操控点个数的情况,如果你依旧存在疑问,可以获取代码自己试一下。
+#### [点击此处查看setPolyToPoly测试代码](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/Code/SetPolyToPoly.md)
+
#### 2.setRectToRect
```JAVA
From db567af9d248450646c9df346b1aebadb4afa11c Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Tue, 30 Aug 2016 04:34:48 +0800
Subject: [PATCH 015/227] Update
---
CustomView/Advance/[10]Matrix_Method.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CustomView/Advance/[10]Matrix_Method.md b/CustomView/Advance/[10]Matrix_Method.md
index a46027af..6d7b0a0c 100644
--- a/CustomView/Advance/[10]Matrix_Method.md
+++ b/CustomView/Advance/[10]Matrix_Method.md
@@ -508,7 +508,7 @@ public class MatrixSetPolyToPolyTest extends View {
*****
-**一下用示例演示一下,所有示例的src均为图片大小,dst根据手势变化。**
+**接下来用示例演示一下,所有示例的src均为图片大小,dst根据手势变化。**
**pointCount为0**
From d2191236700a0521bd566ddc09a4eec8aea7c2e6 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Wed, 31 Aug 2016 00:35:16 +0800
Subject: [PATCH 016/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 CustomView/Advance/[11]Matrix_3D_Camera.md
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
new file mode 100644
index 00000000..e69de29b
From 9c160464b8bb47b4d2416d29dfb6d6ebbbd0a38f Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Wed, 31 Aug 2016 17:03:04 +0800
Subject: [PATCH 017/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index e69de29b..f194327f 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -0,0 +1,2 @@
+# Matrix相机
+
From 2c6d676f4d60e1f82f2d544dc96d45628fc962e1 Mon Sep 17 00:00:00 2001
From: sloop
Date: Thu, 1 Sep 2016 04:51:26 +0800
Subject: [PATCH 018/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index f194327f..6f6c55e0 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -1,2 +1,14 @@
# Matrix相机
+### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
+### 相关文章: [自定义View目录](http://www.gcssloop.com/1970/01/CustomViewIndex/)
+
+
+
+
+## About Me
+
+### 作者微博: @GcsSloop
+
+
+
From cc43b6832a6c16c7990916f03d02b7e6a04efe8b Mon Sep 17 00:00:00 2001
From: sloop
Date: Fri, 2 Sep 2016 20:02:14 +0800
Subject: [PATCH 019/227] Update
---
LICENSE | 202 --------------------------------------------------------
1 file changed, 202 deletions(-)
delete mode 100644 LICENSE
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 8f71f43f..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "{}"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright {yyyy} {name of copyright owner}
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
From b4ee510630fe8e17dd4b16b28654f9437a296074 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Sat, 3 Sep 2016 04:11:43 +0800
Subject: [PATCH 020/227] Update
---
QuickChart/Matrix.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/QuickChart/Matrix.md b/QuickChart/Matrix.md
index 4f5c2905..5aecb593 100644
--- a/QuickChart/Matrix.md
+++ b/QuickChart/Matrix.md
@@ -9,8 +9,9 @@
| 前乘(pre) | preConcat preRotate preScale preSkew preTranslate | 前乘变换 |
| 后乘(post) | postConcat postRotate postScale postSkew postTranslate | 后乘变换 |
| 特殊方法 | setPolyToPoly setRectToRect rectStaysRect setSinCos | 一些特殊操作 |
-| 矩阵相关 | invert isAffine isIdentity | 求逆矩阵、 是否为仿射矩阵、 是否为单位矩阵 ... |
+| 矩阵相关 | invert isAffine(API21) isIdentity | 求逆矩阵、 是否为仿射矩阵、 是否为单位矩阵 ... |
## 相关文章
-* [Matrix原理](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B09%5DMatrix_Basic.md)
+* [Matrix原理](http://www.gcssloop.com/2015/02/Matrix_Basic/)
+* [Matrix详解](http://www.gcssloop.com/2015/02/Matrix_Method/)
From 9b71511ecb89da70f9773ca3abd52b1de567c8fd Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Mon, 5 Sep 2016 05:22:40 +0800
Subject: [PATCH 021/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 6f6c55e0..8b51ce7e 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -1,9 +1,9 @@
-# Matrix相机
+# Matrix-Camera
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### 相关文章: [自定义View目录](http://www.gcssloop.com/1970/01/CustomViewIndex/)
-
+前面用了两篇文章帮助大家了解了Matrix的作用、原理以及用法。本文继续讲解Matrix相关的知识,如题,本文的主角是Camera,翻译过来叫做相机,但这个并不是我们拍照用的相机。
## About Me
From 9e2d1bccf95c3be6f54f469ce273751695359846 Mon Sep 17 00:00:00 2001
From: sloop
Date: Mon, 5 Sep 2016 10:19:33 +0800
Subject: [PATCH 022/227] Update
---
README.md | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/README.md b/README.md
index a8126e62..949bad49 100644
--- a/README.md
+++ b/README.md
@@ -73,9 +73,6 @@
* [LeafLoading - 进度条](https://github.com/GcsSloop/LeafLoading)
* [Rotate3dAnimation - 3D旋转动画(修正版)](https://github.com/GcsSloop/Rotate3dAnimation)
-[](http://www.gcssloop.com/1970/01/contribute/)
-
-
## 传送门
通往异世界的传送门,请谨慎使用。
@@ -105,6 +102,6 @@
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
-
+
[▲ 回到顶部](#top)
From 93c58ff689f37e0ffe45bfb7eb0b55c2c5aa4114 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Tue, 6 Sep 2016 16:03:06 +0800
Subject: [PATCH 023/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 8b51ce7e..efaed864 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -3,7 +3,7 @@
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### 相关文章: [自定义View目录](http://www.gcssloop.com/1970/01/CustomViewIndex/)
-前面用了两篇文章帮助大家了解了Matrix的作用、原理以及用法。本文继续讲解Matrix相关的知识,如题,本文的主角是Camera,翻译过来叫做相机,但这个并不是我们拍照用的相机。
+看标题就知道本篇依旧属于Matrix,不过后面多了一个Camera,Camera我们都知道是相机的意思,不过本文中的相机并不是现实中的相机,而是专业给Matrix拍照的相机。
## About Me
From 412aea3e74b00831b9ee08637efb881860d52b56 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Wed, 7 Sep 2016 03:04:13 +0800
Subject: [PATCH 024/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index efaed864..359f4269 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -3,7 +3,27 @@
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### 相关文章: [自定义View目录](http://www.gcssloop.com/1970/01/CustomViewIndex/)
-看标题就知道本篇依旧属于Matrix,不过后面多了一个Camera,Camera我们都知道是相机的意思,不过本文中的相机并不是现实中的相机,而是专业给Matrix拍照的相机。
+本篇依旧属于Matrix,主要讲解Camera,Android下有很相机应用,其中的美颜相机更是不少,不过今天这个Camera可不是我们平时拍照的那个相机,而是graphic包下的Camera,专业给Matrix拍照的相机,不过既然是相机,作用都是类似的,主要是将3D的内容拍扁变成2D的内容。
+
+众所周知,我们的手机屏幕是一个2D的平面,所以也没办法直接显示3D的信息,因此我们看到的所有3D效果都是3D在2D平面的投影而已,而本文中的Camera主要作用就是这个,将3D信息转换为2D平面上的投影,我们应用中大多数3D效果都离不开这个类,而它将3D转换为2D投影的桥梁则是Matrix。
+
+
+
+## Camera常用方法表
+
+按照惯例,先放表。
+
+
+
+| 返回值 | 方法 |
+| ---- | ---------------------------------------- |
+| | Camera()
构造方法,创建一个空的Camera。 |
+| void | applyToCanvas(Canvas canvas)
将当前的Camera变换应用到Canvas。 |
+| | |
+| | |
+| | |
+
+
## About Me
From ec03e665324ce73ab2c21f4340c3f1957902ea17 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Thu, 8 Sep 2016 00:30:02 +0800
Subject: [PATCH 025/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 39 ++++++++++++++++------
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 359f4269..d816d3d4 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -1,28 +1,47 @@
# Matrix-Camera
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
-### 相关文章: [自定义View目录](http://www.gcssloop.com/1970/01/CustomViewIndex/)
+### 相关文章: [自定义View目录](http://www.gcssloop.com/customview/CustomViewIndex/)
本篇依旧属于Matrix,主要讲解Camera,Android下有很相机应用,其中的美颜相机更是不少,不过今天这个Camera可不是我们平时拍照的那个相机,而是graphic包下的Camera,专业给Matrix拍照的相机,不过既然是相机,作用都是类似的,主要是将3D的内容拍扁变成2D的内容。
-众所周知,我们的手机屏幕是一个2D的平面,所以也没办法直接显示3D的信息,因此我们看到的所有3D效果都是3D在2D平面的投影而已,而本文中的Camera主要作用就是这个,将3D信息转换为2D平面上的投影,我们应用中大多数3D效果都离不开这个类,而它将3D转换为2D投影的桥梁则是Matrix。
+众所周知,我们的手机屏幕是一个2D的平面,所以也没办法直接显示3D的信息,因此我们看到的所有3D效果都是3D在2D平面的投影而已,而本文中的Camera主要作用就是这个,将3D信息转换为2D平面上的投影,实际上这个类更像是一个操作Matrix的工具类,使用Camera和Matrix可以在不使用OpenGL的情况下制作出简单的3D效果。
## Camera常用方法表
-按照惯例,先放表。
+| 方法类别 | 相关 | 简介 |
+| ---- | ---------------------------------------- | -------------- |
+| 基本方法 | [save](#基本方法)、[restore](#基本方法) | 保存、 回滚 |
+| 常用方法 | getMatrix、applyToCanvas | 获取Matrix、应用到画布 |
+| 旋转 | rotate、rotateX、rotateY、rotateZ | 各种旋转 |
+| 平移 | translate | 位移 |
+| 相机位置 | setLocation、getLocationX、getLocationY、getLocationZ | 设置与获取相机位置 |
+> Camera的方法并不是特别多,很多内容与之前的讲解的Canvas和Matrix类似,不过又稍有不同,之前的画布操作和Matrix主要是作用于2D空间,而Camera则主要作用于3D空间。
-| 返回值 | 方法 |
-| ---- | ---------------------------------------- |
-| | Camera()
构造方法,创建一个空的Camera。 |
-| void | applyToCanvas(Canvas canvas)
将当前的Camera变换应用到Canvas。 |
-| | |
-| | |
-| | |
+## 基本方法
+
+基本方法就有两个`save` 和`restore`,主要作用为`保存当前状态和恢复到上一次保存的状态`,通常成对使用,常用格式如下:
+
+```java
+camera.save(); // 保存状态
+... // 具体操作
+camera.retore(); // 回滚状态
+```
+
+
+
+## 常用方法
+
+这两个方法是Camera中最基础也是最常用的方法。
+
+#### getMatrix
+
+获取当前状态下的矩阵。
From cc6abbe1c32339b13c1e2acbc7def3c9fd1fe539 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Fri, 9 Sep 2016 23:30:30 +0800
Subject: [PATCH 026/227] =?UTF-8?q?=E8=AE=A9Jitpack=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Course/jitpack-javadoc.md | 103 ++++++++++++++++++++++++++++++++++++++
1 file changed, 103 insertions(+)
create mode 100644 Course/jitpack-javadoc.md
diff --git a/Course/jitpack-javadoc.md b/Course/jitpack-javadoc.md
new file mode 100644
index 00000000..b5d1af09
--- /dev/null
+++ b/Course/jitpack-javadoc.md
@@ -0,0 +1,103 @@
+# 让JitPack支持Javadoc
+
+很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反应说**发到JitPack上的开源库没有文档注释,使用起来很不方便。**
+
+这算是我的失误,上一篇文章只是讲解了如何使用JitPack发布开源库,最终发布的只有arr(即编译好的动态链接库),不仅没有文档注释,也没有源码,本次就教大家如何在发布同时添加上源码和注释。
+
+由于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/
+
From 4f8d9422b9f65c9d9f66e1703394525062d955c3 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Fri, 9 Sep 2016 23:31:04 +0800
Subject: [PATCH 027/227] Update
---
Course/jitpack-javadoc.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Course/jitpack-javadoc.md b/Course/jitpack-javadoc.md
index b5d1af09..b396ca0f 100644
--- a/Course/jitpack-javadoc.md
+++ b/Course/jitpack-javadoc.md
@@ -1,4 +1,4 @@
-# 让JitPack支持Javadoc
+# 让JitPack支持Javadoc和源码
很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反应说**发到JitPack上的开源库没有文档注释,使用起来很不方便。**
From 105497a49e4010f5513fa050cc6bb17279799553 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Fri, 9 Sep 2016 23:36:38 +0800
Subject: [PATCH 028/227] Update
---
Course/jitpack-javadoc.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Course/jitpack-javadoc.md b/Course/jitpack-javadoc.md
index b396ca0f..1d1882d0 100644
--- a/Course/jitpack-javadoc.md
+++ b/Course/jitpack-javadoc.md
@@ -1,4 +1,4 @@
-# 让JitPack支持Javadoc和源码
+# 用JitPack发布时添加文档和源码
很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反应说**发到JitPack上的开源库没有文档注释,使用起来很不方便。**
From 4aa30d0e6372c275738c485c3e358c2a8c1aca94 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Fri, 9 Sep 2016 23:38:47 +0800
Subject: [PATCH 029/227] Update
---
Course/jitpack-javadoc.md | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/Course/jitpack-javadoc.md b/Course/jitpack-javadoc.md
index 1d1882d0..d43ac8a6 100644
--- a/Course/jitpack-javadoc.md
+++ b/Course/jitpack-javadoc.md
@@ -1,10 +1,8 @@
# 用JitPack发布时添加文档和源码
-很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反应说**发到JitPack上的开源库没有文档注释,使用起来很不方便。**
+很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反应说**发到JitPack上的开源库没有文档注释,使用起来很不方便**,这算是我的失误,上一篇文章只是讲解了如何使用JitPack发布开源库,最终发布的只有arr(即编译好的动态链接库),不仅没有文档注释(Javadoc),也没有源码(sources),本次就教大家如何在发布同时添加上注释和源码。
-这算是我的失误,上一篇文章只是讲解了如何使用JitPack发布开源库,最终发布的只有arr(即编译好的动态链接库),不仅没有文档注释,也没有源码,本次就教大家如何在发布同时添加上源码和注释。
-
-由于JitPack本身就是一个自定义Maven仓库,所以配置方式与Maven类似。
+**由于JitPack本身就是一个自定义Maven仓库,所以配置方式与Maven基本一样。**
From 609017e7fa8b7fe4889f9915393a08a7519c0fd6 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Fri, 9 Sep 2016 23:45:51 +0800
Subject: [PATCH 030/227] Update
---
Course/jitpack-javadoc.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Course/jitpack-javadoc.md b/Course/jitpack-javadoc.md
index d43ac8a6..611a9b1f 100644
--- a/Course/jitpack-javadoc.md
+++ b/Course/jitpack-javadoc.md
@@ -93,7 +93,7 @@ artifacts {
### 查看在线文档
-如果你在JitPack配置了文档支持,你可以在线查看。
+如果你在JitPack配置了文档和源码支持,在引用同时就能看到源码的文档,不仅如此,你也可以在线查看。
查看地址是 `https://jitpack.io/com/github/USER/REPO/VERSION/javadoc/`
From d5bdf70e9500e18374f9d399fef5c320b635207a Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Sat, 10 Sep 2016 01:57:36 +0800
Subject: [PATCH 031/227] Update
---
Course/jitpack-javadoc.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Course/jitpack-javadoc.md b/Course/jitpack-javadoc.md
index 611a9b1f..0adc2dd5 100644
--- a/Course/jitpack-javadoc.md
+++ b/Course/jitpack-javadoc.md
@@ -1,6 +1,6 @@
-# 用JitPack发布时添加文档和源码
+# 用JitPack发布时附加文档和源码
-很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反应说**发到JitPack上的开源库没有文档注释,使用起来很不方便**,这算是我的失误,上一篇文章只是讲解了如何使用JitPack发布开源库,最终发布的只有arr(即编译好的动态链接库),不仅没有文档注释(Javadoc),也没有源码(sources),本次就教大家如何在发布同时添加上注释和源码。
+很早之前写过一篇[用JitPack发布Android开源库](http://www.gcssloop.com/course/PublishLibraryByJitPack/)的文章,有小伙伴反馈说**发布到JitPack上的开源库没有文档注释,使用起来很不方便**,这是我的失误,上一篇文章只是讲解了如何使用JitPack发布开源库,最终发布的只有arr(即编译好的动态链接库),不仅没有文档注释(Javadoc),也没有源码(sources),本次就教大家如何在发布同时添加上注释和源码。
**由于JitPack本身就是一个自定义Maven仓库,所以配置方式与Maven基本一样。**
From bb5dc7eac153632fa0f9cce394205896d1867793 Mon Sep 17 00:00:00 2001
From: sloop
Date: Sat, 10 Sep 2016 16:32:52 +0800
Subject: [PATCH 032/227] Update
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 949bad49..d24d60d0 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@
* [在AndroidStudio中使用PlantUML(Win)](https://github.com/GcsSloop/AndroidNote/blob/master/Course/HowToUsePlantUMLInAS.md)
* [在AndroidStudio中使用PlantUML(Mac)](https://github.com/GcsSloop/AndroidNote/blob/master/Course/HowToUsePlantUMLInAS%5BMac%5D.md)
* [优雅的发布Android开源库(论JitPack的优越性)](https://github.com/GcsSloop/AndroidNote/blob/master/Course/ReleaseLibraryByJitPack.md)
+* [用JitPack发布时附加文档和源码](https://github.com/GcsSloop/AndroidNote/blob/master/Course/jitpack-javadoc.md)
******
From 5c67d6c3ad9e32b541e5f59bde6bdd333050c83d Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Sat, 10 Sep 2016 16:36:53 +0800
Subject: [PATCH 033/227] Update
---
Course/jitpack-javadoc.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Course/jitpack-javadoc.md b/Course/jitpack-javadoc.md
index 0adc2dd5..bf7176dd 100644
--- a/Course/jitpack-javadoc.md
+++ b/Course/jitpack-javadoc.md
@@ -99,3 +99,5 @@ artifacts {
例如我的一个开源库: https://jitpack.io/com/github/GcsSloop/ViewSupport/v1.2.2/javadoc/
+在线API文档样式:
+
\ No newline at end of file
From 293548e207b9d996db5de5929911657b91c873e7 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Sat, 10 Sep 2016 16:39:39 +0800
Subject: [PATCH 034/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 59 ++++++++++++++++++++--
1 file changed, 55 insertions(+), 4 deletions(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index d816d3d4..1d70668c 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -13,16 +13,51 @@
| 方法类别 | 相关 | 简介 |
| ---- | ---------------------------------------- | -------------- |
-| 基本方法 | [save](#基本方法)、[restore](#基本方法) | 保存、 回滚 |
+| 基本方法 | save、restore | 保存、 回滚 |
| 常用方法 | getMatrix、applyToCanvas | 获取Matrix、应用到画布 |
-| 旋转 | rotate、rotateX、rotateY、rotateZ | 各种旋转 |
+| 旋转 | rotat (API 21)、rotateX、rotateY、rotateZ | 各种旋转 |
| 平移 | translate | 位移 |
-| 相机位置 | setLocation、getLocationX、getLocationY、getLocationZ | 设置与获取相机位置 |
+| 相机位置 | setLocation (API 12)、getLocationX (API 16)、getLocationY (API 16)、getLocationZ (API 16) | 设置与获取相机位置 |
> Camera的方法并不是特别多,很多内容与之前的讲解的Canvas和Matrix类似,不过又稍有不同,之前的画布操作和Matrix主要是作用于2D空间,而Camera则主要作用于3D空间。
+## 基础概念
+
+在具体讲解方法之前,先补充几个基础概念,以便于后面理解。
+
+#### 3D坐标系
+
+我们Camera使用的3维坐标系是**左手坐标系,即左手手臂指向x轴正方向,四指弯曲指向y轴正方向,此时展开大拇指指向的方向是z轴正方向**。
+
+
+
+> 至于为什么要用左手坐标系呢?~~大概是因为赶工的时候右手不方便比划吧,大雾。~~实际上不同平台上使用的坐标系也有不同,有的是左手,有的是右手,貌似并没有统一的标准,只需要记住 Android 平台上面使用的是左手坐标系即可。
+
+不过此处需要注意一下 Android 中 2D坐标系 和 3D坐标系 的一些差别。
+
+| 坐标系 | 2D坐标系 | 3D坐标系 |
+| ------- | :---: | :----: |
+| 原点默认位置 | 左上角 | 左上角 |
+| X 轴默认方向 | 右 | 右 |
+| Y 轴默认方向 | 下 | 上 |
+| Z 轴默认方向 | 无 | 垂直屏幕向内 |
+
+3D坐标系在屏幕中各个坐标轴默认方向展示:
+
+> 注意y轴默认方向是向上,而2D则是向下。
+
+
+
+#### 三维投影
+
+**三维投影**是将三维空间中的点映射到二维平面上的方法。由于目前绝大多数图形数据的显示方式仍是二维的,因此三维投影的应用相当广泛,尤其是在计算机图形学,工程学和工程制图中。
+
+
+
+
+
## 基本方法
基本方法就有两个`save` 和`restore`,主要作用为`保存当前状态和恢复到上一次保存的状态`,通常成对使用,常用格式如下:
@@ -41,9 +76,25 @@ camera.retore(); // 回滚状态
#### getMatrix
-获取当前状态下的矩阵。
+```java
+void getMatrix (Matrix matrix)
+```
+
+计算当前状态下矩阵对应的状态,并将计算后的矩阵赋值给参数matrix。
+
+#### applyToCanvas
+
+```java
+void applyToCanvas (Canvas canvas)
+```
+
+计算当前状态下单矩阵对应的状态,并将计算后的矩阵应用到指定的canvas上。
+
+
+## 旋转方法
+旋转是Camera制作3D效果的核心(其实从),不过它制作出来的并不能算是真正的3D,而是伪3D,因为没有厚度,
## About Me
From 76a4a7e513d3d685d4a18b64e95e9bf8e51eb59b Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Sun, 11 Sep 2016 03:30:28 +0800
Subject: [PATCH 035/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 1d70668c..8a57de2c 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -50,11 +50,22 @@

+
+
#### 三维投影
-**三维投影**是将三维空间中的点映射到二维平面上的方法。由于目前绝大多数图形数据的显示方式仍是二维的,因此三维投影的应用相当广泛,尤其是在计算机图形学,工程学和工程制图中。
+> **三维投影**是将三维空间中的点映射到二维平面上的方法。由于目前绝大多数图形数据的显示方式仍是二维的,因此三维投影的应用相当广泛,尤其是在计算机图形学,工程学和工程制图中。
+
+三维投影一般有两种,**正交投影** 和 **透视投影**。
+
+* 正交投影就是我们数学上学过的 "正视图、正视图、侧视图、俯视图" 这些东西。
+* 透视投影则更像拍照片,符合**近大远小**的关系,有立体感,**我们此处使用的就是透视投影。**
+
+
+#### 摄像机
+如果你了解过Unity,那么你对摄像机这一个概念应该会理解的比较透彻。在一个虚拟的3D的立体空间中,由于我们无法直接用眼睛去观察这一个空间,所以要借助摄像机采集信息,制成2D影像供我们观察。简单来说,**摄像机就是我们观察虚拟3D空间的眼睛**。
From 39f6f69e4176d4defd636639a3300968c0af62c0 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Mon, 12 Sep 2016 07:05:14 +0800
Subject: [PATCH 036/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 112 +++++++++++++++++++--
1 file changed, 106 insertions(+), 6 deletions(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 8a57de2c..3525abea 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -15,8 +15,8 @@
| ---- | ---------------------------------------- | -------------- |
| 基本方法 | save、restore | 保存、 回滚 |
| 常用方法 | getMatrix、applyToCanvas | 获取Matrix、应用到画布 |
-| 旋转 | rotat (API 21)、rotateX、rotateY、rotateZ | 各种旋转 |
| 平移 | translate | 位移 |
+| 旋转 | rotat (API 21)、rotateX、rotateY、rotateZ | 各种旋转 |
| 相机位置 | setLocation (API 12)、getLocationX (API 16)、getLocationY (API 16)、getLocationZ (API 16) | 设置与获取相机位置 |
> Camera的方法并不是特别多,很多内容与之前的讲解的Canvas和Matrix类似,不过又稍有不同,之前的画布操作和Matrix主要是作用于2D空间,而Camera则主要作用于3D空间。
@@ -35,7 +35,7 @@
> 至于为什么要用左手坐标系呢?~~大概是因为赶工的时候右手不方便比划吧,大雾。~~实际上不同平台上使用的坐标系也有不同,有的是左手,有的是右手,貌似并没有统一的标准,只需要记住 Android 平台上面使用的是左手坐标系即可。
-不过此处需要注意一下 Android 中 2D坐标系 和 3D坐标系 的一些差别。
+**2D 和 3D 坐标是通过Matrix关联起来的,所以你可以认为两者是同一个坐标系,但又有差别,重点就是y轴方向不同。**
| 坐标系 | 2D坐标系 | 3D坐标系 |
| ------- | :---: | :----: |
@@ -46,7 +46,7 @@
3D坐标系在屏幕中各个坐标轴默认方向展示:
-> 注意y轴默认方向是向上,而2D则是向下。
+> 注意y轴默认方向是向上,而2D则是向下,另外本图不代表3D坐标系实际位置。

@@ -65,7 +65,13 @@
#### 摄像机
-如果你了解过Unity,那么你对摄像机这一个概念应该会理解的比较透彻。在一个虚拟的3D的立体空间中,由于我们无法直接用眼睛去观察这一个空间,所以要借助摄像机采集信息,制成2D影像供我们观察。简单来说,**摄像机就是我们观察虚拟3D空间的眼睛**。
+如果你学过Unity,那么你对摄像机这一个概念应该会有比较透彻的理解。在一个虚拟的3D的立体空间中,由于我们无法直接用眼睛去观察这一个空间,所以要借助摄像机采集信息,制成2D影像供我们观察。简单来说,**摄像机就是我们观察虚拟3D空间的眼睛**。
+
+**Android 上面观察View的摄像机默认位置在屏幕左上角,而且是距屏幕有一段距离的,假设灰色部分是手机屏幕,白色是上面的一个View,摄像机位置看起来大致就是下面这样子的(为了更好的展示摄像机的位置,做了一个空间转换效果的动图)。**
+
+
+
+> 摄像机的位置默认是 (0, 0, -576)。其中 -576= -8 x 72,虽然官方文档说距离屏幕的距离是 -8, 但经过测试实际距离是 -576 像素,当距离为 -10 的时候,实际距离为 -720 像素。不过这个数值72我也不明白是什么东西,我使用了3款手机测试,屏幕大小和像素密度均不同,但结果都是一样的,知道的小伙伴可以告诉我一声。
@@ -103,9 +109,103 @@ void applyToCanvas (Canvas canvas)
-## 旋转方法
+## 平移
+
+> **声明:以下示例中 Matrix 的平移均使用 postTranslate 来演示,实际情况中使用set、pre 或 post 需要视情况而定。**
+
+```java
+void translate (float x, float y, float z)
+```
+
+和2D平移类似,只不过是多出来了一个维度,从只能在2D平面上平移到在3D空间内平移,不过,此处仍有几个要点需要重点对待。
+
+
+
+#### 沿x轴平移
+
+
+``` java
+camera.translate(x, 0, 0);
+
+matrix.postTranslate(x, 0);
+```
+
+两者x轴同向,所以 Camera 和 Matrix 在沿x轴平移上是一致的。
+
+**结论:**
+
+一致是指平移方向和平移距离一致,在默认情况下,上面两种均可以让坐标系向右移动x个单位。
+
+
+
+#### 沿y轴平移
+
+这个就有点意思了,两个坐标系相互关联,但是两者的y轴方向是相反的,很容易把人搞迷糊。你可以这么玩:
+
+```java
+Camera camera = new Camera();
+camera.translate(0, 100, 0); // camera - 沿y轴正方向平移100像素
+
+Matrix matrix = new Matrix();
+camera.getMatrix(matrix);
+matrix.postTranslate(0,100); // matrix - 沿y轴正方向平移100像素
+
+Log.i(TAG, "Matrix: "+matrix.toShortString());
+```
+
+在上面这种写法,虽然用了5行代码,但是效果却和 `Matrix matrix = new Matrix();` 一样,结果都是单位矩阵。而且看起来貌似没有啥问题,毕竟两次平移都是正向100。(~~如果遇见不懂技术的领导嫌你写代码量少,你可以这样多写几遍,反正一般人是看不出问题的。~~)
+
+```
+Matrix: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
+```
+
+**结论:**
+
+由于两者y轴相反,所以 `camera.translate(0, -y, 0);` 与 `matrix.postTranslate(0, y);`平移方向和距离一致,在默认情况下,这两种方法均可以让坐标系向下移动y个单位。
+
+
+
+#### 沿z轴平移
+
+这个不仅有趣,还容易蒙逼,上面两种情况再怎么闹腾也只是在2D平面上,而z轴的出现则让其有了空间感。
+
+**当View和摄像机在同一条直线上时:** 此时沿z轴平移相当于缩放的效果,缩放中心为摄像机所在(x, y)坐标,当View接近摄像机时,看起来会变大,远离摄像机时,看起来会变小,**近大远小**。
+
+**当View和摄像机不在同一条直线上时:** 当View远离摄像机的时候,View在缩小的同时也在不断接近摄像机在屏幕投影位置(通常情况下为Z轴,在平面上表现为接近坐标原点)。相反,当View接近摄像机的时候,View在放大的同时会远离摄像机在屏幕投影位置。
+
+
+
+我知道,这样说你们肯定是蒙逼的,话说为啥远离摄像机的时候会接近摄像机在屏幕投影位置,而接近摄像机的时候会远离摄像机在屏幕投影位置 (´・_・`),肯定觉得我在逗你们玩,完全是前后矛盾,逻辑都不通,不过这个在这里的确是不矛盾的,因为远离是在3D空间里的情况,而接近只是在2D空间的投影,看下图。
+
+> 假设大矩形是手机屏幕,摄像机位于屏幕左上角,请注意上面View与摄像机的距离以及下方View的大小以及距离左上角的距离。
+
+
+
+至于为什么会这样,因为我们人眼视觉就是这样的,当我们看向远方的时候,视线最终都会消失在视平线上,如果你站在两条平行线中间,看起来它们会在远方(视平线上)相交,虽然在3D空间上两者距离不变,但在2D投影上却是越来越接近,如下图(图片来自网络):
+
+
+
+**结论:**
+
+关于3D效果的平移说起来比较麻烦,但你可以自己实际的体验一下,毕竟我们是生活在3D空间的,拿一张纸片来模拟View,用眼睛当做摄像机,在眼前来回移动纸片,多试几次大致就明白是怎么回事了。
+
+| 平移 | 重点内容 |
+| :--: | ----------- |
+| x轴 | 2D 和 3D 相同。 |
+| y轴 | 2D 和 3D 相反。 |
+| z轴 | 近大远小、视线相交。 |
+
+
+
+## 旋转
+
+旋转是Camera制作3D效果的核心,不过它制作出来的并不能算是真正的3D,而是伪3D,因为View是没有厚度的,想要展示出立体效果一般需要两个View进行配合,
+
+
+
+## 核心要点
+
-旋转是Camera制作3D效果的核心(其实从),不过它制作出来的并不能算是真正的3D,而是伪3D,因为没有厚度,
## About Me
From 49eb89ca1f35f8b4e99fd664f4728d99c701816f Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Tue, 13 Sep 2016 05:03:26 +0800
Subject: [PATCH 037/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 26 +++++++++++++++++-----
1 file changed, 20 insertions(+), 6 deletions(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 3525abea..61f49141 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -3,7 +3,7 @@
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### 相关文章: [自定义View目录](http://www.gcssloop.com/customview/CustomViewIndex/)
-本篇依旧属于Matrix,主要讲解Camera,Android下有很相机应用,其中的美颜相机更是不少,不过今天这个Camera可不是我们平时拍照的那个相机,而是graphic包下的Camera,专业给Matrix拍照的相机,不过既然是相机,作用都是类似的,主要是将3D的内容拍扁变成2D的内容。
+本篇依旧属于Matrix,主要讲解Camera,Android下有很多相机应用,其中的美颜相机更是不少,不过今天这个Camera可不是我们平时拍照的那个相机,而是graphic包下的Camera,专业给Matrix拍照的相机,不过既然是相机,作用都是类似的,主要是将3D的内容拍扁变成2D的内容。
众所周知,我们的手机屏幕是一个2D的平面,所以也没办法直接显示3D的信息,因此我们看到的所有3D效果都是3D在2D平面的投影而已,而本文中的Camera主要作用就是这个,将3D信息转换为2D平面上的投影,实际上这个类更像是一个操作Matrix的工具类,使用Camera和Matrix可以在不使用OpenGL的情况下制作出简单的3D效果。
@@ -11,12 +11,12 @@
## Camera常用方法表
-| 方法类别 | 相关 | 简介 |
+| 方法类别 | 相关API | 简介 |
| ---- | ---------------------------------------- | -------------- |
| 基本方法 | save、restore | 保存、 回滚 |
| 常用方法 | getMatrix、applyToCanvas | 获取Matrix、应用到画布 |
| 平移 | translate | 位移 |
-| 旋转 | rotat (API 21)、rotateX、rotateY、rotateZ | 各种旋转 |
+| 旋转 | rotat (API 12)、rotateX、rotateY、rotateZ | 各种旋转 |
| 相机位置 | setLocation (API 12)、getLocationX (API 16)、getLocationY (API 16)、getLocationZ (API 16) | 设置与获取相机位置 |
> Camera的方法并不是特别多,很多内容与之前的讲解的Canvas和Matrix类似,不过又稍有不同,之前的画布操作和Matrix主要是作用于2D空间,而Camera则主要作用于3D空间。
@@ -175,9 +175,9 @@ Matrix: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
-我知道,这样说你们肯定是蒙逼的,话说为啥远离摄像机的时候会接近摄像机在屏幕投影位置,而接近摄像机的时候会远离摄像机在屏幕投影位置 (´・_・`),肯定觉得我在逗你们玩,完全是前后矛盾,逻辑都不通,不过这个在这里的确是不矛盾的,因为远离是在3D空间里的情况,而接近只是在2D空间的投影,看下图。
+我知道,这样说你们肯定是蒙逼的,话说为啥远离摄像机的时候会接近摄像机在屏幕投影位置(´・_・`),肯定觉得我在逗你们玩,完全是前后矛盾,逻辑都不通,不过这个在这里的确是不矛盾的,因为远离是在3D空间里的情况,而接近只是在2D空间的投影,看下图。
-> 假设大矩形是手机屏幕,摄像机位于屏幕左上角,请注意上面View与摄像机的距离以及下方View的大小以及距离左上角的距离。
+> 假设大矩形是手机屏幕,白色小矩形是View,摄像机位于屏幕左上角,请注意上面View与摄像机的距离以及下方View的大小以及距离左上角(摄像机在屏幕投影位置)的距离。

@@ -199,7 +199,21 @@ Matrix: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
## 旋转
-旋转是Camera制作3D效果的核心,不过它制作出来的并不能算是真正的3D,而是伪3D,因为View是没有厚度的,想要展示出立体效果一般需要两个View进行配合,
+旋转是Camera制作3D效果的核心,不过它制作出来的并不能算是真正的3D,而是伪3D,因为View是没有厚度的。
+
+```java
+// (API 12) 可以控制View同时绕x,y,z轴旋转,可以由下面几种方法复合而来。
+void rotate (float x, float y, float z);
+
+// 控制View绕单个坐标轴旋转
+void rotateX (float deg);
+void rotateY (float deg);
+void rotateZ (float deg);
+```
+
+这个东西说理论也不好理解,直接上图:
+
+
From 062c1655fc61731b07800fec7e147f6103f36632 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Wed, 14 Sep 2016 01:25:21 +0800
Subject: [PATCH 038/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 61f49141..5f604bd4 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -1,4 +1,4 @@
-# Matrix-Camera
+# Matrix Camera
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### 相关文章: [自定义View目录](http://www.gcssloop.com/customview/CustomViewIndex/)
@@ -213,6 +213,11 @@ void rotateZ (float deg);
这个东西说理论也不好理解,直接上图:
+
+
+
+
+
From 10966468e541965be2fc59165624aeccc50b9341 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Wed, 14 Sep 2016 04:28:55 +0800
Subject: [PATCH 039/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 297 ++++++++++++++++++++-
1 file changed, 295 insertions(+), 2 deletions(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 5f604bd4..4557217c 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -211,7 +211,7 @@ void rotateY (float deg);
void rotateZ (float deg);
```
-这个东西说理论也不好理解,直接上图:
+这个东西瞎扯理论也不好理解,直接上图:
@@ -219,10 +219,303 @@ void rotateZ (float deg);
+**以上三张图分别为,绕x轴,y轴,z轴旋转的情况,至于为什么没有显示z轴,是因为z轴是垂直于手机屏幕的,在屏幕上的投影就是一个点。**
+关于旋转,有以下几点需要注意:
+#### 默认旋转中心
-## 核心要点
+旋转中心默认是坐标原点,对于图片来说就是左上角位置。
+
+#### 如何控制旋转中心
+
+我们都知道,在2D中,不论是旋转,错切还是缩放都是能够指定操作中心点位置的,但是在3D中却没有默认的方法,如果我们想要让图片围绕中心点旋转怎么办? 这就要使用到我们在[Matrix原理](http://www.gcssloop.com/customview/Matrix_Basic)提到过的方法,虽然当时因为有更好的选择方案,并不提倡这样做:
+
+```java
+Matrix temp = new Matrix(); // 临时Matrix变量
+this.getMatrix(temp); // 获取Matrix
+temp.preTranslate(-centerX, -centerY); // 使用pre将旋转中心移动到和Camera位置相同。
+temp.postTranslate(centerX, centerY); // 使用post将图片(View)移动到原来的位置
+```
+
+#### 官方示例
+
+说到3D旋转,最经典的应该就是ApiDemo里面的 [Rotate3dAnimation](https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/animation/Rotate3dAnimation.java) 了,见过不少博文都是根据Rotate3dAnimation修改的效果,这是一个非常经典的例子,鉴于代码也不长,就贴在这里和大家一起品鉴一下。
+
+```java
+public class Rotate3dAnimation extends Animation {
+ private final float mFromDegrees;
+ private final float mToDegrees;
+ private final float mCenterX;
+ private final float mCenterY;
+ private final float mDepthZ;
+ private final boolean mReverse;
+ private Camera mCamera;
+ /**
+ * 创建一个绕y轴旋转的3D动画效果,旋转过程中具有深度调节,可以指定旋转中心。
+ *
+ * @param fromDegrees 起始时角度
+ * @param toDegrees 结束时角度
+ * @param centerX 旋转中心x坐标
+ * @param centerY 旋转中心y坐标
+ * @param depthZ 最远到达的z轴坐标
+ * @param reverse true 表示由从0到depthZ,false相反
+ */
+ public Rotate3dAnimation(float fromDegrees, float toDegrees,
+ float centerX, float centerY, float depthZ, boolean reverse) {
+ mFromDegrees = fromDegrees;
+ mToDegrees = toDegrees;
+ mCenterX = centerX;
+ mCenterY = centerY;
+ mDepthZ = depthZ;
+ mReverse = reverse;
+ }
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+ mCamera = new Camera();
+ }
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ final float fromDegrees = mFromDegrees;
+ float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
+ final float centerX = mCenterX;
+ final float centerY = mCenterY;
+ final Camera camera = mCamera;
+ final Matrix matrix = t.getMatrix();
+ camera.save();
+
+ // 调节深度
+ if (mReverse) {
+ camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
+ } else {
+ camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
+ }
+
+ // 绕y轴旋转
+ camera.rotateY(degrees);
+
+ camera.getMatrix(matrix);
+ camera.restore();
+
+ // 调节中心点
+ matrix.preTranslate(-centerX, -centerY);
+ matrix.postTranslate(centerX, centerY);
+ }
+}
+```
+
+
+
+可以看到,短短的几十行代码就完成了,而核心代码(有注释部分)仅仅几行而已,简洁易懂。不过呢,这一份代码依旧是一份未完成的代码(不然怎么叫ApiDemo呢?),并且很多人不知道怎么修改。
+
+不知诸位在使用的时候可否发现了一个问题,同一份代码在不同手机上显示效果也是不同的,在像素密度较低的手机上,旋转效果比较正常,但是在像素密度较高的手机上显示效果则会很夸张,具体会怎样的,下面就来看一下具体效果。
+
+
+
+可以看到,图片不仅因为形变失真,而且在中间一段因为形变过大导致图片无法显示,当然了,单个手机失真,你可以用`depthZ`忽悠过去,当 `depthZ` 设置的数值比较大大时候,图像在翻转同时会远离摄像头,距离远离,失真就不会显得很严重,不过这仍掩盖不了在不同手机上显示效果不同。
+
+**如何解决这一问题呢?**
+
+想要解决其实也不难,只要修改两个数值就可以了,这两个数值就是在Matrix中一直被众多开发者忽略的 `MPERSP_0` 和 `MPERSP_1`
+
+
+
+下面是修改后的代码(重点部分都已经标注出来了):
+
+```java
+public class Rotate3dAnimation extends Animation {
+ private final float mFromDegrees;
+ private final float mToDegrees;
+ private final float mCenterX;
+ private final float mCenterY;
+ private final float mDepthZ;
+ private final boolean mReverse;
+ private Camera mCamera;
+ float scale = 1; // <------- 像素密度
+
+ /**
+ * 创建一个绕y轴旋转的3D动画效果,旋转过程中具有深度调节,可以指定旋转中心。
+ * @param context <------- 添加上下文,为获取像素密度准备
+ * @param fromDegrees 起始时角度
+ * @param toDegrees 结束时角度
+ * @param centerX 旋转中心x坐标
+ * @param centerY 旋转中心y坐标
+ * @param depthZ 最远到达的z轴坐标
+ * @param reverse true 表示由从0到depthZ,false相反
+ */
+ public Rotate3dAnimation(Context context, float fromDegrees, float toDegrees,
+ float centerX, float centerY, float depthZ, boolean reverse) {
+ mFromDegrees = fromDegrees;
+ mToDegrees = toDegrees;
+ mCenterX = centerX;
+ mCenterY = centerY;
+ mDepthZ = depthZ;
+ mReverse = reverse;
+
+ // 获取手机像素密度 (即dp与px的比例)
+ scale = context.getResources().getDisplayMetrics().density;
+ }
+
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+ mCamera = new Camera();
+ }
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ final float fromDegrees = mFromDegrees;
+ float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
+ final float centerX = mCenterX;
+ final float centerY = mCenterY;
+ final Camera camera = mCamera;
+ final Matrix matrix = t.getMatrix();
+ camera.save();
+
+ // 调节深度
+ if (mReverse) {
+ camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
+ } else {
+ camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
+ }
+
+ // 绕y轴旋转
+ camera.rotateY(degrees);
+
+ camera.getMatrix(matrix);
+ camera.restore();
+
+ // 修正失真,主要修改 MPERSP_0 和 MPERSP_1
+ float[] mValues = new float[9];
+ matrix.getValues(mValues); //获取数值
+ mValues[6] = mValues[6]/scale; //数值修正
+ mValues[7] = mValues[7]/scale; //数值修正
+ matrix.setValues(mValues); //重新赋值
+
+ // 调节中心点
+ matrix.preTranslate(-centerX, -centerY);
+ matrix.postTranslate(centerX, centerY);
+ }
+}
+```
+
+修改后效果:
+
+
+
+
+
+上下对比差别还是很大的,顺便附上测试代码吧,layout文件就不写了,随便放一个ImageView就行了。
+
+```java
+setContentView(R.layout.activity_test_camera_rotate2);
+ImageView view = (ImageView) findViewById(R.id.img);
+assert view != null;
+view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 计算中心点(这里是使用view的中心作为旋转的中心点)
+ final float centerX = v.getWidth() / 2.0f;
+ final float centerY = v.getHeight() / 2.0f;
+
+ //括号内参数分别为(上下文,开始角度,结束角度,x轴中心点,y轴中心点,深度,是否扭曲)
+ final Rotate3dAnimation rotation = new Rotate3dAnimation(MainActivity.this, 0, 180, centerX, centerY, 0f, true, 2);
+
+ rotation.setDuration(3000); //设置动画时长
+ rotation.setFillAfter(true); //保持旋转后效果
+ rotation.setInterpolator(new LinearInterpolator()); //设置插值器
+ v.startAnimation(rotation);
+ }
+});
+```
+
+
+
+## 相机位置
+
+我们可以使用translate和rotate来控制拍摄对象,也可以移动相机自身的位置,不过这些方法并不常用(看添加时间就知道啦)。
+
+```java
+void setLocation (float x, float y, float z); // (API 12) 设置相机位置,默认位置是(0, 0, -8)
+
+float getLocationX (); // (API 16) 获取相机位置的x坐标,下同
+float getLocationY ();
+float getLocationZ ();
+```
+
+我们知道近大远小,而物体之间的距离是相对的,让物体远离相机和让相机远离物体结果是一样的,实际上设置相机位置基本可以使用`translate`替代。
+
+虽然设置相机位置用处并不大,但还是要提几点注意事项:
+
+#### 相机和View的z轴距离不能为0
+
+这个比较容易理解,当你把一个物体和相机放在同一个位置的时候,相机是拍摄不到这个物体的,正如你拿一张卡片放在手机侧面,摄像头是拍摄不到的。
+
+#### 虚拟相机前后均可以拍摄
+
+当View不断接近摄像机并越过摄像机位置时,仍能看到View,并且View大小会随着距离摄像机的位置越来越远而逐渐变小,你可以理解为它有前置摄像头和后置摄像头。
+
+#### 摄像机右移等于View左移
+
+View的状态只取决于View和摄像机之间的相对位置,不过由于单位不同,摄像机平移一个单位等于View平移72个像素。下面两段代码是等价的:
+
+```java
+Camera camera = new Camera();
+camera.setLocation(1,0,-8); // 摄像机默认位置是(0, 0, -8)
+Matrix matrix = new Matrix();
+camera.getMatrix(matrix);
+Log.e(TAG, "location: "+matrix.toShortString() );
+
+Camera camera2 = new Camera();
+camera2.translate(-72,0,0);
+Matrix matrix2 = new Matrix();
+camera2.getMatrix(matrix2);
+Log.e(TAG, "translate: "+matrix2.toShortString() );
+```
+
+结果:
+
+```
+location: [1.0, 0.0, -72.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
+translate: [1.0, 0.0, -72.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0
+```
+
+#### 要点
+
+* View显示状态取决于View和摄像机之间的相对位置
+* View和相机的Z轴距离不能为0
+
+
+
+**小技巧:关于摄像机和View的位置,你可以打开手机后置摄像头,拿一张卡片来回的转动平移或者移动手机位置,观察卡片在屏幕上的变化,**
+
+
+
+## 总结
+
+本篇主要讲解了关于Camera和Matrix的一些基础知识,Camera运用得当的话是能够制造出很多炫酷的效果的,我这里算是抛砖引玉,推荐一些比较炫酷的控件。
+
+#### [从零开始打造一个Android 3D立体旋转容器](http://blog.csdn.net/mr_immortalz/article/details/51918560)
+
+
+
+
+
+#### [FlipShare](https://github.com/JeasonWong/FlipShare)
+
+
From 2a423484e1073a3f63e085dfc6865c5f2af58343 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Wed, 14 Sep 2016 04:43:13 +0800
Subject: [PATCH 040/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 4557217c..2c8fdc07 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -313,7 +313,7 @@ public class Rotate3dAnimation extends Animation {

-可以看到,图片不仅因为形变失真,而且在中间一段因为形变过大导致图片无法显示,当然了,单个手机失真,你可以用`depthZ`忽悠过去,当 `depthZ` 设置的数值比较大大时候,图像在翻转同时会远离摄像头,距离远离,失真就不会显得很严重,不过这仍掩盖不了在不同手机上显示效果不同。
+可以看到,图片不仅因为形变失真,而且在中间一段因为形变过大导致图片无法显示,当然了,单个手机失真,你可以用`depthZ`忽悠过去,当 `depthZ` 设置的数值比较大大时候,图像在翻转同时会远离摄像头,距离比较远,失真就不会显得很严重,但这仍掩盖不了在不同手机上显示效果不同。
**如何解决这一问题呢?**
@@ -525,3 +525,11 @@ translate: [1.0, 0.0, -72.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0
+
+
+## 参考资料
+
+[Camera](https://developer.android.com/reference/android/graphics/Camera.html)
+[FlipShare](https://github.com/JeasonWong/FlipShare)
+[从零开始打造一个Android 3D立体旋转容器](http://blog.csdn.net/mr_immortalz/article/details/51918560)
+
From 58f27f31e71ccece41016b335c978a03128cba58 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Wed, 14 Sep 2016 05:26:31 +0800
Subject: [PATCH 041/227] Update
---
CustomView/Advance/[11]Matrix_3D_Camera.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CustomView/Advance/[11]Matrix_3D_Camera.md b/CustomView/Advance/[11]Matrix_3D_Camera.md
index 2c8fdc07..0e4c83b6 100644
--- a/CustomView/Advance/[11]Matrix_3D_Camera.md
+++ b/CustomView/Advance/[11]Matrix_3D_Camera.md
@@ -3,7 +3,7 @@
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
### 相关文章: [自定义View目录](http://www.gcssloop.com/customview/CustomViewIndex/)
-本篇依旧属于Matrix,主要讲解Camera,Android下有很多相机应用,其中的美颜相机更是不少,不过今天这个Camera可不是我们平时拍照的那个相机,而是graphic包下的Camera,专业给Matrix拍照的相机,不过既然是相机,作用都是类似的,主要是将3D的内容拍扁变成2D的内容。
+本篇依旧属于Matrix,主要讲解Camera,Android下有很多相机应用,其中的美颜相机更是不少,不过今天这个Camera可不是我们平时拍照的那个相机,而是graphic包下的Camera,专业给View拍照的相机,不过既然是相机,作用都是类似的,主要是将3D的内容拍扁变成2D的内容。
众所周知,我们的手机屏幕是一个2D的平面,所以也没办法直接显示3D的信息,因此我们看到的所有3D效果都是3D在2D平面的投影而已,而本文中的Camera主要作用就是这个,将3D信息转换为2D平面上的投影,实际上这个类更像是一个操作Matrix的工具类,使用Camera和Matrix可以在不使用OpenGL的情况下制作出简单的3D效果。
From aad689acb1a238c6a14f4d8101c847a890048d4d Mon Sep 17 00:00:00 2001
From: sloop
Date: Thu, 15 Sep 2016 04:56:43 +0800
Subject: [PATCH 042/227] Update
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d24d60d0..78a306c2 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@
* [安卓自定义View进阶 - Path玩出花样(PathMeasure)](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B08%5DPath_Play.md)
* [安卓自定义View进阶 - Matrix原理](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B09%5DMatrix_Basic.md)
* [安卓自定义View进阶 - Matrix详解](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B10%5DMatrix_Method.md)
-
+ * [安卓自定义View进阶 - Matrix Camera](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B11%5DMatrix_3D_Camera.md)
******
From 3c1e331638443da5b87045ff1ec7227f65e03f41 Mon Sep 17 00:00:00 2001
From: sloop
Date: Thu, 15 Sep 2016 05:00:20 +0800
Subject: [PATCH 043/227] Update
---
CustomView/README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CustomView/README.md b/CustomView/README.md
index 16f99046..084727ed 100644
--- a/CustomView/README.md
+++ b/CustomView/README.md
@@ -39,6 +39,7 @@
+
### 作者微博: [@GcsSloop](http://weibo.com/GcsSloop)
From a488e3de362f8eb7b7ab784e6fe3a6c8fd3fd211 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Fri, 16 Sep 2016 20:30:20 +0800
Subject: [PATCH 044/227] Update
---
CustomView/Advance/[12]TouchEvent.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 CustomView/Advance/[12]TouchEvent.md
diff --git a/CustomView/Advance/[12]TouchEvent.md b/CustomView/Advance/[12]TouchEvent.md
new file mode 100644
index 00000000..fd411729
--- /dev/null
+++ b/CustomView/Advance/[12]TouchEvent.md
@@ -0,0 +1,2 @@
+# 事件分发
+
From 6592d90ad5b3f82020868bb55b4986ab3978745c Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Sun, 18 Sep 2016 04:46:52 +0800
Subject: [PATCH 045/227] Update
---
Tools/MarkdownEditor.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 Tools/MarkdownEditor.md
diff --git a/Tools/MarkdownEditor.md b/Tools/MarkdownEditor.md
new file mode 100644
index 00000000..c124a1b5
--- /dev/null
+++ b/Tools/MarkdownEditor.md
@@ -0,0 +1,2 @@
+# Markdown编辑器推荐
+
From 33ced75cea84e9aab57f47db56f6b4559e5dc72f Mon Sep 17 00:00:00 2001
From: sloop
Date: Mon, 19 Sep 2016 22:46:28 +0800
Subject: [PATCH 046/227] Update
---
Tools/MarkdownEditor.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/Tools/MarkdownEditor.md b/Tools/MarkdownEditor.md
index c124a1b5..356129d6 100644
--- a/Tools/MarkdownEditor.md
+++ b/Tools/MarkdownEditor.md
@@ -1,2 +1,3 @@
# Markdown编辑器推荐
+markdown是一个非常棒的书写语言,深受程序员喜爱。
From 427bcc270276cded7a9e0bcd72484700ee9d6463 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Tue, 20 Sep 2016 00:03:38 +0800
Subject: [PATCH 047/227] Update
---
CustomView/Advance/[02]Canvas_BasicGraphics.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CustomView/Advance/[02]Canvas_BasicGraphics.md b/CustomView/Advance/[02]Canvas_BasicGraphics.md
index bd60cebc..b0b2eeff 100644
--- a/CustomView/Advance/[02]Canvas_BasicGraphics.md
+++ b/CustomView/Advance/[02]Canvas_BasicGraphics.md
@@ -279,7 +279,7 @@ useCenter // 是否使用中心
相比于使用椭圆,我们还是使用正圆比较多的,使用正圆展示一下效果:
```
- RectF rectF = new RectF(100,100,800,400);
+ RectF rectF = new RectF(100,100,600,600);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);
@@ -290,7 +290,7 @@ useCenter // 是否使用中心
//-------------------------------------
- RectF rectF2 = new RectF(100,600,800,900);
+ RectF rectF2 = new RectF(100,700,600,1200);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);
From a8fb17990858636a7be235206ca241f9f8362714 Mon Sep 17 00:00:00 2001
From: sloop
Date: Wed, 21 Sep 2016 00:30:25 +0800
Subject: [PATCH 048/227] Update
---
ChaosCrystal/Markdowm/README.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 ChaosCrystal/Markdowm/README.md
diff --git a/ChaosCrystal/Markdowm/README.md b/ChaosCrystal/Markdowm/README.md
new file mode 100644
index 00000000..7d82df34
--- /dev/null
+++ b/ChaosCrystal/Markdowm/README.md
@@ -0,0 +1 @@
+# Markdown
From 0d6c4478158797d149b0a19c2ccb2716d7a6b711 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Thu, 22 Sep 2016 00:31:05 +0800
Subject: [PATCH 049/227] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E7=AB=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CustomView/Advance/[01]CustomViewProcess.md | 88 ++++++-------
.../Advance/[02]Canvas_BasicGraphics.md | 8 +-
CustomView/Advance/[03]Canvas_Convert.md | 74 +++++------
CustomView/Advance/[04]Canvas_PictureText.md | 122 +++++++++---------
CustomView/Advance/[05]Path_Basic.md | 114 ++++++++--------
CustomView/Advance/[06]Path_Bezier.md | 85 ++++++------
CustomView/Advance/[07]Path_Over.md | 120 ++++++++---------
CustomView/Advance/[08]Path_Play.md | 100 +++++++-------
CustomView/Advance/[09]Matrix_Basic.md | 38 +++---
9 files changed, 374 insertions(+), 375 deletions(-)
diff --git a/CustomView/Advance/[01]CustomViewProcess.md b/CustomView/Advance/[01]CustomViewProcess.md
index f6687bb5..5166f06f 100644
--- a/CustomView/Advance/[01]CustomViewProcess.md
+++ b/CustomView/Advance/[01]CustomViewProcess.md
@@ -16,17 +16,17 @@
## 一.自定义View分类
**我将自定义View分为了两类(sloop个人分类法,非官方):**
-
+
### 1.自定义ViewGroup
-
+
**自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout,包含有子View。**
-
+
> 例如:应用底部导航条中的条目,一般都是上面图标(ImageView),下面文字(TextView),那么这两个就可以用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片,使用起来会更加方便。
-
+
### 2.自定义View
-
+
**在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View,不包含子View。**
-
+
> 例如:制作一个支持自动加载网络图片的ImageView,制作图表等。
**PS: 自定义View在大多数情况下都有替代方案,利用图片或者组合动画来实现,但是使用后者可能会面临内存耗费过大,制作麻烦更诸多问题。**
@@ -47,9 +47,9 @@ View的构造函数有四种重载分别如下:
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) {
@@ -111,21 +111,21 @@ View的构造函数有四种重载分别如下:
**测量模式一共有三种, 被定义在 Android 中的 View 类的一个内部类View.MeasureSpec中:**
-模式 | 二进制数值 | 描述
------------ |:----------:| ---
-UNSPECIFIED | 00 | 默认值,父控件没有给子view任何限制,子View可以设置为任意大小。
-EXACTLY | 01 | 表示父控件已经确切的指定了子View的大小。
-AT_MOST | 10 | 表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。
+| 模式 | 二进制数值 | 描述 |
+| ----------- | :---: | -------------------------------------- |
+| 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
+| 模式名称 | 模式数值 | 实际数值 |
+| ----------- | ---: | ------------------------------ |
+| UNSPECIFIED | 00 | 000000000000000000001111011000 |
+| EXACTLY | 01 | 000000000000000000001111011000 |
+| AT_MOST | 10 | 000000000000000000001111011000 |
**PS: 实际上关于上面的东西了解即可,在实际运用之中只需要记住有三种模式,用 MeasureSpec 的 getSize是获取数值, getMode是获取模式即可。**
@@ -136,7 +136,7 @@ AT_MOST | 10 | 000000000000000000001111011000
### 3.确定View大小(onSizeChanged)
这个函数在视图大小发生改变时调用。
-
+
**Q: 在测量完View并使用setMeasuredDimension函数之后View的大小基本上已经确定了,那么为什么还要再次确定View的大小呢?**
**A: 这是因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。**
@@ -157,32 +157,32 @@ onSizeChanged如下:
### 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();
+| 名称 | 说明 | 对应的函数 |
+| ---- | ----------------- | ------------ |
+| 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/%5B1%5DCoordinateSystem.md) 这篇文章。
+具体可以参考 [坐标系](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Base/%5B01%5DCoordinateSystem.md) 这篇文章。

PS:关于onLayout这个函数在讲解自定义ViewGroup的时候会详细讲解。
-
-
+
+
========
### 5.绘制内容(onDraw)
-
+
onDraw是实际绘制的部分,也就是我们真正关心的部分,使用的是Canvas绘图。
``` java
@Override
@@ -196,7 +196,7 @@ b | View底部距父View顶部的距离 | getBottom();
### 6.对外提供操作方法和监听回调
自定义完View之后,一般会对外暴露一些接口,用于控制View的状态等,或者监听View的变化.
-
+
本内容会在后续文章中以实例的方式进讲解。
************
@@ -207,20 +207,20 @@ b | View底部距父View顶部的距离 | getBottom();
> PS :实际上ViewGroup是View的一个子类。
-类别 | 继承自 | 特点
---------- | --------------------- | ------------
-View | View SurfaceView 等 | 不含子View
-ViewGroup | ViewGroup xxLayout等 | 包含子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某些状态。
+| 步骤 | 关键字 | 作用 |
+| ---- | ------------- | ---------------------------- |
+| 1 | 构造函数 | View初始化 |
+| 2 | onMeasure | 测量View大小 |
+| 3 | onSizeChanged | 确定View大小 |
+| 4 | onLayout | 确定子View布局(自定义View包含子View时有用) |
+| 5 | onDraw | 实际绘制内容 |
+| 6 | 提供接口 | 控制View或监听View某些状态。 |
diff --git a/CustomView/Advance/[02]Canvas_BasicGraphics.md b/CustomView/Advance/[02]Canvas_BasicGraphics.md
index b0b2eeff..28820e87 100644
--- a/CustomView/Advance/[02]Canvas_BasicGraphics.md
+++ b/CustomView/Advance/[02]Canvas_BasicGraphics.md
@@ -3,7 +3,7 @@
### 作者微博: [@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/%5B1%5DCustomViewProcess.md)中我们了解自定义View相关的基本知识,不过,这些东西依旧还是理论,并不能**拿来(zhuang)用(B)**, 这一次我们就了解一些**能(zhaung)用(B)**的东西。
+在上一篇[自定义View分类与流程](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B01%5DCustomViewProcess.md)中我们了解自定义View相关的基本知识,不过,这些东西依旧还是理论,并不能**拿来(zhuang)用(B)**, 这一次我们就了解一些**能(zhaung)用(B)**的东西。
在本篇文章中,我们先了解Canvas的基本用法,最后用一个小示例来结束本次教程。
@@ -50,7 +50,7 @@ Canvas我们可以称之为画布,能够在上面绘制各种东西,是安
-> 关于颜色的更多资料请参考[基础篇_颜色](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B3%5DColor.md)
+> 关于颜色的更多资料请参考[基础篇_颜色](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B03%5DColor.md)
******
@@ -90,7 +90,7 @@ Canvas我们可以称之为画布,能够在上面绘制各种东西,是安
```
关于坐标原点默认在左上角,水平向右为x轴增大方向,竖直向下为y轴增大方向。
-> 更多参考这里 [基础篇_坐标系](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B1%5DCoordinateSystem.md)
+> 更多参考这里 [基础篇_坐标系](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B01%5DCoordinateSystem.md)
@@ -274,7 +274,7 @@ useCenter // 是否使用中心
-可以发现使用了中心点之后绘制出来类似于一个扇形,而不使用中心点则是圆弧起始点和结束点之间的连线加上圆弧围成的图形。这样中心点这个参数的作用就很明显了,不必多说想必大家试一下就明白了。 另外可以关于角度可以参考一下这篇文章: [角度与弧度](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B2%5DAngleAndRadian.md)
+可以发现使用了中心点之后绘制出来类似于一个扇形,而不使用中心点则是圆弧起始点和结束点之间的连线加上圆弧围成的图形。这样中心点这个参数的作用就很明显了,不必多说想必大家试一下就明白了。 另外可以关于角度可以参考一下这篇文章: [角度与弧度](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView%2FBase%2F%5B02%5DAngleAndRadian.md)
相比于使用椭圆,我们还是使用正圆比较多的,使用正圆展示一下效果:
diff --git a/CustomView/Advance/[03]Canvas_Convert.md b/CustomView/Advance/[03]Canvas_Convert.md
index b462e3e2..13f253ba 100644
--- a/CustomView/Advance/[03]Canvas_Convert.md
+++ b/CustomView/Advance/[03]Canvas_Convert.md
@@ -3,24 +3,24 @@
### 作者微博: [@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/%5B2%5DCanvas_BasicGraphics.md)中我们了解了如何使用Canvas绘制基本图形,本次了解一些基本的画布操作。
+上一篇[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比较难以理解和使用,故封装了一些常用的方法。
+| 操作类型 | 相关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基本操作
@@ -77,15 +77,15 @@ Matrix(矩阵) | getMatrix, setMatrix, concat | 实际上画布的位移,缩
缩放比例(sx,sy)取值范围详解:
-取值范围(n)| 说明
---------- | ------
-[-∞, -1) | 先根据缩放中心放大n倍,再根据中心轴进行翻转
--1 | 根据缩放中心轴进行翻转
-(-1, 0) | 先根据缩放中心缩小到n,再根据中心轴进行翻转
-0 | 不会显示,若sx为0,则宽度为0,不会显示,sy同理
-(0, 1) | 根据缩放中心缩小到n
-1 | 没有变化
-(1, +∞) | 根据缩放中心放大n倍
+| 取值范围(n) | 说明 |
+| -------- | -------------------------- |
+| [-∞, -1) | 先根据缩放中心放大n倍,再根据中心轴进行翻转 |
+| -1 | 根据缩放中心轴进行翻转 |
+| (-1, 0) | 先根据缩放中心缩小到n,再根据中心轴进行翻转 |
+| 0 | 不会显示,若sx为0,则宽度为0,不会显示,sy同理 |
+| (0, 1) | 根据缩放中心缩小到n |
+| 1 | 没有变化 |
+| (1, +∞) | 根据缩放中心放大n倍 |
如果在缩放时稍微注意一下就会发现缩放的中心默认为坐标原点,而缩放中心轴就是坐标轴,如下:
@@ -330,13 +330,13 @@ A:画布的操作是不可逆的,而且很多画布操作会影响后续的
与之相关的API:
-相关API | 简介
---- | ---
-save | 把当前的画布的状态进行保存,然后放入特定的栈中
-saveLayerXxx | 新建一个图层,并放入特定的栈中
-restore | 把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布
-restoreToCount| 弹出指定位置及其以上所有的状态,并按照指定位置的状态进行恢复
-getSaveCount | 获取栈中内容的数量(即保存次数)
+| 相关API | 简介 |
+| -------------- | ------------------------------ |
+| save | 把当前的画布的状态进行保存,然后放入特定的栈中 |
+| saveLayerXxx | 新建一个图层,并放入特定的栈中 |
+| restore | 把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布 |
+| restoreToCount | 弹出指定位置及其以上所有的状态,并按照指定位置的状态进行恢复 |
+| getSaveCount | 获取栈中内容的数量(即保存次数) |
下面对其中的一些概念和方法进行分析:
@@ -359,14 +359,14 @@ A:实际上我们看到的画布是由多个图层构成的,如下图(图片
##### 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)
+| 数据类型 | 名称 | 简介 |
+| ---- | -------------------------- | ---------------------------------------- |
+| 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 有两种方法:
diff --git a/CustomView/Advance/[04]Canvas_PictureText.md b/CustomView/Advance/[04]Canvas_PictureText.md
index 8dc25299..9644f4f1 100644
--- a/CustomView/Advance/[04]Canvas_PictureText.md
+++ b/CustomView/Advance/[04]Canvas_PictureText.md
@@ -3,22 +3,22 @@
### 作者微博: [@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/%5B3%5DCanvas_Convert.md)中我们了解了画布的一些基本操作方法,本次了解一些绘制图片文字相关的内容。如果你对前几篇文章讲述的内容熟练掌握的话,那么恭喜你,本篇结束之后,大部分的自定义View已经难不倒你了,当然了,这并不是终点,接下来还会有更加炫酷的技能。
+在上一篇文章[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比较难以理解和使用,故封装了一些常用的方法。
+| 操作类型 | 相关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基本操作详解
@@ -68,15 +68,15 @@ A Picture records drawing calls (via the canvas returned by beginRecording) and
了解了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中内容写出到输出流中
+| 相关方法 | 简介 |
+| ---------------------------------------- | ---------------------------------------- |
+| 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没什么好说的,最后两个已经废弃也自然就不用关注了,排除了这些方法之后,只剩三个方法了,接下来我们就比较详细的了解一下:
@@ -127,19 +127,19 @@ public void writeToStream (OutputStream stream) | (已废弃)将Pict
Picture虽然方法就那么几个,但是具体使用起来还是分很多情况的,由于录制的内容不会直接显示,就像存储的视频不点击播放不会自动播放一样,同样,想要将Picture中的内容显示出来就需要手动调用播放(绘制),将Picture中的内容绘制出来可以有以下几种方法:
-序号 | 简介
------|-----------
- 1 | 使用Picture提供的draw方法绘制。
- 2 | 使用Canvas提供的drawPicture方法绘制。
- 3 | 将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。
+| 序号 | 简介 |
+| ---- | ---------------------------------------- |
+| 1 | 使用Picture提供的draw方法绘制。 |
+| 2 | 使用Canvas提供的drawPicture方法绘制。 |
+| 3 | 将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。 |
以上几种方法主要区别:
-主要区别 | 分类 | 简介
--------------------|-----------------------------------|------------------
-是否对Canvas有影响 | 1有影响
2,3不影响 | 此处指绘制完成后是否会影响Canvas的状态(Matrix clip等)
-可操作性强弱 | 1可操作性较弱
2,3可操作性较强 | 此处的可操作性可以简单理解为对绘制结果可控程度。
+| 主要区别 | 分类 | 简介 |
+| ------------ | --------------------- | ------------------------------------ |
+| 是否对Canvas有影响 | 1有影响
2,3不影响 | 此处指绘制完成后是否会影响Canvas的状态(Matrix clip等) |
+| 可操作性强弱 | 1可操作性较弱
2,3可操作性较强 | 此处的可操作性可以简单理解为对绘制结果可控程度。 |
几种方法简介和主要区别基本就这么多了,接下来对于各种使用方法一一详细介绍:
@@ -170,9 +170,9 @@ public void drawPicture (Picture picture, RectF dst)
``` java
canvas.drawPicture(mPicture,new RectF(0,0,mPicture.getWidth(),200));
```
-
+
-
+
**PS:对照上一张图片,可以比较明显的看出,绘制的内容根据选区进行了缩放。 **
**3.将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。**
@@ -197,15 +197,15 @@ public void drawPicture (Picture picture, RectF dst)
> 其实一开始知道要讲Bitmap我是拒绝的,为什么呢?因为Bitmap就是很多问题的根源啊有木有,Bitmap可能导致内存不足,内存泄露,ListView中的复用混乱等诸多问题。想完美的掌控Bitmap还真不是一件容易的事情。限于篇幅**本文对于Bitmap不会过多的展开,只讲解一些常用的功能**,关于Bitmap详细内容,以后开专题讲解QAQ。
既然要绘制Bitmap,就要先获取一个Bitmap,那么如何获取呢?
-
+
**获取Bitmap方式:**
-
- 序号 | 获取方式 | 备注
- -----|---------------------------|-----------------------------------------
- 1 | 通过Bitmap创建 | 复制一个已有的Bitmap(_新Bitmap状态和原有的一致_) 或者 创建一个空白的Bitmap(_内容可改变_)
- 2 | 通过BitmapDrawable获取 | 从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap
- 3 | 通过BitmapFactory获取 | 从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap
-
+
+| 序号 | 获取方式 | 备注 |
+| ---- | ------------------ | ---------------------------------------- |
+| 1 | 通过Bitmap创建 | 复制一个已有的Bitmap(_新Bitmap状态和原有的一致_) 或者 创建一个空白的Bitmap(_内容可改变_) |
+| 2 | 通过BitmapDrawable获取 | 从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap |
+| 3 | 通过BitmapFactory获取 | 从资源文件 内存卡 网络等地方获取一张图片并转换为内容不可变的Bitmap |
+
**通常来说,我们绘制Bitmap都是读取已有的图片转换为Bitmap绘制到Canvas上。**
很明显,第1种方式不能满足我们的要求,暂时排除。
第2种方式虽然也可满足我们的要求,但是我不推荐使用这种方式,至于为什么在后续详细讲解Drawable的时候会说明,暂时排除。
@@ -282,10 +282,10 @@ PS:图片左上角位置默认为坐标原点。
第三种方法比较有意思,上面多了两个矩形区域(src,dst),这两个矩形选区是干什么用的?
-名称 | 作用
---------------------------|---------------------
-Rect src | 指定绘制图片的区域
-Rect dst 或RectF dst | 指定图片在屏幕上显示(绘制)的区域
+| 名称 | 作用 |
+| ------------------- | ----------------- |
+| Rect src | 指定绘制图片的区域 |
+| Rect dst 或RectF dst | 指定图片在屏幕上显示(绘制)的区域 |
示例:
``` java
@@ -372,14 +372,14 @@ Rect dst 或RectF dst | 指定图片在屏幕上显示(绘制)的区域
**Paint文本相关常用方法表**
-标题 | 相关方法 | 备注
------|---------------------------|----------------------
-色彩 | setColor setARGB setAlpha | 设置颜色,透明度
-大小 | setTextSize | 设置文本字体大小
-字体 | setTypeface | 设置或清除字体样式
-样式 | setStyle | 填充(FILL),描边(STROKE),填充加描边(FILL_AND_STROKE)
-对齐 | setTextAlign | 左对齐(LEFT),居中对齐(CENTER),右对齐(RIGHT)
-测量 | measureText | 测量文本大小(注意,请在设置完文本各项参数后调用)
+| 标题 | 相关方法 | 备注 |
+| ---- | ------------------------- | ---------------------------------------- |
+| 色彩 | setColor setARGB setAlpha | 设置颜色,透明度 |
+| 大小 | setTextSize | 设置文本字体大小 |
+| 字体 | setTypeface | 设置或清除字体样式 |
+| 样式 | setStyle | 填充(FILL),描边(STROKE),填充加描边(FILL_AND_STROKE) |
+| 对齐 | setTextAlign | 左对齐(LEFT),居中对齐(CENTER),右对齐(RIGHT) |
+| 测量 | measureText | 测量文本大小(注意,请在设置完文本各项参数后调用) |
为了绘制文本,我们先创建一个文本画笔:
``` java
@@ -411,9 +411,9 @@ Rect dst 或RectF dst | 指定图片在屏幕上显示(绘制)的区域
以上一个例子使用的字符串为例,它的下标是这样的(wait,我为啥要说这个,算了,不管了,就这样吧(๑•́ ₃ •̀๑)):
-字符 | A | B | C | D | E | F | G | H | I | J | K
- ---|---|---|---|---|---|---|---|---|---|---|---
-下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
+| 字符 | A | B | C | D | E | F | G | H | I | J | K |
+| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
+| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
假设我我们指定star为1,end为3,那么最终截取的字符串就是"BC"。
@@ -465,11 +465,11 @@ Rect dst 或RectF dst | 指定图片在屏幕上显示(绘制)的区域
不过嘛,虽然虽然这个方法也比较容易理解,但是关于这个方法我个人是不推荐使用的,因为坑比较多,主要有一下几点:
-序号 | 反对理由
------|----------------------
- 1 | 必须指定所有字符位置,否则直接crash掉,反人类设计
- 2 | 性能不佳,在大量使用的时候可能导致卡顿
- 3 | 不支持emoji等特殊字符,不支持字形组合与分解
+| 序号 | 反对理由 |
+| ---- | --------------------------- |
+| 1 | 必须指定所有字符位置,否则直接crash掉,反人类设计 |
+| 2 | 性能不佳,在大量使用的时候可能导致卡顿 |
+| 3 | 不支持emoji等特殊字符,不支持字形组合与分解 |
关于第二类的第二种方法:
diff --git a/CustomView/Advance/[05]Path_Basic.md b/CustomView/Advance/[05]Path_Basic.md
index d3641a42..225ef09c 100644
--- a/CustomView/Advance/[05]Path_Basic.md
+++ b/CustomView/Advance/[05]Path_Basic.md
@@ -4,32 +4,32 @@
### [【本系列相关文章】](https://github.com/GcsSloop/AndroidNote/tree/master/CustomView/README.md)
-在上一篇[Canvas之图片文字](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B4%5DCanvas_PictureText.md)中我们了解了如何使用Canvas中绘制图片文字,结合前几篇文章,Canvas的基本操作已经差不多完结了,然而Canvas不仅仅具有这些基本的操作,还可以更加炫酷,本次会了解到path(路径)这个Canvas中的神器,有了这个神器,就能创造出更多**炫(zhuang)酷(B)**的东东了。
+在上一篇[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中的内容
**reset不保留内部数据结构,但会保留FillType.**
**rewind会保留内部的数据结构,但不保留FillType**
-矩阵操作 | transform | 矩阵变换
+| 作用 | 相关方法 | 备注 |
+| ----------- | ---------------------------------------- | ---------------------------------------- |
+| 移动起点 | 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中的内容
**reset不保留内部数据结构,但会保留FillType.**
**rewind会保留内部的数据结构,但不保留FillType** |
+| 矩阵操作 | transform | 矩阵变换 |
# 二.Path详解
@@ -59,12 +59,12 @@ _The Path class encapsulates compound (multiple contour) geometric paths consist
另外路径有开放和封闭的区别。
-
-图像 | 名称 | 备注
- --- | --- | ---
-  | 封闭路径 | 首尾相接形成了一个封闭区域
-  | 开放路径 | 没有首位相接形成封闭区域
-
+
+| 图像 | 名称 | 备注 |
+| ---------------------------------------- | ---- | ------------- |
+|  | 封闭路径 | 首尾相接形成了一个封闭区域 |
+|  | 开放路径 | 没有首位相接形成封闭区域 |
+
> 这个是我随便画的,仅为展示一下区别,请无视我灵魂画师一般的绘图水准。
**与Path相关的还有一些比较神奇的概念,不过暂且不说,等接下来需要用到的时候再详细说明。**
@@ -76,18 +76,18 @@ _The Path class encapsulates compound (multiple contour) geometric paths consist

### 第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:
方法预览:
```
@@ -95,9 +95,9 @@ public void lineTo (float x, float y)
```
首先讲解的的LineTo,为啥先讲解这个呢?
-
+
是因为moveTo、 setLastPoint、 close都无法直接看到效果,借助有具现化效果的lineTo才能让这些方法现出原形。
-
+
lineTo很简单,只有一个方法,作用也很容易理解,line嘛,顾名思义就是一条线。
@@ -143,10 +143,10 @@ lineTo很简单,只有一个方法,作用也很容易理解,line嘛,顾
这两个方法虽然在作用上有相似之处,但实际上却是完全不同的两个东东,具体参照下表:
-方法名 | 简介 | 是否影响之前的操作 | 是否影响之后操作
---- | --- | --- | ---
-moveTo | 移动下一次操作的起点位置 | 否 | 是
-setLastPoint | 设置之前操作的最后一个点位置 | 是 | 是
+| 方法名 | 简介 | 是否影响之前的操作 | 是否影响之后操作 |
+| ------------ | -------------- | --------- | -------- |
+| moveTo | 移动下一次操作的起点位置 | 否 | 是 |
+| setLastPoint | 设置之前操作的最后一个点位置 | 是 | 是 |
废话不多说,直接上代码:
``` java
@@ -243,19 +243,19 @@ close方法用于连接当前最后一个点和最初的一个点(如果两个
Direction的意思是 方向,趋势。 点进去看一下会发现Direction是一个枚举(Enum)类型,里面只有两个枚举常量,如下:
-类型 | 解释 | 翻译
------|-------------------|-------
-CW | clockwise | 顺时针
-CCW | counter-clockwise | 逆时针
+| 类型 | 解释 | 翻译 |
+| ---- | ----------------- | ---- |
+| CW | clockwise | 顺时针 |
+| CCW | counter-clockwise | 逆时针 |
> **瞬间懵逼,我只是想添加一个基本的形状啊,搞什么顺时针和逆时针, (╯‵□′)╯︵┻━┻**
**稍安勿躁,┬─┬ ノ( ' - 'ノ) {摆好摆好) 既然存在肯定是有用的,先偷偷剧透一下这个顺时针和逆时针的作用。**
-序号 | 作用
------|---------------------------------------------------
- 1 | 在添加图形时确定闭合顺序(各个点的记录顺序)
- 2 | 对图形的渲染结果有影响(是判断图形渲染的重要条件)
+| 序号 | 作用 |
+| ---- | ------------------------- |
+| 1 | 在添加图形时确定闭合顺序(各个点的记录顺序) |
+| 2 | 对图形的渲染结果有影响(是判断图形渲染的重要条件) |
这个先剧透这么多,至于对闭合顺序有啥影响,自相交图形的渲染等问题等请慢慢看下去
@@ -275,7 +275,7 @@ CCW | counter-clockwise | 逆时针
**将上面代码的CW改为CCW再运行一次。接下来就是见证奇迹的时刻,两次运行结果一模一样,有木有很神奇!**
> **(╯°Д°)╯︵ ┻━┻(再TM掀一次)
-坑人也不带这样的啊,一毛一样要它干嘛。**
+> 坑人也不带这样的啊,一毛一样要它干嘛。**
**其实啊,这个东东是自带隐身技能的,想要让它现出原形,就要用到咱们刚刚学到的setLastPoint(重置当前最后一个点的位置)。**
@@ -384,10 +384,10 @@ CCW | counter-clockwise | 逆时针
从名字就可以看出,这两个方法都是与圆弧相关的,作用都是添加一个圆弧到path中,但既然存在两个方法,两者之间肯定是有区别的:
-名称 | 作用 | 区别
- --- | --- | ---
- addArc | 添加一个圆弧到path | 直接添加一个圆弧到path中
- arcTo | 添加一个圆弧到path | 添加一个圆弧到path,如果圆弧的起点和上次最后一个坐标点不相同,就连接两个点
+| 名称 | 作用 | 区别 |
+| ------ | ----------- | --------------------------------------- |
+| addArc | 添加一个圆弧到path | 直接添加一个圆弧到path中 |
+| arcTo | 添加一个圆弧到path | 添加一个圆弧到path,如果圆弧的起点和上次最后一个坐标点不相同,就连接两个点 |
可以看到addArc有1个方法(_实际上是两个的,但另一个重载方法是API21添加的_), 而arcTo有2个方法,其中一个最后多了一个布尔类型的变量forceMoveTo。
@@ -395,10 +395,10 @@ CCW | counter-clockwise | 逆时针
这个变量意思为“是否强制使用moveTo”,也就是说,是否使用moveTo将变量移动到圆弧的起点位移,也就意味着:
-forceMoveTo | 含义 | 等价方法
- --- | --- | ---
- true | 将最后一个点移动到圆弧起点,即不连接最后一个点与圆弧起点 | public void addArc (RectF oval, float startAngle, float sweepAngle)
- false | 不移动,而是连接最后一个点与圆弧起点 | public void arcTo (RectF oval, float startAngle, float sweepAngle)
+| forceMoveTo | 含义 | 等价方法 |
+| ----------- | ---------------------------- | ---------------------------------------- |
+| true | 将最后一个点移动到圆弧起点,即不连接最后一个点与圆弧起点 | public void addArc (RectF oval, float startAngle, float sweepAngle) |
+| false | 不移动,而是连接最后一个点与圆弧起点 | public void arcTo (RectF oval, float startAngle, float sweepAngle) |
**示例(addArc):**
``` java
@@ -526,10 +526,10 @@ log 输出结果:
其实第二个方法中最后的参数das是存储平移后的path的。
-dst状态 | 效果
- --------------|---------------------------------
- dst不为空 | 将当前path平移后的状态存入dst中,不会影响当前path
- dat为空(null) | 平移将作用于当前path,相当于第一种方法
+| dst状态 | 效果 |
+| ----------- | ------------------------------ |
+| dst不为空 | 将当前path平移后的状态存入dst中,不会影响当前path |
+| dat为空(null) | 平移将作用于当前path,相当于第一种方法 |
示例:
``` java
diff --git a/CustomView/Advance/[06]Path_Bezier.md b/CustomView/Advance/[06]Path_Bezier.md
index 26f38ea8..4384d9c2 100644
--- a/CustomView/Advance/[06]Path_Bezier.md
+++ b/CustomView/Advance/[06]Path_Bezier.md
@@ -3,33 +3,32 @@
### 作者微博: [@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/%5B5%5DPath_BasicGraphics.md)中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线。
-
+在上一篇文章[Path之基本图形](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B05%5DPath_BasicGraphics.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中的内容
**reset不保留内部数据结构,但会保留FillType.**
**rewind会保留内部的数据结构,但不保留FillType**
-矩阵操作 | transform | 矩阵变换
+| 作用 | 相关方法 | 备注 |
+| ----------- | ---------------------------------------- | ---------------------------------------- |
+| 移动起点 | 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中的内容
**reset不保留内部数据结构,但会保留FillType.**
**rewind会保留内部的数据结构,但不保留FillType** |
+| 矩阵操作 | transform | 矩阵变换 |
## 二.Path详解
@@ -64,14 +63,14 @@ rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | **不带r的方法是
以上几种类型中比较有用的就是基础型和实战型,但两者各有不足,本文会综合两者内容,从零开始学习贝塞尔曲线。
### 第一步.理解贝塞尔曲线的原理
-
+
此处理解贝塞尔曲线并非是学会公式的推导过程(不是推倒(ノ*・ω・)ノ),而是要了解贝塞尔曲线是如何生成的。
贝塞尔曲线是用一系列点来控制曲线状态的,我将这些点简单分为两类:
- 类型 | 作用
--------|------
-数据点 | 确定曲线的起始和结束位置
-控制点 | 确定曲线的弯曲程度
+| 类型 | 作用 |
+| ---- | ------------ |
+| 数据点 | 确定曲线的起始和结束位置 |
+| 控制点 | 确定曲线的弯曲程度 |
> 此处暂时仅作了解概念,接下来就会讲解其中详细的含义。
@@ -101,7 +100,7 @@ rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | **不带r的方法是

-
+
连接DE,取点F,使得:
@@ -131,7 +130,7 @@ rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | **不带r的方法是
#### 一阶曲线:
-一阶曲线是一条线段,非常简单,可以参见上一篇文章[Path之基本操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B5%5DPath_Basic.md),此处就不详细讲解了。
+一阶曲线是一条线段,非常简单,可以参见上一篇文章[Path之基本操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/5%5DPath_Basic.md),此处就不详细讲解了。
#### 二阶曲线:
@@ -331,10 +330,10 @@ public class Bezier2 extends View {
#### 降阶与升阶
-类型 | 释义 | 变化
------|---|---
-降阶 | 在保持曲线形状与方向不变的情况下,减少控制点数量,即降低曲线阶数 | 方法变得简单,数据点变多,控制点可能减少,灵活性变弱
-升阶 | 在保持曲线形状与方向不变的情况下,增加控制点数量,即升高曲线阶数 | 方法更加复杂,数据点不变,控制点增加,灵活性变强
+| 类型 | 释义 | 变化 |
+| ---- | -------------------------------- | -------------------------- |
+| 降阶 | 在保持曲线形状与方向不变的情况下,减少控制点数量,即降低曲线阶数 | 方法变得简单,数据点变多,控制点可能减少,灵活性变弱 |
+| 升阶 | 在保持曲线形状与方向不变的情况下,增加控制点数量,即升高曲线阶数 | 方法更加复杂,数据点不变,控制点增加,灵活性变强 |
### 第三步.贝塞尔曲线使用实例
@@ -342,12 +341,12 @@ public class Bezier2 extends View {
> 需要绘制不规则图形时? 当然不是!目前来说,我觉得使用贝塞尔曲线主要有以下几个方面(仅个人拙见,可能存在错误,欢迎指正)
-序号 | 内容 | 用例
------|-----------------------------------------------|---------
- 1 | 事先不知道曲线状态,需要实时计算时 | 天气预报气温变化的平滑折线图
- 2 | 显示状态会根据用户操作改变时 | QQ小红点,仿真翻书效果
- 3 | 一些比较复杂的运动状态(配合PathMeasure使用) | 复杂运动状态的动画效果
-
+| 序号 | 内容 | 用例 |
+| ---- | ---------------------------- | -------------- |
+| 1 | 事先不知道曲线状态,需要实时计算时 | 天气预报气温变化的平滑折线图 |
+| 2 | 显示状态会根据用户操作改变时 | QQ小红点,仿真翻书效果 |
+| 3 | 一些比较复杂的运动状态(配合PathMeasure使用) | 复杂运动状态的动画效果 |
+
至于只需要一个静态的曲线图形的情况,用图片岂不是更好,大量的计算会很不划算。
如果是显示SVG矢量图的话,已经有相关的解析工具了(内部依旧运用的有贝塞尔曲线),不需要手动计算。
@@ -375,15 +374,15 @@ public class Bezier2 extends View {
#### 核心难点:
##### 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.如何达到渐变效果?
-
+
渐变其实就是每次对数据点和控制点稍微移动一点,然后重绘界面,在短时间多次的调整数据点与控制点,使其逐渐接近目标值,通过不断的重绘界面达到一种渐变的效果。过程可以参照下图动态效果:
-
+

#### 代码:
diff --git a/CustomView/Advance/[07]Path_Over.md b/CustomView/Advance/[07]Path_Over.md
index be9818bc..a942b63d 100644
--- a/CustomView/Advance/[07]Path_Over.md
+++ b/CustomView/Advance/[07]Path_Over.md
@@ -3,32 +3,32 @@
### 作者微博: [@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/%5B5%5DPath_Basic.md) 和 [Path之贝塞尔曲线](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B6%5DPath_Bezier.md) 的讲解,本篇终于进入Path的收尾篇,本篇结束后Path的大部分相关方法都已经讲解完了,但Path还有一些更有意思的玩法,应该会在后续的文章中出现吧,嗯,应该会的ˊ_>ˋ
+经历过前两篇 [Path之基本操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B05%5DPath_Basic.md) 和 [Path之贝塞尔曲线](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B06%5DPath_Bezier.md) 的讲解,本篇终于进入Path的收尾篇,本篇结束后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中的内容
**reset不保留内部数据结构,但会保留FillType.**
**rewind会保留内部的数据结构,但不保留FillType**
-矩阵操作 | transform | 矩阵变换
+| 作用 | 相关方法 | 备注 |
+| ----------- | ---------------------------------------- | ---------------------------------------- |
+| 移动起点 | 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中的内容
**reset不保留内部数据结构,但会保留FillType.**
**rewind会保留内部的数据结构,但不保留FillType** |
+| 矩阵操作 | transform | 矩阵变换 |
## 二、Path方法详解
@@ -79,10 +79,10 @@ rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | **不带r的方法是
> PS:此处所有的图形均为封闭图形,不包括图形不封闭这种情况。
-方法 | 判定条件 | 解释
----------------|-----------------------------------------------|----------------
-奇偶规则 | 奇数表示在图形内,偶数表示在图形外 | 从任意位置p作一条射线, 若与该射线相交的图形边的数目为奇数,则p是图形内部点,否则是外部点。
-非零环绕数规则 | 若环绕数为0表示在图形外,非零表示在图形内 | 首先使图形的边变为矢量。将环绕数初始化为零。再从任意位置p作一条射线。当从p点沿射线方向移动时,对在每个方向上穿过射线的边计数,每当图形的边从右到左穿过射线时,环绕数加1,从左到右时,环绕数减1。处理完图形的所有相关边之后,若环绕数为非零,则p为内部点,否则,p是外部点。
+| 方法 | 判定条件 | 解释 |
+| ------- | --------------------- | ---------------------------------------- |
+| 奇偶规则 | 奇数表示在图形内,偶数表示在图形外 | 从任意位置p作一条射线, 若与该射线相交的图形边的数目为奇数,则p是图形内部点,否则是外部点。 |
+| 非零环绕数规则 | 若环绕数为0表示在图形外,非零表示在图形内 | 首先使图形的边变为矢量。将环绕数初始化为零。再从任意位置p作一条射线。当从p点沿射线方向移动时,对在每个方向上穿过射线的边计数,每当图形的边从右到左穿过射线时,环绕数加1,从左到右时,环绕数减1。处理完图形的所有相关边之后,若环绕数为非零,则p为内部点,否则,p是外部点。 |
接下来我们先了解一下两种判断方法是如何工作的。
@@ -95,16 +95,16 @@ rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | **不带r的方法是
在上图中有一个四边形,我们选取了三个点来判断这些点是否在图形内部。
>
-P1: 从P1发出一条射线,发现图形与该射线相交边数为0,偶数,故P1点在图形外部。
-P2: 从P2发出一条射线,发现图形与该射线相交边数为1,奇数,故P2点在图形内部。
-P3: 从P3发出一条射线,发现图形与该射线相交边数为2,偶数,故P3点在图形外部。
+>P1: 从P1发出一条射线,发现图形与该射线相交边数为0,偶数,故P1点在图形外部。
+>P2: 从P2发出一条射线,发现图形与该射线相交边数为1,奇数,故P2点在图形内部。
+>P3: 从P3发出一条射线,发现图形与该射线相交边数为2,偶数,故P3点在图形外部。
#### 非零环绕数规则(Non-Zero Winding Number Rule)
非零环绕数规则相对来说比较难以理解一点。
-我们在之前的文章 [Path之基本操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B5%5DPath_Basic.md) 中我们了解到,在给Path中添加图形时需要指定图形的添加方式,是用顺时针还是逆时针,另外我们不论是使用lineTo,quadTo,cubicTo还是其他连接线的方法,都是从一个点连接到另一个点,换言之,**Path中任何线段都是有方向性的**,这也是使用非零环绕数规则的基础。
+我们在之前的文章 [Path之基本操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B05%5DPath_Basic.md) 中我们了解到,在给Path中添加图形时需要指定图形的添加方式,是用顺时针还是逆时针,另外我们不论是使用lineTo,quadTo,cubicTo还是其他连接线的方法,都是从一个点连接到另一个点,换言之,**Path中任何线段都是有方向性的**,这也是使用非零环绕数规则的基础。
我们依旧用一个简单的例子来说明非零环绕数规则的用法:
@@ -113,9 +113,9 @@ P3: 从P3发出一条射线,发现图形与该射线相交边数为2,偶数

>
-P1: 从P1点发出一条射线,沿射线防线移动,并没有与边相交点部分,环绕数为0,故P1在图形外边。
-P2: 从P2点发出一条射线,沿射线方向移动,与图形点左侧边相交,该边从左到右穿过穿过射线,环绕数-1,最终环绕数为-1,故P2在图形内部。
-P3: 从P3点发出一条射线,沿射线方向移动,在第一个交点处,底边从右到左穿过射线,环绕数+1,在第二个交点处,右侧边从左到右穿过射线,环绕数-1,最终环绕数为0,故P3在图形外部。
+>P1: 从P1点发出一条射线,沿射线防线移动,并没有与边相交点部分,环绕数为0,故P1在图形外边。
+>P2: 从P2点发出一条射线,沿射线方向移动,与图形点左侧边相交,该边从左到右穿过穿过射线,环绕数-1,最终环绕数为-1,故P2在图形内部。
+>P3: 从P3点发出一条射线,沿射线方向移动,在第一个交点处,底边从右到左穿过射线,环绕数+1,在第二个交点处,右侧边从左到右穿过射线,环绕数-1,最终环绕数为0,故P3在图形外部。
通常,这两种方法的判断结果是相同的,但也存在两种方法判断结果不同的情况,如下面这种情况:
@@ -135,12 +135,12 @@ P3: 从P3点发出一条射线,沿射线方向移动,在第一个交点处
Android中的填充模式有四种,是封装在Path中的一个枚举。
-模式 | 简介
------------------|-----------------------
-EVEN_ODD | 奇偶规则
-INVERSE_EVEN_ODD | 反奇偶规则
-WINDING | 非零环绕数规则
-INVERSE_WINDING | 反非零环绕数规则
+| 模式 | 简介 |
+| ---------------- | -------- |
+| EVEN_ODD | 奇偶规则 |
+| INVERSE_EVEN_ODD | 反奇偶规则 |
+| WINDING | 非零环绕数规则 |
+| INVERSE_WINDING | 反非零环绕数规则 |
我们可以看到上面有四种模式,分成两对,例如 "奇偶规则" 与 "反奇偶规则" 是一对,它们之间有什么关系呢?
@@ -150,12 +150,12 @@ Inverse 和含义是“相反,对立”,说明反奇偶规则刚好与奇偶
> 这些都是Path中的方法。
-方法 | 作用
-------------------------|----------------------------
-setFillType | 设置填充规则
-getFillType | 获取当前填充规则
-isInverseFillType | 判断是否是反向(INVERSE)规则
-toggleInverseFillType | 切换填充规则(即原有规则与反向规则之间相互切换)
+| 方法 | 作用 |
+| --------------------- | ------------------------ |
+| setFillType | 设置填充规则 |
+| getFillType | 获取当前填充规则 |
+| isInverseFillType | 判断是否是反向(INVERSE)规则 |
+| toggleInverseFillType | 切换填充规则(即原有规则与反向规则之间相互切换) |
#### 示例演示:
@@ -247,13 +247,13 @@ toggleInverseFillType | 切换填充规则(即原有规则与反向规则之
Path的布尔运算有五种逻辑,如下:
-逻辑名称 | 类比 | 说明 | 示意图
--------------------|------|----------------------------------------|-------------------------
-DIFFERENCE | 差集 | Path1中减去Path2后剩下的部分 | 
-REVERSE_DIFFERENCE | 差集 | Path2中减去Path1后剩下的部分 | 
-INTERSECT | 交集 | Path1与Path2相交的部分 | 
-UNION | 并集 | 包含全部Path1和Path2 | 
-XOR | 异或 | 包含Path1与Path2但不包括两者相交的部分 | 
+| 逻辑名称 | 类比 | 说明 | 示意图 |
+| ------------------ | ---- | ------------------------ | ---------------------------------------- |
+| DIFFERENCE | 差集 | Path1中减去Path2后剩下的部分 |  |
+| REVERSE_DIFFERENCE | 差集 | Path2中减去Path1后剩下的部分 |  |
+| INTERSECT | 交集 | Path1与Path2相交的部分 |  |
+| UNION | 并集 | 包含全部Path1和Path2 |  |
+| XOR | 异或 | 包含Path1与Path2但不包括两者相交的部分 |  |
#### 布尔运算方法
@@ -282,7 +282,7 @@ XOR | 异或 | 包含Path1与Path2但不包括两者相交的部
代码:
-``` java
+``` java
int x = 80;
int r = 100;
@@ -331,10 +331,10 @@ XOR | 异或 | 包含Path1与Path2但不包括两者相交的部
它有两个参数:
-参数 | 作用
--------|--------
-bounds | 测量结果会放入这个矩形
-exact | 是否精确测量,目前这一个参数作用已经废弃,一般写true即可。
+| 参数 | 作用 |
+| ------ | ------------------------------- |
+| bounds | 测量结果会放入这个矩形 |
+| exact | 是否精确测量,目前这一个参数作用已经废弃,一般写true即可。 |
关于exact如有疑问可参见Google官方的提交记录[Path.computeBounds()](https://code.google.com/p/android/issues/detail?id=4070)
@@ -371,10 +371,10 @@ exact | 是否精确测量,目前这一个参数作用已经废弃,一般
重置Path有两个方法,分别是reset和rewind,两者区别主要有一下两点:
-方法 | 是否保留FillType设置 | 是否保留原有数据结构
--------|:--------------------:|:--------------------:
-reset | 是 | 否
-rewind | 否 | 是
+| 方法 | 是否保留FillType设置 | 是否保留原有数据结构 |
+| ------ | :------------: | :--------: |
+| reset | 是 | 否 |
+| rewind | 否 | 是 |
**这个两个方法应该何时选择呢?**
@@ -384,7 +384,7 @@ _因为“FillType”影响的是显示效果,而“数据结构”影响的
## 总结
-
+
Path中常用的方法到此已经结束,希望能够帮助大家加深对Path对理解运用,让大家能够用Path愉快的玩耍。( ̄▽ ̄)
(,,• ₃ •,,)
diff --git a/CustomView/Advance/[08]Path_Play.md b/CustomView/Advance/[08]Path_Play.md
index 181158c6..3c6e342c 100644
--- a/CustomView/Advance/[08]Path_Play.md
+++ b/CustomView/Advance/[08]Path_Play.md
@@ -5,9 +5,9 @@
可以看到,在经过
-[Path之基本操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B5%5DPath_Basic.md)
-[Path之贝塞尔曲线](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B6%5DPath_Bezier.md) 和
-[Path之完结篇(伪)](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B7%5DPath_Over.md) 后, Path中各类方法基本上都讲完了,表格中还没有讲解到到方法就是矩阵变换了,难道本篇终于要讲矩阵了?
+[Path之基本操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B05%5DPath_Basic.md)
+[Path之贝塞尔曲线](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B06%5DPath_Bezier.md) 和
+[Path之完结篇(伪)](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B07%5DPath_Over.md) 后, Path中各类方法基本上都讲完了,表格中还没有讲解到到方法就是矩阵变换了,难道本篇终于要讲矩阵了?
非也,矩阵这一部分仍在后面单独讲解,本篇主要讲解 PathMeasure 这个类与 Path 的一些使用技巧。
> PS:不要问我为什么不讲 PathEffect,因为这个方法在后面的Paint系列中。
@@ -24,22 +24,22 @@
### 构造方法
-方法名 | 释义
----|---
-PathMeasure() | 创建一个空的PathMeasure
-PathMeasure(Path path, boolean forceClosed) | 创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。
+| 方法名 | 释义 |
+| ---------------------------------------- | ---------------------------------------- |
+| PathMeasure() | 创建一个空的PathMeasure |
+| PathMeasure(Path path, boolean forceClosed) | 创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。 |
### 公共方法
-返回值 | 方法名 | 释义
---------|--------------------------------------------------------------------------|-------------------
-void | setPath(Path path, boolean forceClosed) | 关联一个Path
-boolean | isClosed() | 是否闭合
-float | getLength() | 获取Path的长度
-boolean | nextContour() | 跳转到下一个轮廓
-boolean | getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) | 截取片段
-boolean | getPosTan(float distance, float[] pos, float[] tan) | 获取指定长度的位置坐标及该点切线值
-boolean | getMatrix(float distance, Matrix matrix, int flags) | 获取指定长度的位置坐标及该点Matrix
+| 返回值 | 方法名 | 释义 |
+| ------- | ---------------------------------------- | -------------------- |
+| void | setPath(Path path, boolean forceClosed) | 关联一个Path |
+| boolean | isClosed() | 是否闭合 |
+| float | getLength() | 获取Path的长度 |
+| boolean | nextContour() | 跳转到下一个轮廓 |
+| boolean | getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) | 截取片段 |
+| boolean | getPosTan(float distance, float[] pos, float[] tan) | 获取指定长度的位置坐标及该点切线值 |
+| boolean | getMatrix(float distance, Matrix matrix, int flags) | 获取指定长度的位置坐标及该点Matrix |
PathMeasure的方法也不多,接下来我们就逐一的讲解一下。
@@ -138,13 +138,13 @@ getSegment 用于获取Path的一个片段,方法如下:
方法各个参数释义:
-参数 | 作用 | 备注
-----------------|----------------------------------|--------------------------------------------
-返回值(boolean) | 判断截取是否成功 | true 表示截取成功,结果存入dst中,false 截取失败,不会改变dst中内容
-startD | 开始截取位置距离 Path 起点的长度 | 取值范围: 0 <= startD < stopD <= Path总长度
-stopD | 结束截取位置距离 Path 起点的长度 | 取值范围: 0 <= startD < stopD <= Path总长度
-dst | 截取的 Path 将会添加到 dst 中 | 注意: 是添加,而不是替换
-startWithMoveTo | 起始点是否使用 moveTo | 用于保证截取的 Path 第一个点位置不变
+| 参数 | 作用 | 备注 |
+| --------------- | -------------------- | ---------------------------------------- |
+| 返回值(boolean) | 判断截取是否成功 | true 表示截取成功,结果存入dst中,false 截取失败,不会改变dst中内容 |
+| startD | 开始截取位置距离 Path 起点的长度 | 取值范围: 0 <= startD < stopD <= Path总长度 |
+| stopD | 结束截取位置距离 Path 起点的长度 | 取值范围: 0 <= startD < stopD <= Path总长度 |
+| dst | 截取的 Path 将会添加到 dst 中 | 注意: 是添加,而不是替换 |
+| startWithMoveTo | 起始点是否使用 moveTo | 用于保证截取的 Path 第一个点位置不变 |
>
* 如果 startD、stopD 的数值不在取值范围 [0, getLength] 内,或者 startD == stopD 则返回值为 false,不会改变 dst 内容。
@@ -230,10 +230,10 @@ startWithMoveTo | 起始点是否使用 moveTo | 用于保证截取
从而我们可以用以下规则来判断 startWithMoveTo 的取值:
-取值 | 主要功用
-------|------------------
-true | 保证截取得到的 Path 片段不会发生形变
-false | 保证存储截取片段的 Path(dst) 的连续性
+| 取值 | 主要功用 |
+| ----- | ------------------------ |
+| true | 保证截取得到的 Path 片段不会发生形变 |
+| false | 保证存储截取片段的 Path(dst) 的连续性 |
@@ -296,12 +296,12 @@ log输出结果:
方法各个参数释义:
-参数 | 作用 | 备注
-----------------|----------------------------------|--------------------------------------------
-返回值(boolean) | 判断获取是否成功 | true表示成功,数据会存入 pos 和 tan 中,
false 表示失败,pos 和 tan 不会改变
-distance | 距离 Path 起点的长度 | 取值范围: 0 <= distance <= getLength
-pos | 该点的坐标值 | 坐标值: (x==[0], y==[1])
-tan | 该点的正切值 | 正切值: (x==[0], y==[1])
+| 参数 | 作用 | 备注 |
+| ------------ | ------------- | ---------------------------------------- |
+| 返回值(boolean) | 判断获取是否成功 | true表示成功,数据会存入 pos 和 tan 中,
false 表示失败,pos 和 tan 不会改变 |
+| distance | 距离 Path 起点的长度 | 取值范围: 0 <= distance <= getLength |
+| pos | 该点的坐标值 | 坐标值: (x==[0], y==[1]) |
+| tan | 该点的正切值 | 正切值: (x==[0], y==[1]) |
这个方法也不难理解,除了其中 `tan` 这个东东,这个东西是干什么的呢?
@@ -387,12 +387,12 @@ boolean getMatrix (float distance, Matrix matrix, int flags)
方法各个参数释义:
-参数 | 作用 | 备注
-----------------|----------------------------------|--------------------------------------------
-返回值(boolean) | 判断获取是否成功 | true表示成功,数据会存入matrix中,false 失败,matrix内容不会改变
-distance | 距离 Path 起点的长度 | 取值范围: 0 <= distance <= getLength
-matrix | 根据 falgs 封装好的matrix | 会根据 flags 的设置而存入不同的内容
-flags | 规定哪些内容会存入到matrix中 | 可选择
POSITION_MATRIX_FLAG(位置)
ANGENT_MATRIX_FLAG(正切)
+| 参数 | 作用 | 备注 |
+| ------------ | ------------------- | ---------------------------------------- |
+| 返回值(boolean) | 判断获取是否成功 | true表示成功,数据会存入matrix中,false 失败,matrix内容不会改变 |
+| distance | 距离 Path 起点的长度 | 取值范围: 0 <= distance <= getLength |
+| matrix | 根据 falgs 封装好的matrix | 会根据 flags 的设置而存入不同的内容 |
+| flags | 规定哪些内容会存入到matrix中 | 可选择
POSITION_MATRIX_FLAG(位置)
ANGENT_MATRIX_FLAG(正切) |
其实这个方法就相当于我们在前一个例子中封装 `matrix` 的过程由 `getMatrix` 替我们做了,我们可以直接得到一个封装好到 `matrix`,岂不快哉。
@@ -447,17 +447,17 @@ measure.getMatrix(distance, matrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasur
我们知道,用Path可以创建出各种个样的图形,但如果图形过于复杂时,用代码写就不现实了,不仅麻烦,而且容易出错,所以在绘制复杂的图形时我们一般是将 SVG 图像转换为 Path。
**你说什么是 SVG?**
-
+
SVG 是一种矢量图,内部用的是 xml 格式化存储方式存储这操作和数据,你完全可以将 SVG 看作是 Path 的各项操作简化书写后的存储格式。
-
+
Path 和 SVG 结合通常能诞生出一些奇妙的东西,如下:


>
-**该图片来自这个开源库 ->[PathView](https://github.com/geftimov/android-pathview)**
-**SVG 转 Path 的解析可以用这个库 -> [AndroidSVG](https://bigbadaboom.github.io/androidsvg/)**
+>**该图片来自这个开源库 ->[PathView](https://github.com/geftimov/android-pathview)**
+>**SVG 转 Path 的解析可以用这个库 -> [AndroidSVG](https://bigbadaboom.github.io/androidsvg/)**
限于篇幅以及本人精力,这一部分就暂不详解了,感兴趣的可以直接看源码,或者搜索一些相关的解析文章。
@@ -468,7 +468,7 @@ Path 和 SVG 结合通常能诞生出一些奇妙的东西,如下:
**话说本篇文章的名字不是叫 玩出花样么?怎么只见前面啰啰嗦嗦的扯了一大堆不明所以的东西,花样在哪里?**
>
-**前面的内容虽然啰嗦繁杂,但却是重中之重的基础,如果在修仙界,这叫根基,而下面讲述的内容的是招式,有了根基才能演化出千变万化的招式,而没有根基只学招式则是徒有其表,只能学一样会一样,很难适应千变万化的需求。**
+>**前面的内容虽然啰嗦繁杂,但却是重中之重的基础,如果在修仙界,这叫根基,而下面讲述的内容的是招式,有了根基才能演化出千变万化的招式,而没有根基只学招式则是徒有其表,只能学一样会一样,很难适应千变万化的需求。**
先放一个效果图,然后分析一下实现过程:
@@ -476,12 +476,12 @@ Path 和 SVG 结合通常能诞生出一些奇妙的东西,如下:
这是一个搜索的动效图,通过分析可以得到它应该有四种状态,分别如下:
-状态 |概述
----------|--------------------------------------------------
-初始状态 | 初始状态,没有任何动效,只显示一个搜索标志 :mag:
-准备搜索 | 放大镜图标逐渐变化为一个点
-正在搜索 | 围绕这一个圆环运动,并且线段长度会周期性变化
-准备结束 | 从一个点逐渐变化成为放大镜图标
+| 状态 | 概述 |
+| ---- | --------------------------- |
+| 初始状态 | 初始状态,没有任何动效,只显示一个搜索标志 :mag: |
+| 准备搜索 | 放大镜图标逐渐变化为一个点 |
+| 正在搜索 | 围绕这一个圆环运动,并且线段长度会周期性变化 |
+| 准备结束 | 从一个点逐渐变化成为放大镜图标 |
这些状态是有序转换的,转换流程以及转换条件如下:
diff --git a/CustomView/Advance/[09]Matrix_Basic.md b/CustomView/Advance/[09]Matrix_Basic.md
index 044cbaf4..b4f4b346 100644
--- a/CustomView/Advance/[09]Matrix_Basic.md
+++ b/CustomView/Advance/[09]Matrix_Basic.md
@@ -18,12 +18,12 @@
## 前言
-本文内容偏向理论,和 [画布操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B3%5DCanvas_Convert.md) 有重叠的部分,本文会让你更加深入的了解其中的原理。
+本文内容偏向理论,和 [画布操作](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B03%5DCanvas_Convert.md) 有重叠的部分,本文会让你更加深入的了解其中的原理。
本篇的主角Matrix,是一个一直在后台默默工作的劳动模范,虽然我们所有看到View背后都有着Matrix的功劳,但我们却很少见到它,本篇我们就看看它是何方神圣吧。
>
-由于Google已经对这一部分已经做了很好的封装,所以跳过本部分对实际开发影响并不会太大,不想深究的粗略浏览即可,下一篇中将会详细讲解Matrix的具体用法和技巧。
+>由于Google已经对这一部分已经做了很好的封装,所以跳过本部分对实际开发影响并不会太大,不想深究的粗略浏览即可,下一篇中将会详细讲解Matrix的具体用法和技巧。
******
@@ -138,13 +138,13 @@ y_0 \\\\
\\right ]
$$)
->
-你可能注意到了,我们坐标多了一个1,这是使用了齐次坐标系的缘故,在数学中我们的点和向量都是这样表示的(x, y),两者看起来一样,计算机无法区分,为此让计算机也可以区分它们,增加了一个标志位,增加之后看起来是这样:
>
-(x, y, 1) - 点
-(x, y, 0) - 向量
+> 你可能注意到了,我们坐标多了一个1,这是使用了齐次坐标系的缘故,在数学中我们的点和向量都是这样表示的(x, y),两者看起来一样,计算机无法区分,为此让计算机也可以区分它们,增加了一个标志位,增加之后看起来是这样:
+>
+> (x, y, 1) - 点
+> (x, y, 0) - 向量
>
-另外,齐次坐标具有等比的性质,(2,3,1)、(4,6,2)...(2N,3N,N)表示的均是(2,3)这一个点。(**将MPERSP_2解释为scale这一误解就源于此**)。
+> 另外,齐次坐标具有等比的性质,(2,3,1)、(4,6,2)...(2N,3N,N)表示的均是(2,3)这一个点。(**将MPERSP_2解释为scale这一误解就源于此**)。
图例:
@@ -328,8 +328,8 @@ $$)
### 4.平移(Translate)
->
-此处也是使用齐次坐标的优点体现之一,实际上前面的三个操作使用 2x2 的矩阵也能满足需求,但是使用 2x2 的矩阵,无法将平移操作加入其中,而将坐标扩展为齐次坐标后,将矩阵扩展为 3x3 就可以将算法统一,四种算法均可以使用矩阵乘法完成。
+>
+> 此处也是使用齐次坐标的优点体现之一,实际上前面的三个操作使用 2x2 的矩阵也能满足需求,但是使用 2x2 的矩阵,无法将平移操作加入其中,而将坐标扩展为齐次坐标后,将矩阵扩展为 3x3 就可以将算法统一,四种算法均可以使用矩阵乘法完成。

@@ -616,16 +616,16 @@ $$)
这个方法表,暂时放到这里让大家看看,方法的使用讲解放在下一篇文章中。
-方法类别 | 相关API | 摘要
------------|---------------------------------------------------------|------------------------
-基本方法 | equals hashCode toString toShortString | 比较、 获取哈希值、 转换为字符串
-数值操作 | set reset setValues getValues | 设置、 重置、 设置数值、 获取数值
-数值计算 | mapPoints mapRadius mapRect mapVectors | 计算变换后的数值
-设置(set) | setConcat setRotate setScale setSkew setTranslate | 设置变换
-前乘(pre) | preConcat preRotate preScale preSkew preTranslate | 前乘变换
-后乘(post) | postConcat postRotate postScale postSkew postTranslate | 后乘变换
-特殊方法 | setPolyToPoly setRectToRect rectStaysRect setSinCos | 一些特殊操作
-矩阵相关 | invert isAffine isIdentity | 求逆矩阵、 是否为仿射矩阵、 是否为单位矩阵 ...
+| 方法类别 | 相关API | 摘要 |
+| -------- | ---------------------------------------- | -------------------------- |
+| 基本方法 | equals hashCode toString toShortString | 比较、 获取哈希值、 转换为字符串 |
+| 数值操作 | set reset setValues getValues | 设置、 重置、 设置数值、 获取数值 |
+| 数值计算 | mapPoints mapRadius mapRect mapVectors | 计算变换后的数值 |
+| 设置(set) | setConcat setRotate setScale setSkew setTranslate | 设置变换 |
+| 前乘(pre) | preConcat preRotate preScale preSkew preTranslate | 前乘变换 |
+| 后乘(post) | postConcat postRotate postScale postSkew postTranslate | 后乘变换 |
+| 特殊方法 | setPolyToPoly setRectToRect rectStaysRect setSinCos | 一些特殊操作 |
+| 矩阵相关 | invert isAffine isIdentity | 求逆矩阵、 是否为仿射矩阵、 是否为单位矩阵 ... |
## 总结
From 0ec832b2e334718a4e23b05188abc4d92582c486 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Fri, 23 Sep 2016 19:59:41 +0800
Subject: [PATCH 050/227] Update
---
QuickChart/Canvas.md | 38 ++++++++++++++++++++++++++------------
1 file changed, 26 insertions(+), 12 deletions(-)
diff --git a/QuickChart/Canvas.md b/QuickChart/Canvas.md
index edc90448..5b5f269c 100644
--- a/QuickChart/Canvas.md
+++ b/QuickChart/Canvas.md
@@ -1,14 +1,28 @@
# Canvas常用操作速查表
-操作分类 | 相关API | 备注
----------|---------|--------------
-绘制颜色 | drawColor, drawRGB, drawARGB | 使用单一颜色填充整个画布
**相关链接:**
[【基础-颜色】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Base/%5B3%5DColor.md)
[【Canvas-颜色与基本形状】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B2%5DCanvas_BasicGraphics.md)
-绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
**相关链接 :**
[【Canvas-颜色与基本形状】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B2%5DCanvas_BasicGraphics.md)
-绘制图片 | drawBitmap, drawPicture | 绘制位图和图片
**相关链接:**
[【Canvas-图片文字】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B4%5DCanvas_PictureText.md)
-绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
**相关链接:**
[【Canvas-图片文字】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B4%5DCanvas_PictureText.md)
-绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数
**相关链接:**
[【Path-基本操作】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B5%5DPath_Basic.md)
[【Path-贝塞尔曲线】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B6%5DPath_Bezier.md)
[【Path-完结篇(伪)】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B7%5DPath_Over.md)
[【Path-Path玩出花样(PathMeasure)】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B08%5DPath_Play.md)
-顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
-画布剪裁 | clipPath, clipRect | 设置画布的显示区域
-画布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 会滚到指定状态、 获取保存次数
**相关链接:**
[【Canvas-画布操作】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B3%5DCanvas_Convert.md)
-画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切
**相关链接:**
[【基础-坐标系】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Base/%5B1%5DCoordinateSystem.md)
[【基础-角度弧度】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Base/%5B2%5DAngleAndRadian.md)
[【Canvas-画布操作】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B3%5DCanvas_Convert.md)
-Matrix(矩阵) | getMatrix, setMatrix, concat | 实际画布的位移,缩放等操作的都是图像矩阵Matrix,只不过Matrix比较难以理解和使用,故封装了一些常用的方法。
[【Matrix-原理】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B09%5DMatrix_Basic.md)
+| 操作分类 | 相关API | 备注 |
+| ---------- | ---------------------------------------- | ---------------------------------------- |
+| 绘制颜色 | drawColor, drawRGB, drawARGB | 使用单一颜色填充整个画布
**相关链接:**
[【基础-颜色】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Base/%5B3%5DColor.md)
[【Canvas-颜色与基本形状】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B2%5DCanvas_BasicGraphics.md) |
+| 绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
**相关链接 :**
[【Canvas-颜色与基本形状】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B2%5DCanvas_BasicGraphics.md) |
+| 绘制图片 | drawBitmap, drawPicture | 绘制位图和图片
**相关链接:**
[【Canvas-图片文字】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B4%5DCanvas_PictureText.md) |
+| 绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
**相关链接:**
[【Canvas-图片文字】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B4%5DCanvas_PictureText.md) |
+| 绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数
**相关链接:**
[【Path-基本操作】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B5%5DPath_Basic.md)
[【Path-贝塞尔曲线】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B6%5DPath_Bezier.md)
[【Path-完结篇(伪)】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B7%5DPath_Over.md)
[【Path-Path玩出花样(PathMeasure)】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B08%5DPath_Play.md) |
+| 顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
+| 画布剪裁 | clipPath, clipRect | 设置画布的显示区域 |
+| 画布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 会滚到指定状态、 获取保存次数
**相关链接:**
[【Canvas-画布操作】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B3%5DCanvas_Convert.md) |
+| 画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切
**相关链接:**
[【基础-坐标系】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Base/%5B1%5DCoordinateSystem.md)
[【基础-角度弧度】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Base/%5B2%5DAngleAndRadian.md)
[【Canvas-画布操作】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B3%5DCanvas_Convert.md) |
+| Matrix(矩阵) | getMatrix, setMatrix, concat | 实际画布的位移,缩放等操作的都是图像矩阵Matrix,只不过Matrix比较难以理解和使用,故封装了一些常用的方法。
[【Matrix-原理】](https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B09%5DMatrix_Basic.md) |
+
+| 操作类型 | 相关API | 备注 |
+| ---------- | ---------------------------------------- | ---------------------------------------- |
+| 基础方法 | getDensity, getWidth, getHeight,getDrawFilter,isHardwareAccelerated(API 11),getMaximumBitmapWidth,getMaximumBitmapHeight,getDensity,quickReject,isOpaque,setBitmap,setDrawFilter | 使用单一颜色填充画布 |
+| 绘制颜色 | drawColor, drawRGB, drawARGB,drawPaint | 使用单一颜色填充画布 |
+| 绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧 |
+| 绘制图片 | drawBitmap, drawPicture | 绘制位图和图片 |
+| 绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字 |
+| 绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数 |
+| 顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
+| 画布剪裁 | clipPath, clipRect, clipRegion,getClipBounds | 画布剪裁相关方法 |
+| 画布快照 | save, restore, saveLayer, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数 |
+| 画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切 |
+| Matrix(矩阵) | getMatrix, setMatrix, concat | 实际画布的位移,缩放等操作的都是图像矩阵Matrix,只不过Matrix比较难以理解和使用,故封装了一些常用的方法。 |
From 466ef074b68a5722987f88ee1aabd237fe182011 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Sun, 25 Sep 2016 04:29:55 +0800
Subject: [PATCH 051/227] Update
---
QuickChart/Matrix.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/QuickChart/Matrix.md b/QuickChart/Matrix.md
index 5aecb593..6aaead2c 100644
--- a/QuickChart/Matrix.md
+++ b/QuickChart/Matrix.md
@@ -13,5 +13,7 @@
## 相关文章
-* [Matrix原理](http://www.gcssloop.com/2015/02/Matrix_Basic/)
-* [Matrix详解](http://www.gcssloop.com/2015/02/Matrix_Method/)
+* [安卓自定义View进阶 - Matrix原理](http://www.gcssloop.com/customview/Matrix_Basic/)
+* [安卓自定义View进阶 - Matrix详解](http://www.gcssloop.com/customview/Matrix_Method/)
+* [安卓自定义View进阶 - Matrix Camera](http://www.gcssloop.com/customview/matrix-3d-camera)
+
From 2adc99d34fd6c990cf3d0e96d712137e04e0b809 Mon Sep 17 00:00:00 2001
From: sloop
Date: Mon, 26 Sep 2016 05:41:04 +0800
Subject: [PATCH 052/227] Update
---
.../\345\237\272\346\234\254\350\257\255\346\263\225.md" | 1 +
1 file changed, 1 insertion(+)
create mode 100644 "ChaosCrystal/Markdowm/\345\237\272\346\234\254\350\257\255\346\263\225.md"
diff --git "a/ChaosCrystal/Markdowm/\345\237\272\346\234\254\350\257\255\346\263\225.md" "b/ChaosCrystal/Markdowm/\345\237\272\346\234\254\350\257\255\346\263\225.md"
new file mode 100644
index 00000000..8010bd78
--- /dev/null
+++ "b/ChaosCrystal/Markdowm/\345\237\272\346\234\254\350\257\255\346\263\225.md"
@@ -0,0 +1 @@
+# Markdown 基本语法
From 11daf0d294d204cf04574a15eeed639476681ba5 Mon Sep 17 00:00:00 2001
From: sloop
Date: Tue, 27 Sep 2016 22:52:14 +0800
Subject: [PATCH 053/227] Update
---
.../\345\237\272\346\234\254\350\257\255\346\263\225.md" | 2 ++
1 file changed, 2 insertions(+)
diff --git "a/ChaosCrystal/Markdowm/\345\237\272\346\234\254\350\257\255\346\263\225.md" "b/ChaosCrystal/Markdowm/\345\237\272\346\234\254\350\257\255\346\263\225.md"
index 8010bd78..b711b2b6 100644
--- "a/ChaosCrystal/Markdowm/\345\237\272\346\234\254\350\257\255\346\263\225.md"
+++ "b/ChaosCrystal/Markdowm/\345\237\272\346\234\254\350\257\255\346\263\225.md"
@@ -1 +1,3 @@
# Markdown 基本语法
+
+## 标题
From bdcbc29ffbfd9f3075e7c895e339b69ad84da573 Mon Sep 17 00:00:00 2001
From: GcsSloop
Date: Wed, 28 Sep 2016 03:27:39 +0800
Subject: [PATCH 054/227] Update
---
CustomView/Demo/dispatchTouchEventDemo.zip | Bin 0 -> 54682 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 CustomView/Demo/dispatchTouchEventDemo.zip
diff --git a/CustomView/Demo/dispatchTouchEventDemo.zip b/CustomView/Demo/dispatchTouchEventDemo.zip
new file mode 100644
index 0000000000000000000000000000000000000000..ea52c7c4dcf87ee8383f607b153915b99928a1a3
GIT binary patch
literal 54682
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