碰一碰跨設(shè)備協(xié)作開發(fā)實(shí)戰(zhàn)》02:觸點(diǎn)坐標(biāo)怎么變成應(yīng)用里的精準(zhǔn)落點(diǎn)【鴻蒙心跡】)
上一篇我們解決了能傳這一篇要解決傳哪。同樣一張圖片碰左邊進(jìn)素材庫碰中間進(jìn)畫布碰右邊進(jìn)屬性區(qū)——坐標(biāo)到底是怎么變成業(yè)務(wù)落點(diǎn)的先講個我調(diào)試時遇到的事。我把CrossDrop工作臺分成了三個區(qū)域左邊素材庫中間編輯畫布右邊屬性面板。代碼寫好以后拿手機(jī)一碰——嘿不管碰哪圖片全進(jìn)了素材庫。我當(dāng)時就懵了。系統(tǒng)明明給了觸點(diǎn)坐標(biāo)為什么區(qū)域判斷全錯后來打日志才發(fā)現(xiàn)系統(tǒng)給的坐標(biāo)是屏幕坐標(biāo)我拿它直接和組件坐標(biāo)比差了整整一個標(biāo)題欄的高度。就這一個坑調(diào)了一下午。一、先搞清楚四種坐標(biāo)根本不是一回事我們?nèi)粘Uf坐標(biāo)好像就是一個(x,y)。但在窗口系統(tǒng)里至少有四種坐標(biāo)每兩種之間都差著偏移量第一種——屏幕坐標(biāo)。整個顯示器左上角是(0,0)你碰屏幕哪一點(diǎn)系統(tǒng)返回的就是這個坐標(biāo)。這個坐標(biāo)是全局的不管你開了幾個窗口它都是相對于整個屏幕算的。第二種——窗口坐標(biāo)。相對于當(dāng)前窗口的左上角。窗口標(biāo)題欄、邊框這些算進(jìn)去沒有不同系統(tǒng)不一樣。窗口在屏幕上移了這個坐標(biāo)會不會變不會它是窗口內(nèi)部的。第三種——頁面坐標(biāo)。相對于應(yīng)用內(nèi)容區(qū)域。狀態(tài)欄、標(biāo)題欄、導(dǎo)航欄這些都不算從真正的內(nèi)容區(qū)域開始算。第四種——組件局部坐標(biāo)。相對于某個具體組件的左上角。素材庫有自己的坐標(biāo)畫布有自己的坐標(biāo)。你看從屏幕上哪一點(diǎn)到哪個組件的哪一塊中間要換算四層。少算任何一層結(jié)果都是錯的。二、一步一步拆觸點(diǎn)坐標(biāo)怎么變成業(yè)務(wù)落點(diǎn)我們拿一個具體例子走一遍。假設(shè)用戶碰屏幕位置是(x300, y200)。第一步屏幕坐標(biāo) → 窗口坐標(biāo)先知道這個觸點(diǎn)落在哪個窗口里。系統(tǒng)會告訴你這個點(diǎn)屬于CrossDrop主窗口。窗口在屏幕上的位置是(offsetX100, offsetY50)。那窗口內(nèi)部坐標(biāo)就是窗口內(nèi)x 300 - 100 200窗口內(nèi)y 200 - 50 150這一步你得問窗口要它的位置。窗口移了位置變了換算公式就要跟著變。第二步窗口坐標(biāo) → 頁面坐標(biāo)窗口里不是全是內(nèi)容。上面有標(biāo)題欄左邊有側(cè)邊欄。標(biāo)題欄高度是40px。那內(nèi)容區(qū)域的起點(diǎn)就是y40。頁面坐標(biāo)頁面x 200假設(shè)左邊沒有邊距頁面y 150 - 40 110這一步最容易漏。我第一次調(diào)的時候就忘了減標(biāo)題欄高度結(jié)果所有區(qū)域判斷都往下偏了一截。第三步頁面坐標(biāo) → 組件區(qū)域判斷現(xiàn)在知道了內(nèi)容區(qū)域里的位置接下來判斷落在哪個業(yè)務(wù)區(qū)域。CrossDrop工作臺布局大概是這樣左邊素材庫x從0到250中間畫布x從250到750右邊屬性區(qū)x從750到1000頁面x200小于250那就是落在素材庫區(qū)域。碰中間畫布位置x大概是500落在250~750之間那就是插入畫布。碰右邊屬性區(qū)x大概是800大于750那就是進(jìn)入待處理資源區(qū)。這段代碼解決什么問題坐標(biāo)轉(zhuǎn)換工具。文件coordinate/CoordinateConverter.ets用途系統(tǒng)坐標(biāo)轉(zhuǎn)應(yīng)用業(yè)務(wù)坐標(biāo)接入位置觸點(diǎn)事件回調(diào)后classCoordinateConverter{// 窗口在屏幕上的偏移privatewindowOffsetX:number0;privatewindowOffsetY:number0;// 標(biāo)題欄高度privatetitleBarHeight:number40;// 更新窗口位置updateWindowPosition(offsetX:number,offsetY:number){this.windowOffsetXoffsetX;this.windowOffsetYoffsetY;}// 屏幕坐標(biāo) → 頁面內(nèi)容坐標(biāo)toContentCoordinate(screenX:number,screenY:number):{x:number;y:number}{return{x:screenX-this.windowOffsetX,y:screenY-this.windowOffsetY-this.titleBarHeight};}}第四步組件區(qū)域匹配光知道坐標(biāo)落在哪個大塊還不夠還要知道具體是哪個組件。這時候有兩種做法做法一自己算邊界。每個區(qū)域記錄自己的位置和尺寸判斷坐標(biāo)在不在這個矩形里。做法二用系統(tǒng)的HitTest。讓ArkUI自己做命中測試直接返回落在哪個組件上。自己算的好處是可控壞處是布局變了邊界也要跟著改。HitTest的好處是自動跟隨布局壞處是有時候你想要的業(yè)務(wù)區(qū)域和組件邊界不完全一致。這段代碼解決什么問題區(qū)域匹配判斷。文件router/RegionMatcher.ets用途根據(jù)坐標(biāo)判斷業(yè)務(wù)落點(diǎn)接入位置坐標(biāo)轉(zhuǎn)換完成后interfaceRegion{id:string;name:string;x:number;y:number;width:number;height:number;handler:string;}classRegionMatcher{privateregions:Region[][];addRegion(region:Region){this.regions.push(region);}match(x:number,y:number):Region|null{for(letregionofthis.regions){if(xregion.xxregion.xregion.widthyregion.yyregion.yregion.height){returnregion;}}returnnull;}}// 使用constmatchernewRegionMatcher();matcher.addRegion({id:material,name:素材庫,x:0,y:0,width:250,height:800,handler:add_material});matcher.addRegion({id:canvas,name:編輯畫布,x:250,y:0,width:500,height:800,handler:insert_canvas});matcher.addRegion({id:property,name:屬性區(qū),x:750,y:0,width:250,height:800,handler:add_pending});三、坐標(biāo)這塊最容易踩的坑坐標(biāo)轉(zhuǎn)換看起來簡單實(shí)際全是細(xì)節(jié)。我踩過的坑列一下第一個坑狀態(tài)欄高度沒算。系統(tǒng)返回的坐標(biāo)到底算不算狀態(tài)欄不同設(shè)備不一樣。手機(jī)和PC不一樣橫屏和豎屏也不一樣。這個要實(shí)測別想當(dāng)然。第二個坑窗口標(biāo)題欄偏移。PC上窗口有標(biāo)題欄你是從窗口邊框開始算還是從客戶區(qū)開始算這個差幾個像素判斷就錯了。第三個坑縮放比例。有些設(shè)備有屏幕縮放比如125%縮放。你拿到的坐標(biāo)是物理像素還是邏輯像素這個一定要搞清楚。第四個坑窗口移動了。用戶把窗口從左邊拖到右邊你還在用舊的窗口偏移量算結(jié)果全錯。所以窗口位置變化的時候要監(jiān)聽并更新。第五個坑橫豎屏切換。手機(jī)豎屏和橫屏窗口尺寸完全變了。區(qū)域邊界也要跟著重算。第六個坑自由窗口尺寸變了。PC上用戶可以拖窗口邊緣調(diào)整大小窗口尺寸變了你的區(qū)域邊界也要重新布局。第二篇總結(jié)坐標(biāo)轉(zhuǎn)換這件事核心不是寫幾行換算代碼而是搞清楚四種坐標(biāo)之間的層級關(guān)系屏幕坐標(biāo) → 窗口坐標(biāo) → 頁面坐標(biāo) → 組件局部坐標(biāo)。每一層都有自己的偏移量少算一層結(jié)果就錯。工程上最容易翻車的不是算法是細(xì)節(jié)狀態(tài)欄高度、標(biāo)題欄偏移、縮放比例、窗口移動、橫豎屏切換——這些環(huán)境變量任何一個變了你的坐標(biāo)換算就要跟著變。下一篇我們把難度再往上提一級PC上同時開了三個窗口用戶碰屏幕的時候怎么知道他碰的是哪個窗口