1、什么是Matrix?
官方的释义:The Matrix class holds a 3x3 matrix for transforming coordinates
翻译一下:矩阵类包含一个用于变换坐标的3x3矩阵
2、如何搞懂Matrix?
我们只关心旋转、位移和缩放,只有透视和错切暂时不理会,有兴趣的自行研究。
我们先测试一下,看看Matrix里面到底存的是什么:
Matrix mMatrix=new Matrix();
//打印矩阵中的数据
Log.i("Matrix",mMatrix.toShortString());
结果:
[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
放到上面的图里,看一下:
然后我们验证一下:
Matrix mMatrix=new Matrix();
//放到两倍
mMatrix.postScale(2,2);
Log.i("Matrix",mMatrix.toShortString());
结果:
[2.0, 0.0, 0.0][0.0, 2.0, 0.0][0.0, 0.0, 1.0]
Matrix mMatrix=new Matrix();
//放到两倍
mMatrix.postScale(2,2);
//平移X-150,Y-200
mMatrix.postTranslate(150,200);
Log.i("Matrix",mMatrix.toShortString());
结果:
[2.0, 0.0, 150.0][0.0, 2.0, 200.0][0.0, 0.0, 1.0]
//旋转30度
mMatrix.postRotate(30);
Log.i("Matrix",mMatrix.toShortString());
double sin = Math.sin(30);
Log.i("Matrix",sin+"");
结果:
[0.8660254, -0.5, 0.0][0.5, 0.8660254, 0.0][0.0, 0.0, 1.0]
-0.9880316240928618
关于角度的测试,直接引用http://www.gcssloop.com/customview/Matrix_Basic gcssloop大神的解释,本人几何功底有限,不甚明了,会用就行。
3、一个例子,搞懂post和pre
例:
实现X方向平移100像素,Y方向平移100像素,放大2倍。默认情况下,Matrix的操作坐标为左上角为0,0起始点。
代码一:
代码一:先平移,在缩放,结果平移了200,200像素,不是想要的结果。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//要实现的效果,平移X-100,Y-100,放大2倍
//进行平移
mScaleMatrix.postTranslate(100, 100);
//进行缩放
mScaleMatrix.postScale(2, 2);
//设置这个矩阵
setImageMatrix(mScaleMatrix);
}
代码二:
代码二:想缩放,在平移,平移100,100,放大2倍,是想要的结果
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//要实现的效果,平移X-100,Y-100,放大2倍
//进行缩放
mScaleMatrix.postScale(2, 2);
//进行平移
mScaleMatrix.postTranslate(100, 100);
//设置这个矩阵
setImageMatrix(mScaleMatrix);
}
分析:
为什么只是改变一下平移和缩放的顺序,结果就不一样了呢?这要了解Matrix的乘操作,还要知道post的时候到底是如何操作的。我们想打印一下,看看Matrix里面到底是什么,平移和缩放操作后Matrix到底发生了什么变化。
代码二:想缩放,在平移,平移100,100,放大2倍,是想要的结果
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//要实现的效果,平移X-100,Y-100,放大2倍
//进行缩放
mScaleMatrix.postScale(2, 2);
//进行平移
mScaleMatrix.postTranslate(100, 100);
//设置这个矩阵
setImageMatrix(mScaleMatrix);
}
I/MatrixView: 初始状态: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
I/MatrixView: 缩放后状态:[2.0, 0.0, 0.0][0.0, 2.0, 0.0][0.0, 0.0, 1.0]
I/MatrixView: 平移后状态:[2.0, 0.0, 100.0][0.0, 2.0, 100.0][0.0, 0.0, 1.0]
根据上面的解释,我们可以发现,Matrix中的数据是正确的,可以实现平移100,100,放到2倍的效果。
代码一:先平移,在缩放,结果平移了200,200像素,不是想要的结果。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//要实现的效果,平移X-100,Y-100,放大2倍
//进行平移
mScaleMatrix.postTranslate(100, 100);
//进行缩放
mScaleMatrix.postScale(2, 2);
//设置这个矩阵
setImageMatrix(mScaleMatrix);
}
I/MatrixView: 初始状态:[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
I/MatrixView: 平移后状态:[1.0, 0.0, 100.0][0.0, 1.0, 100.0][0.0, 0.0, 1.0]
I/MatrixView: 缩放后状态:[2.0, 0.0, 200.0][0.0, 2.0, 200.0][0.0, 0.0, 1.0]
我们发现,这里平移成了200,200.
为什么我们只是更改了一下平移和缩放的执行顺序,结果就不同了呢?这个问题困扰了很多初次接触Matrix的攻城狮,这里做一下解释,希望可以帮助更多人理解好Matrix的操作。
首先,我们看一下源码,以平移为例。
/**
* 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);
}
postTranslate:
/**
* 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);
}
pre是拿当前的矩阵乘以T,而post是拿T来乘以当前的矩阵,(矩阵的乘法是不满足交换率的,所以这两种乘法的结果是不一样的)
在图形学中,矩阵M右乘A,表示的是 A * M,而矩阵 M 左乘 A,则表示的是 M * A,可以形象地理解为右乘就是从右边乘进来,左乘就是从左边乘进来。
一比较,我们可以看出,pre其实执行的就是右乘的操作,而post执行的就是左乘的操作。
这是因为,在图像处理中,越靠近右边的矩阵越先执行,所以pre(也就是先的意思)所设置的矩阵T(Scale,Rotation也是一样的)就会先于其一开始设置的Scale执行,而post(后的意思)的因为是左乘,所以它会放在最左边,那么就会最后执行。
那么平移和缩放操作顺序执行时,顺序的先后,导致Matrix的乘操作不同,进而结果也不同。
代码三:
代码三:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//要实现的效果,平移X-100,Y-100,放大2倍
Log.i(TAG,"初始状态:"+mScaleMatrix.toShortString());
//进行平移
mScaleMatrix.preTranslate(100, 100);
Log.i(TAG,"平移后状态:"+mScaleMatrix.toShortString());
//进行缩放
mScaleMatrix.preScale(2, 2);
Log.i(TAG,"缩放后状态:"+mScaleMatrix.toShortString());
//设置这个矩阵
setImageMatrix(mScaleMatrix);
}
代码三,大家应该可以自己理解了。
关键类的完整代码:
package hongzhen.com.matrixdemo;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
@SuppressLint("AppCompatCustomView")
public class MatrixView extends ImageView {
private String TAG="MatrixView";
private Matrix mScaleMatrix;
private Paint paint;
enum Mode {
NONE, DOWN, MOVE
}
private Mode mode; //当前mode
public MatrixView(Context context) {
this(context, null);
}
public MatrixView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MatrixView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setScaleType(ScaleType.MATRIX);
mScaleMatrix = new Matrix();
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(2);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//要实现的效果,平移X-100,Y-100,放大2倍
Log.i(TAG,"初始状态:"+mScaleMatrix.toShortString());
//进行平移
mScaleMatrix.preTranslate(100, 100);
Log.i(TAG,"平移后状态:"+mScaleMatrix.toShortString());
//进行缩放
mScaleMatrix.preScale(2, 2);
Log.i(TAG,"缩放后状态:"+mScaleMatrix.toShortString());
//设置这个矩阵
setImageMatrix(mScaleMatrix);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制100像素的标线,用于观察图片的平移
canvas.drawLine(100, 0, 100, getHeight(), paint);
canvas.drawLine(0, 100, getWidth(), 100, paint);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<hongzhen.com.matrixdemo.MatrixView
android:src="@mipmap/ic_launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>