以下针对 Matrix.setPolyToPoly()
的实现原理、应用场景及优化策略进行全面解析,结合数学原理与Android系统级实践,深入说明其技术细节。
🔧 一、函数基础与数学原理
1. 函数定义与参数
java
boolean setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)
-
参数解析:
src
:原始点坐标数组,格式为[x0, y0, x1, y1, ...]
,每两个浮点数表示一个点。dst
:目标点坐标数组,与src
的点顺序严格对应。pointCount
:参与变换的点数(0~4),决定变换自由度。
2. 数学本质:透视变换矩阵
变换本质是求解 3×3 透视变换矩阵,满足:
x′y′1=adgbehcfi×xy1
其中:
- (x,y) 为原始坐标,(x′,y′) 为目标坐标。
- 当 pointCount=4 时,通过4组点映射关系解算全部9个参数(含透视分量 g,h,i)。
3. 点的选择与映射规则
- 关键点选择 :只需指定 矩形的4个角点(非全部像素点),即可完全定义变换。
- 索引对应 :
src[0]→左上角
必须映射到dst[0]→新左上角
,顺序不可错乱,否则导致扭曲异常。
📐 二、点数量与变换能力详解
1. **pointCount=0
:重置矩阵**
- 等效于
reset()
,生成单位矩阵。
2. **pointCount=1
:纯平移**
-
数学形式 :
x′=x+dx,y′=y+dy
-
代码示例(向右平移100像素):
inifloat[] src = {0, 0}; float[] dst = {100, 0}; matrix.setPolyToPoly(src, 0, dst, 0, 1);
3. **pointCount=2
:平移+旋转+缩放**
-
数学原理:
- 缩放因子 = ∥src1−src0∥∥dst1−dst0∥
- 旋转角度 = θdst−θsrc(向量夹角差)。
-
示例(绕中心点旋转90°):
scssfloat[] src = {width/2, height/2, width, height/2}; // 中心点+右侧中点 float[] dst = {width/2, height/2, width/2, height}; // 中心点不变,右侧中点移至下方 matrix.setPolyToPoly(src, 0, dst, 0, 2);
4. **pointCount=3
:平移+旋转+缩放+错切**
-
效果:矩形 → 平行四边形。
-
示例(水平错切):
scssfloat[] src = {0, 0, 0, height, width, height}; // 左下、左上、右下 float[] dst = {0, 0, 100, height, width+100, height}; // 左侧固定,右侧右移
5. **pointCount=4
:任意透视扭曲**
-
核心能力 :支持 近大远小 的3D效果(书本折叠的核心)。
-
书本折叠典型配置:
scssfloat[] dst = { 0, 0, // 左上固定 width, foldFactor*50, // 右上下移(模拟重力) width, height, // 右下固定 foldFactor*30, height - foldFactor*20 // 左下偏移 };
📖 三、书本折叠效果完整实现
1. 分割图片为折叠单元
ini
int foldCount = 8; // 分为8个折叠条
int foldWidth = bitmap.getWidth() / foldCount;
for (int i = 0; i < foldCount; i++) {
Rect srcRect = new Rect(i * foldWidth, 0, (i+1) * foldWidth, height);
}
2. 动态计算顶点映射
-
奇偶单元反向折叠(Z字型):
scssfloat offsetX = foldWidth * foldFactor * (i % 2 == 0 ? 1 : -1); float offsetY = height * foldFactor * 0.3f; // Y轴下移模拟重力 float[] dst = { 0, 0, // 左上 foldWidth, offsetY, // 右上 foldWidth, height, // 右下 offsetX, height - offsetY // 左下 };
3. 阴影增强立体感
-
渐变阴影绘制:
iniLinearGradient shadowShader = new LinearGradient( 0, 0, foldWidth, 0, Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP ); Paint shadowPaint = new Paint(); shadowPaint.setShader(shadowShader); shadowPaint.setAlpha(180); // 70%透明度 canvas.drawRect(0, 0, foldWidth, height, shadowPaint); // 绘制在折叠边缘
4. 动画驱动与性能优化
-
ValueAnimator控制折叠进度:
iniValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); animator.addUpdateListener(animation -> { foldFactor = (float) animation.getAnimatedValue(); invalidate(); // 触发重绘 }); animator.start();
-
性能关键点:
- 复用
Bitmap
避免重复解码。 - 使用
Canvas.saveLayer()
离屏缓冲减少重绘区域。
- 复用
⚙️ 四、高级技巧与避坑指南
1. 透视增强:非线性偏移
-
采用 二次曲线 模拟物理折叠加速度:
arduinofloat offsetY = height * (float) Math.pow(foldFactor, 2);
2. 矩阵操作顺序陷阱
-
**
set
/pre
/post
调用顺序**:-
set
清空当前矩阵,pre
在队列头部插入,post
在尾部插入。 -
错误示例:
scssmatrix.setTranslate(100, 100); matrix.preRotate(45); // 旋转会作用于平移后的坐标
-
正确做法 :避免混用
set
与pre/post
,优先用pre/post
链式操作。
-
3. 数值稳定性问题
-
退化情况处理 :当目标点共线时,矩阵不可逆(返回
false
),需增加校验:scssif (!matrix.setPolyToPoly(src, 0, dst, 0, 4)) { matrix.reset(); // 退化时恢复单位矩阵 }
4. 非矩形图形的处理
- 三角形/不规则多边形需按 实际顶点数 指定
pointCount
(如三角形用3个点)。 - 边缘锯齿问题:启用抗锯齿
paint.setAntiAlias(true)
。
💎 总结与适用场景
场景 | 推荐点数 | 优势 | 性能开销 |
---|---|---|---|
图标平移 | 1点 | 代码简单 | 最低 |
图片旋转/缩放 | 2点 | 支持仿射变换 | 低 |
按钮倾斜效果 | 3点 | 实现错切 | 中 |
书本折叠/3D翻页 | 4点 | 唯一支持透视变换的方案 | 高 |
开发建议:
- 书本折叠必须用 4点变换,且需配合阴影与非线性动画提升真实感。
- 避免在每帧重复计算固定参数(如矩形角点),应在
onSizeChanged()
中预计算。- 完整代码参考:Google FoldingLayout 或 开源实现Folding-Android。