iOS視覺化動態繪製連通圖(Swift版)
上篇部落格《》可視化了一下一些排序的過程,本篇部落格就來聊聊圖的東西。在之前的部落格中詳細的講過圖的相關內容,比如《》。當然之前寫的程式是比較抽象的。上篇部落格我們以視覺化的方式看了一下各種排序的過程,今天部落格中我們就來視覺化的看一下圖的相關部分,今天我們要畫的圖是無向圖,並且每個點到其他點都有直接的連線。今天我們就基於此圖來做一些事情。當然本篇部落格在畫圖時我們使用的是Bezier曲線來畫的,因為之前也聊過關於Bezier的相關東西,所以今天就不對Bezier做過多贅述了。
今天的部落格我們有易到難大致分為三個部分。第一部分我們會畫出相應的圖,並該圖是可以對每個點進行拖動的,在拖動的過程中,我們對其進行重繪。第二部分會取消拖動,使用UIView
上述這三部分的內容下方會詳細的進行介紹,並會附有相應的執行結果圖。接下來就進入我們的主題部分。
一、圖的繪製
在本篇部落格的第一部分我們要按照要求先把圖給繪製出來,我們會隨機的生成幾個座標點,然後在這些座標點上新增上View,然後再將這些座標點使用Bezier進行連線。當然,在連線時我們使用的是鄰接矩陣來記錄的每兩點之間的關係。在繪製的過程中,我們會隨機的為每個點每條邊分配顏色。
當相應的圖繪製好後,我們需要為每個點新增上Move事件,在對每個點進行拖動時,我們會及時的重新繪製整個圖的關係。下方就是我們本部分要實現內容的執行效果,如下所示:
如果理解了資料結構中圖的構建,實現上述效果,並不困難。解析來我們就來看一下實現上述效果的核心程式碼。
1、圖的節點View的封裝
首先我們來封裝上述圖的節點View,當然此節點View的封裝比較簡單。核心就在於給每個節點View新增一個TouchesMoved事件,然後在TouchesMoved事件執行時,將觸控的移動點設定成當前View的Center即可。這樣我們就可以拖動每個節點View了。在拖動節點View時,我們還需要將拖動的事件回撥到節點View的父檢視上
下方這段程式碼的上一部分就是我們定義的一個閉包型別,用來將節點View的觸控事件回撥給父檢視。該閉包型別需要傳一個引數,該引數就是當前View的Tag, 這樣父檢視就知道當前使用者拖動的是哪個節點了。
而randomColor()函式則是用來負責隨機生成顏色的,上面每次顏色的變化都是使用的下方這個函式所隨機生成的UIColor物件。
下方這段就是節點View的TouchesMoved事件,在該事件中我們獲取到當前使用者觸控移動的座標點,然後將該點賦值給當前節點View的Center,然後呼叫更新父檢視的閉包回撥物件即可。如下所示:
2、圖View的封裝
接下來我們要實現畫圖的View了,也就是上述節點View的父檢視了。父檢視主要負責的工作內容就是建立上述的節點View,然後使用Bezier將每個節點進行連線即可。當然,在使用者拖動相應的View的時候,需要對當前圖進行重繪。
下方這個方法就是往父檢視上新增相應的節點檢視,在節點檢視初始化後,要設定一個閉包回撥,該回呼叫來移動後圖的重繪。在該閉包回撥中,我們會呼叫drawLine()方法。當然在建立節點View時,我們也建立了相應的BezierPath的物件。每個節點對應一個BezierPath物件,用來繪製該節點所連節點的線。具體程式碼如下所示:
我們整個圖的關係是儲存在鄰接矩陣中的,所以我們要對鄰接矩陣進行建立,在重繪時要對該鄰接矩陣進行初始化。下方就是該鄰接矩陣建立和初始化的程式碼,關於鄰接矩陣的內容在此就不做過多贅述了,具體內容請參考之前的部落格。
節點View和鄰接矩陣的準備工作完成後,接下來就是畫線的工作了。下方就是畫線的核心程式碼,在畫線之前我們要先將相應的BezierPath物件上的點移除掉,然後再新增上新的點,最後就是進行重繪了。在往BezierPath物件上新增點時,我們要將節點的關係在鄰接矩陣中進行記錄。如果兩個點之間已經畫完線了,那麼鄰接矩陣上的內容我們設定為true,未畫線的節點之間則是false。具體程式碼如下所示。
在上述方法呼叫setNeedsDisplay()方法後,就會執行View的draw()方法,我們就在此方法中進行線條的繪製。當然下方的程式碼比較簡單,在此就不做過多贅述了。
上述這些程式碼就是本部分所展示的效果圖核心程式碼,完整示例請移步本篇部落格末尾的github分享連結。
二、圖的自動變換
上一部分是我們手動的拖動讓建立的圖進行變換的,接下來我們對上述程式碼進行改造一下,使其自動的進行變換。在點自動移動時,如果碰到螢幕的邊界,我們讓其反彈接著進行移動。下方就是我們本部分要實現的效果。
當然有了第一部分作為基礎,我們實現本部分的效果並不複雜。我們需要做的事情是隨機生成每個節點所移動的方向。然後判斷移動時是不是超出螢幕範圍,如果超出螢幕範圍我們就要對運動方向進行修正,讓其往反方向進行移動。本部分我們只需要修改節點View,而節點View的父檢視不做修改。
下方這段程式碼片就是為了讓其自動變換所實現的方法。下方的這兩個方法會替換掉第一部分的TouchesMoved方法。下方的randomIncrement()方法用來生成當前View的x座標和y座標的偏移量。x的偏移量為1則表示往右運動,-1表示往左運動。y的偏移量為1則往下運動,-1則是往上執行。
下方的changePoint()就是根據x和y的偏移量不斷修改當前節點View的座標的方法。為了簡單,此處使用了UIView自帶的Animate來實現的。在修改x和y座標的值時要判斷是否超出螢幕邊距,如果超出螢幕邊界就往反方向移動。為了讓點一直運動下去,我們需要不斷的呼叫changePoint()方法,如下所示。當然每呼叫一次changePoint()方法,我們就需要呼叫一下重繪的回撥。具體程式碼如下所示。
三、特定區域內畫圖
接下來我們要做的就是繼續在上述內容中做一些東西。在節點自動運動的過程中,我們不把所有的點都連線起來,本部分要做的事情是當點運動時,我們以改點為中心劃定個區域,如果有其他點在該區域內,我們就將該區域內的點進行連線。如果點在運動的過程中超出了劃定的範圍,那麼我們就去除之前畫的線。效果如下所示:
本部分主要修改的內容是節點View的父檢視,核心就是要計算當前點與周圍點的距離,如果該距離小於我們規定的距離的話,那麼我們就畫線,否則就不畫線。下方程式碼片段就是本部分的核心程式碼。主要就是往貝塞爾上新增點時進行距離的判斷。下方的countDistance()函式就是用來計算兩點之間直線距離的函式,在areaPoints()中呼叫了該函式來確定當前區域中的點。核心程式碼如下所示:
四、點選新增節點
本部分也將在上述部分的程式碼上進行更新。該部分要做的事情是點選螢幕,往螢幕上新增新的節點。這一點在上述基礎上實現是比較簡單的。只需給節點的父View新增上新的節點即可。下方就是第四部分要實現的效果,每點選一次螢幕,就會在螢幕點選的地方生成一個節點,該節點就會運動。具體效果如下所示。
要想實現上述效果,下方是我們修改的程式碼片段。就是給父檢視添加了一個TouchesEnded事件,在點選的地方生成一個節點View即可。具體如下所示: