写点什么

Android----Matrix- 与坐标变换

用户头像
Android架构
关注
发布于: 1 小时前

// 在构造函数中调用 private void initImageView() {mMatrix = new Matrix();mScaleType = ScaleType.FIT_CENTER;}


public void setImageMatrix(Matrix matrix) {// 省略部分代码...// 分析点 1:参数 matrix 的值拷贝到 mMatrixmMatrix.set(matrix);


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


// 分析点 2:设置 mDrawMatrixconfigureBounds();// 重绘:触发 onDraw(Canvas)invalidate();}


// Matrix.java


// 分析点 1:参数 matrix 的值拷贝到 mMatrixpublic void set(Matrix src) {if (src == null) {reset();} else {// native 方法 nSet(native_instance, src.native_instance);}}


可以看到,mMatrixImageView的构造器中就创建了,另外ImageView还提供了setImageMatrix(Matrix)供外部设置。那么mDrawMatrix是在哪里创建的呢?


// ImageView.java


// 分析点 2:设置 mDrawMatrixprivate void configureBounds() {// 省略部分代码...if (ScaleType.CENTER == mScaleType) {mDrawMatrix = mMatrix;// 居中 mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),Math.round((vheight - dheight) * 0.5f));}// 省略部分代码...


}


configureBounds()里有多个分支,其中有些分支里将mMatrix赋值给mDrawMatrix,说明两者是同一个对象。

步骤 2:设置矩阵

创建矩阵之后,就可以使用Matrix提供的方法设置矩阵了,例如上面的代码在ScaleTypeScaleType.CENTER时使用setTranslate()设置为居中。


// ImageView.java


mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f), Math.round((vheight - dheight) * 0.5f));


当然了,创建并设置好Matrix之后,再使用ImageView#setImageMatrix()设置进来也可以达到同样的效果。

步骤 3:使用矩阵进行坐标变换

现在我们看使用mDrawMatrix的地方:


// ImageView.java


protected void onDraw(Canvas canvas) {super.onDraw(canvas);// 省略部分代码...if (mDrawMatrix != null) {// 分析点 1:左乘 mDrawMatrixcanvas.concat(mDrawMatrix);}mDrawable.draw(canvas);}


// Canvas.java


// 分析点 1:左乘 mDrawMatrixpublic void concat(@Nullable Matrix matrix) {if (matrix != null) nConcat(mNativeCanvasWrapper, matrix.native_instance);}


可以看到,ImageView#onDraw(Canvas)中对Canvas左乘mDrawMatrix,前面说到:**矩阵左乘相当于一次坐标变换。**我们通过下面一个简单的例子展示了ImageView设置Matrix前后的效果:


// 图一:未设置 Matrixiv.setBackgroundColor(0xFF999999.toInt())iv.scaleType = ImageView.ScaleType.MATRIXiv.setImageResource(R.color.colorAccent)


// 图二:设置 Matrix,缩放到两倍 val matrix = Matrix().apply {setScale(2F,2F)}iv.imageMatrix = matrix



在后续的文章里,我将专门写一篇文章分享更多ImageView源码的细节,感兴趣的同学点一点关注哦



3. Matrix 源码分析

从这一节开始我们来阅读Matrix的源码,源码中出现了native方法,这意味着Matrix中的部分源码是在native层实现,具体分为:Matrix.hMatrix.cppMatrix.java

3.1 Java 层初始化

// Matrix.javapublic final long native_instance;


// sizeof(SkMatrix) is 9 * sizeof(float) + uint32_tprivate static final long NATIVE_ALLOCATION_SIZE = 40;


private static class NoImagePreloadHolder {// 单例 public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(Matrix.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE);}


// 从 Android 8 开始,使用 NativeAllocationRegistry 帮助回收 native 层内存 public Matrix() {// 创建一个 native 层对象,具体为 SkMatrixnative_instance = nCreate(0);NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);}


// 在 Android 8 之前 public Matrix() {native_instance = native_create(0);}


// Java 层初始化// ---------------------------------------------------------------------// native 层初始化 private static native long nCreate(long nSrc_or_zero);


static jlong create(JNIEnv* env, jobject clazz, jlong srcHandle) {const SkMatrix* src = reinterpret_cast<SkMatrix*>(srcHandle);SkMatrix* obj = new SkMatrix();if (src)// 浅拷贝*obj = *src;else// 重置 obj->reset();return reinterpret_cast<jlong>(obj);}


Java层初始化要点如下:


  • Matrix构造器在native创建了一个SkMatrix对象,并通过reinterpret_cast强制转换为long赋值给Java层的native_instance

  • MatrixJava层其实没有太多操作,真正完成任务的实体是native层的SkMatrixSKMatrix是 Skia 图形引擎提供的用于完成坐标变换的 3 x 3 矩阵

  • Android 8开始,使用NativeAllocationRegistry帮助回收 native 层内存。NativeAllocationRegistry绑定了Java层和native层的两个对象,并标记内存大小为 40 字节,为什么是 40 个字节呢?我们在源码里寻找答案:SkMatrix.hSkMatrix.cpp


# 提示

NativeAllocationRegistry是用来帮助回收native层内存的,即当Java层对象被垃圾回收时,立即去释放Native层的内存,在CanvasBitmap等类中也有同样的机制,详见文章:《Android | 带你理解 NativeAllocationRegistry 的原理与设计思想》

3.2 native 层初始化

// SkMatrix.h


SK_BEGIN_REQUIRE_DENSEclass SK_API SkMatrix {public:enum {kMScaleX, //!< horizontal scale factorkMSkewX, //!< horizontal skew factorkMTransX, //!< horizontal translationkMSkewY, //!< vertical skew factorkMScaleY, //!< vertical scale factorkMTransY, //!< vertical translationkMPersp0, //!< input x perspective factorkMPersp1, //!< input y perspective factorkMPersp2, //!< perspective bias};


// 分析点 1:SkScalar get(int index) const {SkASSERT((unsigned)index < 9);return fMat[index];}// 分析点 2:重置 void reset();


// 判断是否为单位矩阵,使用单位矩阵进行矩阵乘法是无效的 bool isIdentity() const {return this->getType() == 0;}


private:SkScalar fMat[9];mutable uint32_t fTypeMask;


// SkMatrix.cpp


// 分析点 2:重置为单位矩阵 void SkMatrix::reset() {fMat[kMScaleX] = fMat[kMScaleY] = fMat[kMPersp2] = 1;fMat[kMSkewX] = fMat[kMSkewY] =fMat[kMTransX] = fMat[kMTransY] =fMat[kMPersp0] = fMat[kMPersp1] = 0;this->setTypeMask(kIdentity_Mask | kRectStaysRect_Mask);}


// SkScalar.h


typedef float SkScalar;


native层初始化要点如下:


  • SkMatrix有两个字段:大小为 9 的数组fMatunit21_tfTypeMask,其中SkScalar其实是一个float,具体可以查看:SkScalar.h,现在你知道 **40 个字节(8 * 4 + 4 = 40)**是如何的来了吗?

  • SkMatrix逻辑上是一个 3 x 3 矩阵,物理上是一个 1 x 9 数组

  • 初始化时会调用reset(),设置为单位矩阵,注意:使用单位矩阵进行矩阵乘法是无效的


3.3 设置矩阵

前面我们理解了Matrix初始化时是一个单位矩阵,现在我们开始为矩阵的元素赋值。从Java层源码可以看到,Matrix的方法主要分为setXXX()preXXX()postXXX()三大类,这三类方法有什么区别呢?我们以scale为例:


// Matrix.java


// setpublic void setScale(float sx, float sy, float px, float py) {nSetScale(native_instance, sx, sy, px, py);}// 左乘 public boolean preScale(float sx, float sy, float px, float py) {nPreScale(native_instance, sx, sy, px, py);return true;}// 右乘 public boolean postScale(float sx, float sy, float px, float py) {nPostScale(native_instance, sx, sy, px, py);return true;}


// Java 层// ---------------------------------------------------------------------

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android----Matrix-与坐标变换