1 Matrix 在Android 中的运用
- ImageView 的ScaleType,通过 setImageMatrix(mMatrix) 方法,可以自定义一个Matrix。 对 Bitmap 做相应的处理,如缩放,位移。
- Matrix 可以对 Rect 和 点的集合做变换处理
public void mapPoints(float[] pts) {
mapPoints(pts, 0, pts, 0, pts.length >> 1);
}
public boolean mapRect(RectF rect) {
return mapRect(rect, rect);
}
- Android 中 Path 路径也可以做相应的 Matrix 变换
/**
* Transform the points in this path by matrix.
*
* @param matrix The matrix to apply to the path
*/
public void transform(Matrix matrix) {
isSimplePath = false;
nTransform(mNativePath, matrix.native_instance);
}
2 矩阵的乘法运算
以下内容摘自百度百科
矩阵乘法的前提条件 : A*B = C ,A 的列数 和 B的行数相等 。 C 的行数等于 A 的行数 ,列数等于B的列数
矩阵的乘法 满足结合律 。不满足交换律
乘法结合律: (AB)C=A(BC)
乘法交换律: (AB)!= (BA) 。
矩阵乘法的实际案例 :
3 单位矩阵
Android 中矩阵的实体结构
从字面上就可以理解 :
- MTRANS_X 是和 位移相关
- MSCALE_X 是和 缩放相关
- MSKEW_X 是和 错切相关
其中 最后一行 的 MPERSP 与 3d 变换有关 。本文不做解析。
在矩阵的乘法中,有一种矩阵起着特殊的作用,如同数的乘法中的1,这种矩阵被称为单位矩阵。它是个方阵,从左上角到右下角的对角线(称为主对角线)上的元素均为1。除此以外全都为0。
当我们New 一个Matrix 的时候 ,创建的是 单位矩阵
单位矩阵的特点:任何矩阵与单位矩阵相乘都等于本身,无论左乘右乘
测试New 的Matrix 矩阵 实体:
Matrix matrix = new Matrix();
Log.e("tag", "MatrixTest" + matrix.toShortString());
Log 打印如下 : MatrixTest[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
单位矩阵的这种性质,在我们 对Matrix 只做单次变换时 ,例如 preTranslate 和 postTranslate 是等价的。
4 Android 中Matrix Pre ,Post变换对应的矩阵乘法
不管是图形,文字,图片,都是由无数个坐标点形成的。对坐标点的变化才是最根本的变换。
Android 中 点的坐标 的表示
上面的3*3 的 变化矩阵 右乘 坐标矩阵。就是 变化后的坐标
pre 操作代表 右乘 M(原始) * M( 位移变化)
/**
* Preconcats the matrix with the specified translation.
* M' = M * T(dx, dy)
*/
public boolean preTranslate(float dx, float dy) {
return native_preTranslate(native_instance, dx, dy);
}
post 操作 代表 左乘 M( 位移变化) * M(原始)
/**
* Postconcats the matrix with the specified translation.
* M' = T(dx, dy) * M
*/
public boolean postTranslate(float dx, float dy) {
return native_postTranslate(native_instance, dx, dy);
}
5 Pre ,Post 的执行顺序 。
我们使用 Matrix 对坐标点point 的变化如下
Matrix matrix = new Matrix();
matrix.preTranslate(1000, 1000);
matrix.preScale(0.5f, 0.5f);
matrix.postScale(0.5f, 0.5f);
matrix.postTranslate(1000, 1000);
float[] point = new float[2];
point[0] = 0;
point[1] = 0;
matrix.mapPoints(point);
最终会转换成 矩阵的乘法运算
- trans1 = postTranslate(1000, 1000);
- trans2 = postScale(0.5f, 0.5f);
- trans3 = preTranslate(1000, 1000);
- trans4 = preTranslate(1000, 1000);
关于Matrix的博文 一般会提到:pre 先执行 ,post 后执行 。
由于矩阵的乘法运算满足结合率 。也就是
先相乘是没问题的,得到一个新的 坐标矩阵
上述坐标(X0,Y0), 先执行位移变换,得到一个新的坐标(Xnew, Ynew),再执行缩放变换得到一个新的坐标(Xnew2 ,Ynew2)。再。。。。
结论就是 :Matrix 的pre 和 post 操作。会转换为一系列的矩阵相乘 。最后右乘 坐标矩阵 。 越靠近右边的Matrix变换 ,会先对坐标点做处理 。
这样看来,pre是右乘操作,说是 pre 先执行,也是没有毛病的。
6 Pre ,Post 实际操作的例子
1 将图片放置到屏幕中心,并放大 2倍,在顺时针旋转90度。
Matrix matrix;
Paint paint;
Bitmap bitmap;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
matrix = new Matrix();
paint = new Paint();
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.touxiang, null);
}
/***
* 测试View 的大小和屏幕大小相同
* 将图片以屏幕中心为缩放中心 ,先放大2倍, 再顺时针旋转 90度 。
*/
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, matrix, paint);
int postTranslateX = getWidth() / 2 - bitmap.getWidth() / 2;
int postTranslateY = getHeight() / 2 - bitmap.getHeight() / 2;
// 将图片 位移 置 屏幕中心
matrix.postTranslate(postTranslateX, postTranslateY);
// 以屏幕中心为旋转点和缩放点 ,不指定坐标就是,以原点为中心
matrix.postScale(2, 2, getWidth() / 2, getHeight() / 2);
matrix.postRotate(90, getWidth() / 2, getHeight() / 2);
canvas.drawBitmap(bitmap, matrix, paint);
}
7 将Matrix 运用于图表绘制
效果图如下: 这是我下一周的股票收益折线图
public class MatrixTestView extends View {
public MatrixTestView(Context context) {
super(context);
}
public MatrixTestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MatrixTestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MatrixTestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
Matrix matrix;
Paint paint;
Bitmap bitmap;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
matrix = new Matrix();
paint = new Paint();
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.touxiang, null);
mapping();
}
@Override
protected void onDraw(Canvas canvas) {
drawProfitChart(canvas);
}
/***
* 绘制股票每天收益的折现图
* 第一天 :5000
* 第二天 :1000
* 第三天 :3000
* 第四天 :500
* 第五天 :8000
* 首先是坐标系的确立。这里我们选取 View 的 Padding 100px 为 图表的显示区域 。
* X 轴的坐标 最大值是 7
* Y 轴的坐标最大值为 10000
* 下一步就是将 实际的 点 (0,0)(7,0)(0,10000)(1,1000) (2,3000) 映射到 Canvas 的坐标系上 。
*
*/
private void drawProfitChart(Canvas canvas){
paint.setColor(Color.BLACK);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.STROKE);
// 第一步画 坐标轴 两条线
Path path = new Path();
path.moveTo(0,10000);
path.lineTo(0,0);
path.lineTo(5,0);
path.transform(mappingMatrix);
canvas.drawPath(path,paint);
// 第二步绘制 折线图
Path valuePath = new Path();
valuePath.moveTo(1,5000);
valuePath.lineTo(2,1000);
valuePath.lineTo(3,3000);
valuePath.lineTo(4,500);
valuePath.lineTo(5,10000);
valuePath.transform(mappingMatrix);
Log.e("mytest"," valuePath " +valuePath.toString());
paint.setColor(Color.RED);
canvas.drawPath(valuePath,paint);
// 第三步绘制点
paint.reset();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
paint.setTextSize(50);
float[] values = new float[]{1,5000,2,1000,3,3000,4,500,5,10000};
float[] valuesCopy = values.clone();
// 将坐标点变换
mappingMatrix.mapPoints(valuesCopy);
for(int i=0;i<valuesCopy.length;i+=2){
float x = valuesCopy[i];
float y = valuesCopy[i + 1];
String valueText = (int)values[i+1]+"";
// 绘制原点
canvas.drawCircle(x,y,20,paint);
// 绘制文字
canvas.drawText(valueText, x-60, y-80, paint);
}
// 第四步绘制坐标值 ,省略
}
private Matrix mappingMatrix = new Matrix();
private int paddingLeftOrRight = 100;
private int paddingTopOrBottom = 300;
/***
* 图表 的 X 轴范围是 7 ,Y 轴范围是 10000 。
* 对应的 Canvas 坐标体系 。X轴范围 是 view .getWidth() - padding*2
* Y轴范围 是 view .getHeight() - padding*2
* 第一步:创建缩放 操作对应的 Matrix 。将 X 和 Y 放大 (view .getWidth() - padding*2)/7 。(view .getHeight() - padding*2)/10000
* Canvas 坐标系的 Y轴方向是朝下的 ,如果想让图表Y 轴的方向是 朝上。 再对matrix 做翻转处理 matrix.postScale(1,-1);
* 此时,图表的显示区域在 Canvas 的显示区域之外, 图表的Y轴坐标已经被转换成了负数 。
* 需要向下做位移 View.getHeight() - padding 。为了绘制PaddingLeft 。 需要向左位移 Paddiing 。
*/
private void mapping(){
float scaleX = (float)(getWidth() - paddingLeftOrRight * 2) / 5;
float scaleY = (float)-(getHeight() - paddingTopOrBottom * 2) / 10000;
mappingMatrix.reset();
mappingMatrix.postScale(scaleX, scaleY);
mappingMatrix.postTranslate(paddingLeftOrRight, getHeight() - paddingTopOrBottom);
}
}