之前做了這個東西埃碱,一個內(nèi)部素材加外操作邊框,包含基本的移動酥泞、縮放砚殿、旋轉(zhuǎn),拉伸芝囤,快速定位似炎,十字對齊等操作。常見使用場景如添加馬賽克悯姊,添加畫中畫等羡藐。感覺比較有意思而且中間也遇到了一些問題就記錄一下
先上圖:
如圖,這次就先講一下平移悯许、旋轉(zhuǎn)仆嗦、縮放
如果只是view做平移,有很多種實現(xiàn)方式比如通過layout先壕、動畫等欧啤。
基于我們的使用場景:知道一個圖片的位置信息和旋轉(zhuǎn)角度(中心旋轉(zhuǎn)),就可以將它畫出來启上。所以選擇了維護一個rect以及繞中心旋轉(zhuǎn)的角度rotation邢隧,這樣一些第三方如需要c層操作的地方,直接把這些信息給c層也可以馬上明確冈在。
一倒慧、如何畫
可以看出這個控件其實是一個圖片+外面一個框,不過要考慮到旋轉(zhuǎn)的情況包券,這里我們就借助matrix來做中心旋轉(zhuǎn)纫谅。其實相對的就是先把canvas繞圖片的中心做一次旋轉(zhuǎn)
@Override protected void dispatchDraw(Canvas canvas) {
canvas.save();
canvas.concat(mRotateMatrix);
mainDrawable.setBounds((int) mRect.left, (int) mRect.top, (int) mRect.right,
(int) mRect.bottom);
mainDrawable.draw(canvas);
canvas.drawRect(mRect, mPaint);
drawAnchors(canvas, mRect);
canvas.restore();
super.dispatchDraw(canvas);
}
虛線就用paint設(shè)置setPathEffect就可以了,虛線框角上的幾個角標也是借助rect的位置信息來畫出來
比如畫右上角的旋轉(zhuǎn)按鈕:
if (rotateDrawable != null) {
//右上角
rotateDrawable.setBounds(right - drawableWidth, top - drawableHeight, right + drawableWidth,
top + drawableHeight);
rotateDrawable.draw(canvas);
}
重點就是去計算上面代碼的mRotateMatrix
private void invalidateMatrix() {
mRotateMatrix.reset();
mRotateMatrix.postTranslate(-mRect.centerX(), -mRect.centerY());
mRotateMatrix.postRotate(mRotation);
mRotateMatrix.postTranslate(mRect.centerX(), mRect.centerY());
}
這里簡單提一下matrix溅固。學(xué)過線性代數(shù)的都知道矩陣吧付秕,matrix其實就是個3x3的矩陣,里面的元素控制著旋轉(zhuǎn)侍郭、縮放询吴、平移、錯切亮元。直接new出來的矩陣是一個單位矩陣猛计,描述的就是原來的圖形信息,沒有做變換爆捞。
然后就是矩陣計算不滿足交換律奉瘤,換句話說矩陣的前乘和后乘結(jié)果不一樣,反應(yīng)在matrix里面就是pre和post接口效果不一樣煮甥。
簡單點理解就是pre是放在操作隊列頭,post放在隊尾,還有個set會清空整個隊列再把它放進去盗温。如果覺得擔心記混藕赞,推薦就用post接口,符合先進先出的原則卖局,先post的先執(zhí)行找默。
二、如何判斷什么時候該縮放吼驶、旋轉(zhuǎn)或者平移
這里肯定是事件處理相關(guān)的了。處于方便店煞,我們在ontouchEvent的時候把操作委托給GestureDetector蟹演,在onDown回調(diào)時判斷點到了哪里,比如旋轉(zhuǎn)顷蟀、縮放酒请、平移。然后在onScroll回調(diào)的時候通過每次的增量dx,dy計算縮放的比例鸣个、旋轉(zhuǎn)角度以及移動距離羞反。
在手指頭按下時,由于我們知道圖片rec的位置信息和角標位置信息囤萤,所以通過x和y可以判斷是否點到了角標昼窗。
但是有個問題是,畫的矩形是在旋轉(zhuǎn)過的畫布上面涛舍,我們手指頭的xy是屏幕上的位置澄惊,這里對應(yīng)不起來,會出現(xiàn)旋轉(zhuǎn)后就點不到角標了
為了解決這個問題富雅,手指頭按下的point需要利用matix做一次映射掸驱。在這里的場景其實就相當于把旋轉(zhuǎn)的信息考慮進去,其實也完全可以自己用三角函數(shù)算没佑,但是matrix已經(jīng)提供這種接口了毕贼,而且是調(diào)用的c層計算,效率應(yīng)該更高些蛤奢。
final Matrix rotateMatrix = new Matrix();
//反向旋轉(zhuǎn)回去 抵消canvas的旋轉(zhuǎn)
rotateMatrix.postTranslate(-mRect.centerX(), -mRect.centerY());
rotateMatrix.postRotate(-mRotation);
rotateMatrix.postTranslate(mRect.centerX(), mRect.centerY());
rotateMatrix.mapPoints(point);
eventX = point[0];
eventY = point[1];
RectF rectF = mRect;
//hit rotate 右上
if (Math.abs(rectF.right - eventX) < drawableWidth * 2
&& Math.abs(rectF.top - eventY) < drawableHeight * 2) {
return HitModes.ROTATE;
}
需要注意的是這里matix的旋轉(zhuǎn)角度和canvas的是相反的鬼癣,其實就相當于轉(zhuǎn)了n角度,然后又轉(zhuǎn)回去n角度啤贩,相當于沒有轉(zhuǎn)扣溺。然后就可以按照沒有旋轉(zhuǎn)的情況判斷有沒有點到角標。
三瓜晤、如何進行旋轉(zhuǎn)锥余、縮放、平移
第二步已經(jīng)判斷到用戶想要進行什么操作了痢掠,接下來就是執(zhí)行對應(yīng)的操作了驱犹。
平移
先說簡單的平移吧嘲恍,上面說了,我們的場景是一個矩形位置信息+繞中心旋轉(zhuǎn)角度雄驹。所以平移其實就是改矩形的位置罷了,直接調(diào)rec的offset佃牛,然后一定要記得在重新繪制前更新canvas的旋轉(zhuǎn)矩陣
private void onMove(float dx, float dy) {
mRect.offset(-dx, -dy);
invalidateMatrix();
invalidate();
}
縮放
首先我們要明確一個東西,就是縮放時旋轉(zhuǎn)中心一定是不變的医舆。所以可以算出中心和右下角的距離以及scroll后的中心和右下角的距離算出兩個距離的變化當成x的變化俘侠。我們這里是等比例縮放,根據(jù)比例算出y的變化蔬将。
這里做了一個縮放最小的限制爷速,縮放到1.5個角度寬高后就不嫩再縮小了,這樣可以保證角標不會擠在一起霞怀。
private void onScale(float dx, float dy) {
// TODO: 2019/4/8 這里的dx,dy計算需要改進
float[] pt1 = new float[] { mRect.centerX(), mRect.centerY() };
float[] pt2 = new float[] { mRect.right, mRect.bottom };
float[] pt3 = new float[] { mRect.right + dx, mRect.bottom + dy };
float distance1 = getPointDistance(pt1, pt2);
float distance2 = getPointDistance(pt1, pt3);
float distance = distance1 - distance2;
if (!checkCanScale(distance)) {
return;
}
mRect.inset(-distance, -distance / mRatio);
invalidateMatrix();
invalidate();
}
旋轉(zhuǎn)
至于旋轉(zhuǎn)其實也簡單惫东,因為旋轉(zhuǎn)時中心也是不變的,類似縮放的操作毙石。根據(jù)右上角的旋轉(zhuǎn)角標和中心的角度廉沮,以及scroll后右上角和中心的角度,這兩個的角度差就是旋轉(zhuǎn)角度徐矩。
已知兩個點的位置滞时,通過math的atan2函數(shù)可以算出角度
private void onRotate(float triggerX, float triggerY) {
// TODO: 2019/4/8 這里的dx,dy計算需要改進
float[] pt1 = new float[] { mRect.centerX(), mRect.centerY() };
float[] pt2 = new float[] { mRect.right, mRect.top };
float[] pt3 = new float[] { triggerX, triggerY };
double angel1 = PointUtil.calculateAngleBetweenPoints(pt2, pt1);
double angel2 = PointUtil.calculateAngleBetweenPoints(pt3, pt1);
mRotation = (float) (angel1 - angel2);
invalidateMatrix();
invalidate();
}
到這里基本的平移旋轉(zhuǎn)縮放都介紹完了,詳細的一些位置計算可以參考demo
https://github.com/dynamicBai/ScaleRotateView 給個star鼓勵下唄
后面會逐步介紹:拉伸四邊的操作(旋轉(zhuǎn)中心會變化)滤灯、圖片在屏幕上的快速定位和微調(diào)漂洋、移動時十字輔助線對齊等效果。