旋轉(zhuǎn)控件(一):矩形的平移旋轉(zhuǎn)縮放

之前做了這個東西埃碱,一個內(nèi)部素材加外操作邊框,包含基本的移動酥泞、縮放砚殿、旋轉(zhuǎn),拉伸芝囤,快速定位似炎,十字對齊等操作。常見使用場景如添加馬賽克悯姊,添加畫中畫等羡藐。感覺比較有意思而且中間也遇到了一些問題就記錄一下

先上圖:


scaleRotae.gif

如圖,這次就先講一下平移悯许、旋轉(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)漂洋、移動時十字輔助線對齊等效果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末力喷,一起剝皮案震驚了整個濱河市刽漂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弟孟,老刑警劉巖贝咙,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拂募,居然都是意外死亡庭猩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門陈症,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔼水,“玉大人,你說我怎么就攤上這事录肯∨恳福” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長优炬。 經(jīng)常有香客問我颁井,道長,這世上最難降的妖魔是什么蠢护? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任雅宾,我火速辦了婚禮,結(jié)果婚禮上葵硕,老公的妹妹穿的比我還像新娘眉抬。我一直安慰自己,他們只是感情好懈凹,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布蜀变。 她就那樣靜靜地躺著,像睡著了一般蘸劈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尊沸,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天威沫,我揣著相機與錄音,去河邊找鬼洼专。 笑死棒掠,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的屁商。 我是一名探鬼主播烟很,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜡镶!你這毒婦竟也來了雾袱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤官还,失蹤者是張志新(化名)和其女友劉穎芹橡,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體望伦,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡林说,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了屯伞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腿箩。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖劣摇,靈堂內(nèi)的尸體忽然破棺而出珠移,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布剑梳,位于F島的核電站唆貌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏垢乙。R本人自食惡果不足惜锨咙,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望追逮。 院中可真熱鬧酪刀,春花似錦、人聲如沸钮孵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巴席。三九已至历涝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間漾唉,已是汗流浹背荧库。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赵刑,地道東北人分衫。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像般此,于是被迫代替她去往敵國和親蚪战。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容