Android绘图软件开发(2)-图形的编辑操作实现

引言

图形并不是画了就画了,用户完全可以在接下来对任一图形进行后期编辑,而编辑操作包括:选中、平移、缩放、旋转、拷贝、删除。本章就想讲讲怎么去实现这些操作。

开发思路

还是用分类划分的思路考虑:纵观这六个编辑操作,“选中”是最特殊的,它是其它操作能够进行的前置条件(因为必须要选中了某个图形后才能进行呀,不然系统怎么知道您想要操作哪个图形)。其次,“平移”、“缩放”、“旋转”这3个操作的共同之处是都要对画布上的图形进行触摸,进而产生变换。最后,“拷贝”和“删除”这2个操作非常简单,只要选中某个图形,再点击对应按钮即可瞬间执行。

选中操作

用户手指落下后会触发MotionEvent.ACTION_DOWN事件,该事件对象可以捕获落点坐标,此时只要遍历图形链表pelList,逐个计算每个图形与落点分别在“水平”和“垂直”方向的距离之和,并找出“水平”或“垂直”距离之和最短的那个图形,该图形即被选中,如下图所示,黑点表示落点,实线代表图形轨迹,最终圆形被选中。



计算距离时,需要知道图形的位置信息,该信息由pel对象的region提供,通过调用region.getBounds()即可获得图形的包围盒,上图中的虚线即表示包围盒。代码大体如下所示。

ListIterator<Pel> pelIterator = pelList.listIterator(); // 获取pelList对应的迭代器头结点
while (pelIterator.hasNext())
{
    Pel pel = pelIterator.next();
    Rect rect=(pel.region).getBounds(); //获取图形包围盒
    //计算落点距离图形左、右边界的距离和
    float leftDis=Math.abs(rect.left-downPoint.x);
    float rightDis=Math.abs(rect.right-downPoint.x);
    float horizontalDis=leftDis+rightDis;
    //计算落点距离图形上、下边界的距离和
    float topDis=Math.abs(rect.top-downPoint.y);
    float bottomDis=Math.abs(rect.bottom-downPoint.y);
    float verticalDis=topDis+bottomDis;
    //判断落点与该图形的距离和是否更小,若是进一步判断落点是否在图形内部或附近,若太远则仍然不算
    if((horizontalDis < minHorizontalDis || verticalDis < minVerticalDis) && (horizontalDis < rect.width() + 5 && verticalDis < rect.height() + 5))
    {
        selectedPel=pel; //选中该图形
        minHorizontalDis=horizontalDis;
        minVerticalDis=verticalDis;
    }
}

变换矩阵

在讲图形的变换操作(即平移、缩放、旋转)之前,首先要引入“变换矩阵”这个概念,若要详细说明那太多了,所以简单起见大家只用知道:这个matrix存储了某一图形距离它初始形态的水平偏移量、垂直偏移量、缩放系数、旋转角度、倾斜角度等参数信息,当要发生变换时,只用把图形初始形态的坐标和这个变换矩阵做前乘、后乘、相加等运算,即可得到新图形的形态。
所以,表面上看似是在对图形进行变换,但实质是对pel.path在中封装的Matrix matrix进行变换,我们的任务是需要获取这个matrix,然后调用Matrix类提供的各种方法去变换该矩阵即可。下面是获取matrix的代码。

public Matrix getMatrix(Pel pel)
{
    Matrix matrix = new Matrix();
    PathMeasure pathMeasure = new PathMeasure(pel.path, true); // 必须先将Path封装成PathMeasure
    pathMeasure.getMatrix(pathMeasure.getLength(),matrix, PathMeasure.POSITION_MATRIX_FLAG & PathMeasure.TANGENT_MATRIX_FLAG);
    return matrix;
}

平移操作

平移很简单,由于选中的时候记录了落点坐标,而移动的时候又会实时捕获手指当前所在的坐标,通过这两个坐标很容易计算出偏移距离dx和dy,以它们作为变换参数,更新选中图形原始的变换矩阵,最后刷新,实现平移,如下图及下面代码所示:



if (mode == DRAG)// 平移操作
{
    dx = curPoint.x - downPoint.x;//计算距离
    dy = curPoint.y - downPoint.y;
    // 对选中图元施行平移变换
    transMatrix.set(savedMatrix);
    transMatrix.postTranslate(dx, dy); // 作用于平移变换因子
    (selectedPel.path).set(savedPel.path);
    (selectedPel.path).transform(transMatrix); // 作用于图元
    (selectedPel.region).setPath(selectedPel.path, clipRegion); // 更新平移后路径所在区域
}

缩放操作

缩放就要稍微复杂点了,但只要稍微想一下咱们平时用智能手机缩放图片的过程,也是轻而易举的。其实,缩放过程可以简单抽象为下图所示:



其中B1和B2为两个手指的落点,通过他俩可以算得缩放中心,B1’和B2’是两个手指移动点。任务很明确啦,我们只需算出缩放中心+缩放比例,即可实现缩放。但问题就是这个缩放比例怎么计算才更合理呢?B1B2是图形的初始距离,B1’B2’是图形缩放后的距离,“两者的比”其实就可以大致表征这个缩放比例,如下所示:



缩放的代码如下:
if (mode == ZOOM)// 缩放操作
{
    float scale = newDist / oriDist;
    transMatrix.set(savedMatrix);
    transMatrix.postScale(scale, scale, centerPoint.x,centerPoint.y); // 作用于缩放变换因子
    (selectedPel.path).set(savedPel.path);
    (selectedPel.path).transform(transMatrix); // 作用于图元
    (selectedPel.region).setPath(selectedPel.path, clipRegion); // 更新平移后路径所在区域
}

旋转操作

旋转就更是复杂了,但万变不离其宗,我们还是用老方法,把复杂问题抽象成一个简单的图分析,如下图:



C1和C2是落点,C1’和C2’是移动点,O是旋转中心。归根究底,我们不外乎就是想要C1OC1’这个角度,有了它就能控制图形的旋转。那怎么算这个角度呢?以前上中学的时候有个公式不知道大家还记得不,我们用白话形容一下就是:一个圆,只要知道了一段弧和半径的长度,就能求得圆心角的弧度值。有了弧度值再做个转换,自然就能求到度数值了。就像如下所示一样:



但这里需要注明一点,就是这段弧长是一个近似的长度,是用弦C1C1’去近似模拟的,因为真正的弧长基本不可能计算出来,也没有必要算出来,就算算出来了也会很慢,导致旋转过程的不流畅。下面是代码:
if (mode == ROTATE)// 旋转操作
{
    transMatrix.set(savedMatrix);
    transMatrix.setRotate(getDegree(),centerPoint.x,centerPoint.y);
    (selectedPel.path).set(savedPel.path);
    (selectedPel.path).transform(transMatrix); // 作用于图元
    (selectedPel.region).setPath(selectedPel.path, clipRegion); // 更新平移后路径所在区域
}
// 计算旋转角度
private float getDegree()
{
    // 获得两次down下时的距离
    float x=curPoint.x-downPoint.x;
    float y=curPoint.y-downPoint.y;
    float arc=FloatMath.sqrt(x * x + y * y);//弧长
    float radius=getRadius();//半径
    return (arc/radius)*(180/3.14f);
}
// 计算两个触摸点之间的距离
private float distance()
{
    float x = curPoint.x - secPoint.x;
    float y = curPoint.y - secPoint.y;
    return FloatMath.sqrt(x * x + y * y) / 2;
}

拷贝操作

选中某个图形后,点击拷贝按钮,就复制出了一个一模一样的图形在画布上。这个功能的实现非常简单,只需要根据当前选中的图形selectedPel的信息,去构造一个完全相同的另一图形对象,并将其添加至pelList尾部,刷新画布即可,这里就不贴代码了。

删除操作

选中某个图形后,点击删除按钮,画布上的这个图形就不复存在了。这个功能也非常简单,唯一需要注意的是不能删除selectedPel就了事了,而要在这之前,先遍历pelList,根据这个selectedPel引用找到它在pelList中的位置,并删除,最后刷新画布。同样比较简单,这里不贴代码了。

结语

本章就快要到一段落了,不知道复杂的编辑操作有没有变得简单一些呢?但这里似乎还是有一个交互上的问题没有解决:
选中图形以后可以平移、缩放和旋转,我们分别用了DRAG、ZOOM、ROTATE来标志状态,但其中平移操作是单指操作,而缩放和旋转都是多指操作,平移倒是很好和其它二位区分,但缩放和旋转就很难区分了,都是先落下两只手指再进行操作,且他俩的操作非常相似,您觉得这里应该如何去设置一个“临界条件”来作为两操作的分水岭,从而自然而人性化地完成交互呢?我这里也有一个自己写的方案,效果还不错,也很符合人的思维。如果实在没有头绪的话,可以去我的GitHub下载源码参考。
今天就先到这里啦,下一章讲解一下绘图软件(不,宏观说应该是数字编辑软件)中“撤销(undo)”和“重做(redo)”的一种有效的实现方案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容