uCrop源码思路分析--流程梳理

首先要把思路弄清楚,先将这个项目运行在手机上,玩一玩,看看他都有哪些功能,然后再考虑那些功能是怎么实现的。

项目Github链接

第一阶段

折腾一番后就大致有个印象了,接下就可以分析具体功能了,且不看布局这些是怎么弄的,先从这个库的入口开始分析,就是我们是怎么与图片进行交互的,手势! 对,我们通过手势来改变图片的显示,可以平移,旋转,放大,我们应该从这里切入,整个思路就会比较清晰。

那么,问题来了。

如何分配这些功能,我应该创建几个类,他们之间应该是怎样的继承关系等等,这就是体现作者功底的时候了,究竟该如何架构,是很值得研究的问题。

1.手势的处理

我们需要一个类,这个类可以识别用户的手势,然后调用相应的方法。这里肯定涉及到多点触控

问题1:如何判断用户的最后一根手指已经释放?

假如我们现在已经拿到了用户的手势,那么肯定需要接着调用某个类的某种方法来进行处理,比如,我们已经识别出是一个平移的手势,势必有一个postTranslate(disX,disY)方法来对应。

那具体平移的操作又该放在哪个类中呢,还有缩放,旋转,裁剪等等方法。

问题2:如何确定类的功能,如何去耦合

我是想不到一个完美的方案来解决这个问题,或者说,我必须要在做的过程中慢慢的优化架构,而暂时不具备在开始项目的时候就直接描绘出轮廓,毕竟是菜鸟。

分层的思想

作者在这里分出了三个类,各有不同的功能

GestureCropImageView

专门识别用户的手势,平移,缩放,旋转。他并不关心怎样去做具体的操作,也不关心裁剪。

TransformImageView

基本功能包括加载图片,对图片进行平移,缩放,旋转,并且通知监听者正在进行哪些操作。

CropImageView

核心类。具体的手势识别,具体的平移操作,这些都只是一个功能点,这个类的作用更像是一个管理者,负责将这些功能点组合起来,达到好的用户体验和库真正功能的实现。



第二阶段

第一阶段只是一个大致的印象,有了一个宏观脉络,可以到现在,我们还一行代码都没看呢?

折腾这个库的时候,我很好奇他进去的时候是如何摆放图片和裁剪提示框的,如何做到正好能让他们吻合。

问题3:怎样初始化我们的裁剪控件,达到和裁剪提示框吻合的效果

作者使用了一个uCropView作为外层嵌套布局,里面包含我们的裁剪提示框OverlayView和裁剪控件GestureCropImageView.

因为作者将这个库设计得很灵活,用户可以选择指定的裁剪比例,有16:9,4:3等,还可以使用原生图片的宽高比,并且在控件中可以动态调整裁剪比例,这就必然需要两个控件之间进行交互,最好的办法就是设置一个监听器。

mGestureCropImageView.setCropBoundsChangeListener(new CropImageView.CropBoundsChangeListener() {
            @Override
            public void onCropBoundsChangedRotate(float cropRatio) {
                if (mOverlayView != null) {
                    mOverlayView.setTargetAspectRatio(cropRatio);
                    mOverlayView.postInvalidate();
                }
            }
        });

具体使用场景我们后面再看。

根据作者的布局,TransformImageView 是顶层父类(至于为什么这么架构,也可以有小小的思考)。看看他做了哪些事情。

这时我们要跳到外面的UCropActivity中去看看数据是怎样给的。

在setImageData()方法中,会指定图片的uri

mGestureCropImageView.setImageUri(inputUri, mOutputUri);

TransformImageView

   public void setImageUri(@NonNull Uri imageUri, @NonNull Uri outputUri) throws Exception {
        mImageUri = imageUri;
        int maxBitmapSize = getMaxBitmapSize();

        BitmapLoadUtils.decodeBitmapInBackground(getContext(), imageUri, outputUri, maxBitmapSize, maxBitmapSize,
                new BitmapLoadCallback() {
                    @Override
                    public void onBitmapLoaded(@NonNull final Bitmap bitmap) {
                        mBitmapWasLoaded = true;
                        setImageBitmap(bitmap);
                        invalidate();
                    }

                    @Override
                    public void onFailure(@NonNull Exception bitmapWorkerException) {
                        Log.e(TAG, "onFailure: setImageUri", bitmapWorkerException);
                        if (mTransformImageListener != null) {
                            mTransformImageListener.onLoadFailure(bitmapWorkerException);
                        }
                    }
                });
    }

我们暂时只需要知道这是一个后台解析图片的方法,成功后可以拿到Bitmap,设置给ImageView,然后重新布局,重新绘制。

就我感觉,我们之前说的根据裁剪比例来布局图片现在应该可以体现了。

根据上面的代码,我们已经在UCropView中给裁剪控件设置了比例,CropImageView重写了onImageLaidOut()方法,很重要,这里会确定裁剪框矩形和图片矩形。

 //在Random模式下,走的是这段代码
        if (mTargetAspectRatio == SOURCE_IMAGE_ASPECT_RATIO) {
            mTargetAspectRatio = drawableWidth / drawableHeight;
        }

        setupCropBounds(); //设置裁剪框矩形
        setupInitialImagePosition(drawableWidth, drawableHeight); //设置图片初始位置

至于设置裁剪框的细节代码,我们后面会拉出来分析,这里是梳理前期的流程。

 if (mCropBoundsChangeListener != null) {
            mCropBoundsChangeListener.onCropBoundsChangedRotate(mTargetAspectRatio);
        }

当我们确定了裁剪框的位置之后,通知OverlayView进行重绘(这又是另一细节问题了)。

然后,我们摆放图片的位置。

    private void setupInitialImagePosition(float drawableWidth, float drawableHeight) {
        float cropRectWidth = mCropRect.width();
        float cropRectHeight = mCropRect.height();

        float widthScale = cropRectWidth / drawableWidth;
        float heightScale = cropRectHeight / drawableHeight;

        mMinScale = Math.max(widthScale, heightScale);
        mMaxScale = mMinScale * mMaxScaleMultiplier;

        float tw = (cropRectWidth - drawableWidth * mMinScale) / 2.0f + mCropRect.left;
        float th = (cropRectHeight - drawableHeight * mMinScale) / 2.0f + mCropRect.top;

        mCurrentImageMatrix.reset();
        mCurrentImageMatrix.postScale(mMinScale, mMinScale);
        mCurrentImageMatrix.postTranslate(tw, th);

    }

这里传入的是原生图片的宽高。

因为图片的位置和裁剪框的位置在初始时肯定是不吻合的,我们可能需要平移,缩放之后才能让他们吻合(又一个细节)

到这里,图片已经正确摆放,和裁剪提示框吻合。



第三阶段

图片正确的摆放之后,剩下的就回到我们之前的问题了—识别手势,调用方法。

如上所说,识别手势的方法全在GestureCropImageView 这个类中了。

小技巧:自己无需计算细节来识别手势,只需使用已经封装好的类,将事件传递给他即可,他自然会给你提供回调方法的。

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
            cancelAllAnimations();
        }
        if (event.getPointerCount() > 1) {
            mMidPntX = (event.getX(0) + event.getX(1)) / 2;
            mMidPntY = (event.getY(0) + event.getY(1)) / 2;
        }
            mGestureDetector.onTouchEvent(event);//手势识别
        if (mIsScaleEnabled) {
            mScaleDetector.onTouchEvent(event);//缩放识别
        }
        if (mIsRotateEnabled) {
            mRotateDetector.onTouchEvent(event);//旋转识别
        }
        if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {  //处理手指释放
            setImageToWrapCropBounds();
        }
  }

在回调方法中就是各种postXX了,这里有个小细节,因为之前没有做过多点触控,这里正好回答第一个问题,怎样判断最后一根手指释放了。

对于多根手指,最后一根的释放触发ACTION_UP,之前的手指释放事件触发ACTION_POINTER_UP。

所有的平移,缩放,旋转都是交给TransformImageView 这个类来完成的,一起来看看。

以平移为例

   public void postTranslate(float deltaX, float deltaY) {
        if (deltaX != 0 || deltaY != 0) {
            mCurrentImageMatrix.postTranslate(deltaX, deltaY);
            setImageMatrix(mCurrentImageMatrix);
        }
    }

其他的方法也都是通过Matrix实现的,我也是第一次接触,感觉是一个强大的类,暂时只需要知道他怎么用,有需要的可以深入研究。

当你设置了这个矩阵之后,在View的绘制过程中会进行判断,根据该矩阵对canvas作出变换,使你能够画出想要的结果。

好了,流程梳理就到这了,后面的文章会接着分析一个个具体的实现,欢迎大家交流。

时间: 2024-08-10 14:44:25

uCrop源码思路分析--流程梳理的相关文章

ajaxFileupload的源码思路分析

? 1 ajaxfileupload源码: jQuery.extend({ //创建一个临时的iframe; createUploadIframe: function(id, uri) { //create frame var frameId = 'jUploadFrame' + id; var iframeHtml = '<iframe id="' + frameId + '" name="' + frameId + '" style="posit

RxJava &amp;&amp; Agera 从源码简要分析基本调用流程(2)

版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qcloud.com/community 接上篇RxJava && Agera 从源码简要分析基本调用流程(1)我们从"1.订阅过程"."2.变换过程"进行分析,下篇文章我们继续分析"3.线程切换过程" 3.线程切换过程 从上文中我们知道了R

boost.asio源码剖析(三) ---- 流程分析

* 常见流程分析之一(Tcp异步连接) 我们用一个简单的demo分析Tcp异步连接的流程: 1 #include <iostream> 2 #include <boost/asio.hpp> 3 4 // 异步连接回调函数 5 void on_connect(boost::system::error_code ec) 6 { 7 if (ec) // 连接失败, 输出错误码 8 std::cout << "async connect error:"

自定义View系列教程02--onMeasure源码详尽分析

PS:如果觉得文章太长,那就直接看视频吧 大家知道,自定义View有三个重要的步骤:measure,layout,draw.而measure处于该链条的首端,占据着极其重要的地位:然而对于measure的理解却不是那么容易,许多问题都是一知半解,比如:为什么父View影响到了子View的MeasureSpec的生成?为什么我们自定义一个View在布局时将其宽或者高指定为wrap_content但是其实际是match_parent的效果?子View的specMode和specSize的生成依据又是

Java7/8 中 HashMap 和 ConcurrentHashMap源码对比分析

网上关于 HashMap 和 ConcurrentHashMap 的文章确实不少,不过缺斤少两的文章比较多,所以才想自己也写一篇,把细节说清楚说透,尤其像 Java8 中的 ConcurrentHashMap,大部分文章都说不清楚.终归是希望能降低大家学习的成本,不希望大家到处找各种不是很靠谱的文章,看完一篇又一篇,可是还是模模糊糊. 阅读建议:四节基本上可以进行独立阅读,建议初学者可按照 Java7 HashMap -> Java7 ConcurrentHashMap -> Java8 Ha

又是正版!Win下ffmpeg源码调试分析二(Step into ffmpeg from Opencv for bugs in debug mode with MSVC)

最近工作忙一直没时间写,但是看看网络上这方面的资源确实少,很多都是linux的(我更爱unix,哈哈),而且很多是直接引入上一篇文章的编译结果来做的.对于使用opencv但是又老是被ffmpeg库坑害的朋友们,可能又爱又恨,毕竟用它处理和分析视频是第一选择,不仅是因为俩者配合使用方便,而且ffmpeg几乎囊括了我所知道的所有解编码器,但是正是因为这个导致了一些bug很难定位,所以有必要考虑一下如何快速定位你的ffmpeg bug. sorry,废话多了.首先给个思路: 1.使opencv 的hi

分享我写的IOCP:源码+思路

首先说明,下面的代码仅是一个IOCP的demo,很多地方的设计非常差,当然也有一些设计还算可以:).此篇仅供对IOCP有些了解但又不深入的.需要一个稍微完整示例的.对网络编程感兴趣的同学参考.点击这里下载代码 整个程序的流程如下: 流程完全是无阻塞的,主线程里,将收到的消息全都一次性取出后,然后派发.所有欲发送的消息都缓存起来,等到更新的时候一起发送.有些地方代码没有完善,比如断开连接后,socket.内存等资源的关闭回收.要注意MAXRECEIVEDBUFFLENGTH这个宏,它是定义每个so

kafka 0.8.1 新producer 源码简单分析

1 背景 最近由于项目需要,需要使用kafka的producer.但是对于c++,kafka官方并没有很好的支持. 在kafka官网上可以找到0.8.x的客户端.可以使用的客户端有C版本客户端,此客户端虽然目前看来还较为活跃,但是代码问题还是较多的,而且对于c++的支持并不是很好. 还有c++版本,虽然该客户端是按照c++的思路设计,但是最近更新时间为2013年12月19日,已经很久没有更新了. 从官方了解到,kafka作者对于现有的producer和consumer的设计是不太满意的.他们打算

jQuery源码 框架分析

每一个框架都有一个核心,所有的结构都是基于这个核心之上,结构建立好了之后,剩下的就是功能的堆砌. jQuery的核心就是从HTML文档中匹配元素并对其操作. 就跟一座大楼一样,让我们一步一步了解这座大厦的基石和结构. 1.构造函数 2.链式语法 3.选择器 4.扩展性  一.构造函数 我们知道类是面向对象编程的一个重要概念,它是对事物的最高抽象,它是一个模型.通过实例化一个类,我们可以创建一个实例. javascript本身没有类的概念,只有原型prototype,prototype是什么呢?它