摘要:學習小游戲開發中最常用的碰撞檢測狀態監控刷新保持狀態的處理方法。保存縮略圖的信息是當游戲結束后顯示源縮略圖時,根據中的內容展示圖片。
如果您想要綜合使用javascript中canvas、原生拖拽、本地存儲等多種技術完成一個有趣的項目,那么這篇博文將非常適合您,水平有限,還望感興趣的開發人員給予更多代碼優化建議。1 簡介和源碼
該項目中的拼圖小游戲使用javascript原創,相比于網站上類似的功能,它使用到的技術點更先進豐富,功能更強大,還包含程序開發中更多先進的思想理念,從該項目中您將能學到:
FileReader、Image對象的配合canvas對圖片進行壓縮,切割的技巧。
學習小游戲開發中最常用的碰撞檢測、狀態監控、刷新保持狀態的處理方法。
深入了解拖拽交換元素的細節,學習到動態元素綁定事件、回調函數的處理方式。
項目源碼-github
下面是游戲界面的示例圖:
根據游戲界面圖我們可以將完成這么一個小游戲分為以下幾步來實現:
1.拖拽圖片到指定區域,使用FileReader對象讀取到圖片的base64內容,然后添加到Image對象中
2.當Image對象加載完成后,使用canvas對圖片進行等比縮放,然后取到縮略圖的base64內容,添加到另外一個縮略圖Image對象中,并將該縮略圖base64的內容保存到本地存儲(localStorage)中
3.當縮略圖Image對象加載完成后,再次使用canvas對縮略圖進行切割,該游戲中將縮略圖切割成3*4一共12等份,使用本地存儲保存每份切割縮略圖base64內容,將縮略圖順序打亂,使用img標簽顯示在web頁面上
4.當縮略圖切片都添加到web界面上以后,為每一份縮略圖切片添加注冊拖拽事件,使得縮略圖切片可以相互交換,在這個過程當中,添加對縮略圖切片順序狀態的監控,一旦完成拼圖,就直接展示完整的縮略圖,完成游戲
從以上對小游戲制作過程的分析來看,第4步是程序功能實現的重點和難點,在以上的每個步驟中都有很多小細節需要注意和探討,下面我就詳細分析一下每個步驟的實現細節,說的不好的地方,歡迎大家留言指正。
3 開發細節詳解 3.1 圖片內容讀取和加載在游戲開發第1步中,我們將圖片拖拽到指定區域后,程序是怎樣得到圖片內容信息的呢?fileReader對象又是怎樣將圖片信息轉化為base64字符串內容的?Image對象拿到圖片的base64內容之后,又是怎樣初始化加載的?帶著這些疑問,我們來研究一下實現項目中實現了第一步的關鍵代碼。
var droptarget = document.getElementById("droptarget"), output = document.getElementById("ul1"), thumbImg = document.getElementById("thumbimg"); //此處省略相關代碼........ function handleEvent(event) { var info = "", reader = new FileReader(), files, i, len; EventUtil.preventDefault(event); localStorage.clear(); if (event.type == "drop") { files = event.dataTransfer.files; len = files.length; if (!/image/.test(files[0].type)) { alert("請上傳圖片類型的文件"); } if (len > 1) { alert("上傳圖片數量不能大于1"); } var canvas = document.createElement("canvas"); var context = canvas.getContext("2d"); var img = new Image(), //原圖 thumbimg = new Image(); //等比縮放后的縮略圖 reader.readAsDataURL(files[0]); reader.onload = function (e) { img.src = e.target.result; } //圖片對象加載完畢后,對圖片進行等比縮放處理。縮放后最大寬度為三百像素 img.onload = function () { var targetWidth, targetHeight; targetWidth = this.width > 300 ? 300 : this.width; targetHeight = targetWidth / this.width * this.height; canvas.width = targetWidth; canvas.height = targetHeight; context.clearRect(0, 0, targetWidth, targetHeight); context.drawImage(img, 0, 0, targetWidth, targetHeight); var tmpSrc = canvas.toDataURL("image/jpeg"); //在本地存儲完整的縮略圖源 localStorage.setItem("FullImage", tmpSrc); thumbimg.src = tmpSrc; } //此處省略相關代碼...... EventUtil.addHandler(droptarget, "dragenter", handleEvent); EventUtil.addHandler(droptarget, "dragover", handleEvent); EventUtil.addHandler(droptarget, "drop", handleEvent); }
這段代碼的思路就是首先獲得拖拽區域目標對象droptarget,為droptarget注冊拖拽監聽事件。代碼中用到的EventUtil是我封裝的一個對元素添加事件、事件對象的兼容處理等常用功能的簡單對象,下面是其添加注冊事件的簡單簡單代碼,其中還有很多其他的封裝,讀者可自行查閱,功能比較簡單。
var EventUtil = { addHandler: function(element, type, handler){ if (element.addEventListener){ element.addEventListener(type, handler, false); } else if (element.attachEvent){ element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, //此處省略代...... }
當用戶將圖片文件拖放到區域目標對象droptarget時,droptarget的事件對象通過event.dataTransfer.files獲取到文件信息,對文件進行過濾(限制只能為圖片內容,并且最多只能有一張圖片)。拿到文件內容以后,使用FileReader對象reader讀取文件內容,使用其readAsDataURL方法讀取到圖片的base64內容,賦值給Image對象img的src屬性,就可以等到img對象初始化加載完畢,使canvas對img進行下一步的處理了。這里有一個重點的地方需要說明:一定要等img加載完成后,再使用canvas進行下一步的處理,不然可能會出現圖片損壞的情況。原因是:當img的src屬性讀取圖片文件的base64內容時,可能還沒有將內容加載到內存中時,canvas就開始處理圖片(此時的圖片是不完整的)。所以我們可以看到canvas對圖片的處理是放在img.onload方法中進行的,程序后邊還會有這種情況,之后就不再贅述了。
3.2 圖片等比縮放和本地存儲在第一步中我們完成了對拖拽文件的內容讀取,并將其成功加載到了Image對象img中。接下來我們使用canvas對圖片進行等比縮放,對圖片進行等比縮放,我們采取的策略是限制圖片的最大寬度為300像素,我們再來看一下這部分代碼吧:
img.onload = function () { var targetWidth, targetHeight; targetWidth = this.width > 300 ? 300 : this.width; targetHeight = targetWidth / this.width * this.height; canvas.width = targetWidth; canvas.height = targetHeight; context.clearRect(0, 0, targetWidth, targetHeight); context.drawImage(img, 0, 0, targetWidth, targetHeight); var tmpSrc = canvas.toDataURL("image/jpeg"); //在本地存儲完整的縮略圖源 localStorage.setItem("FullImage", tmpSrc); thumbimg.src = tmpSrc; }
確定了縮放后的寬度targetWidth和高度targetHeight之后,我們使用canvas的drawImage方法對圖像進行壓縮,在這之前我們最好先使用畫布的clearRect對畫布進行一次清理。對圖片等比縮放以后,使用canvas的toDataURL方法,獲取到縮放圖的base64內容,賦給新的縮放圖Image對象thumbimg的src屬性,待縮放圖加載完畢,進行下一步的切割處理。縮放圖的base64內容使用localStorage存儲,鍵名為"FullImage"。瀏覽器的本地存儲localStorage是硬存儲,在瀏覽器刷新之后內容不會丟失,這樣我們就可以在游戲過程中保持數據狀態,這點稍后再詳細講解,我們需要知道的是localStorage是有大小限制的,最大為5M。這也是為什么我們先對圖片進行壓縮,減少存儲數據大小,保存縮放圖base64內容的原因。關于開發過程中存儲哪些內容,下一小節會配有圖例詳細說明。
3.3 縮略圖切割生成縮略圖之后要做的工作就是對縮略圖進行切割了,同樣的也是使用canvas的drawImage方法,而且相應的處理必須放在縮略圖加載完成之后(即thumbimg.onload)進行處理,原因前面我們已經說過。下面我們再來詳細分析一下源代碼吧:
thumbimg.onload = function () { //每一個切片的寬高[切割成3*4格式] var sliceWidth, sliceHeight, sliceBase64, n = 0, outputElement = "", sliceWidth = this.width / 3, sliceHeight = this.height / 4, sliceElements = []; canvas.width = sliceWidth; canvas.height = sliceHeight; for (var j = 0; j < 4; j++) { for (var i = 0; i < 3; i++) { context.clearRect(0, 0, sliceWidth, sliceHeight); context.drawImage(thumbimg, sliceWidth * i, sliceHeight * j, sliceWidth, sliceHeight, 0, 0, sliceWidth, sliceHeight); sliceBase64 = canvas.toDataURL("image/jpeg"); localStorage.setItem("slice" + n, sliceBase64); //為了防止圖片三像素問題發生,請為圖片屬性添加 display:block newElement = ""; //根據隨機數打亂圖片順序 (Math.random() > 0.5) ? sliceElements.push(newElement) : sliceElements.unshift(newElement); n++; } } //拼接元素 for (var k = 0, len = sliceElements.length; k < len; k++) { outputElement += sliceElements[k]; } localStorage.setItem("imageWidth", this.width + 18); localStorage.setItem("imageHeight", this.height + 18); output.style.width = this.width + 18 + "px"; output.style.height = this.height + 18 + "px"; (output.innerHTML = outputElement) && beginGamesInit(); droptarget.remove(); }
上面的代碼對于大家來說不難理解,就是將縮略圖分割成12個切片,這里我給大家解釋一下幾個容易困惑的地方:
1.為什么我們再切割圖片的時候,代碼如下,先從列開始循環?
for (var j = 0; j < 4; j++) { for (var i = 0; i < 3; i++) { //此處省略邏輯代碼 } }
這個問題大家仔細想一想就明白了,我們將圖片進行切割的時候,要記錄下來每一個圖片切片的原有順序。在程序中我們使用 n 來表示圖片切片的原有順序,而且這個n記錄在了每一個圖片切片的元素的name屬性中。在后續的游戲過程中我們可以使用元素的getAttribute("name")方法取出 n 的值,來判斷圖片切片是否都被拖動到了正確的位置,以此來判斷游戲是否結束,現在講起這個問題可能還會有些迷惑,我們后邊還會再詳細探討,我給出一張圖幫助大家理解圖片切片位置序號信息n:
序號n從零開始是為了和javascript中的getElementsByTagName()選擇的子元素坐標保持一致。
2 我們第3步實現的目的不僅是將縮略圖切割成小切片,還要將這些圖片切片打亂順序,代碼程序中這一點是怎樣實現的?
閱讀代碼程序我們知道,我們每生成一個切片,就會構造一個元素節點: newElement = ""; 。我們在是在外部先聲明了一個放新節點的數組sliceElements,我們每生成一個新的元素節點,就會把它放到sliceElements數組中,但是我們向sliceElements頭部還是尾部添加這個新節點則是隨機的,代碼是這樣的:
(Math.random() > 0.5) ? sliceElements.push(newElement) : sliceElements.unshift(newElement);
我們知道Math.random()生成一個[0, 1)之間的數,所以再canvas將縮略圖裁切成切片以后,根據這些切片生成的web節點順序是打亂的。打亂順序以后重新組裝節點:
//拼接元素 for (var k = 0, len = sliceElements.length; k < len; k++) { outputElement += sliceElements[k]; }
然后再將節點添加到web頁面中,也就自然而然出現了圖片切片被打亂的樣子了。
3.我們根據縮略圖切片生成的DOM節點是動態添加的元素,怎樣給這樣動態元素綁定事件呢?我們的項目中為每個縮略圖切片DOM節點綁定的事件是“拖動交換”,和其他節點都有關系,我們要保證所有的節點都加載后再對事件進行綁定,我們又是怎樣做到的呢?
下面的一行代碼,雖然簡單,但是用的非常巧妙:
(output.innerHTML = outputElement) && beginGamesInit();
有開發經驗的同學都知道 && 和 || 是短路運算符,代碼中的含義是:只有當切片元素節點都添加到
WEB頁面之后,才會初始化為這些節點綁定事件。
代碼中多次用到了本地存儲,下面我們來詳細解釋一下本游戲開發過程中都有哪些信息需要存儲,為什么要存儲?下面是我給出的需要存儲的信息圖示例(從瀏覽器控制臺獲取):
瀏覽器本地存儲localStorage使用key:value形式存儲,從圖中我們看到我們本次存儲的內容有:
FullImage:圖片縮略圖base64編碼。
imageWidth:拖拽區域圖片的寬度。
imageHeight:拖拽區域圖片的高度。
slice*:每一個縮略圖切片的base64內容。
nodePos:保存的是當前縮略圖的位置坐標信息。
保存FullImage縮略圖的信息是當游戲結束后顯示源縮略圖時,根據FullImage中的內容展示圖片。而imageWidth,imageHeight,slice*,nodePos是為了防止瀏覽器刷新導致數據丟失所做的存儲,當刷新頁面的時候,瀏覽器會根據本地存儲的數據加載沒有完成的游戲內容。其中nodePos是在為縮略圖切片發生拖動時存入本地存儲的,并且它隨著切片位置的變化而變化,也就是它追蹤著游戲的狀態,我們在接下來的代碼功能展示中會再次說到它。
3.5 拖拽事件注冊和監控接下來我們要做的事才是游戲中最重要的部分,還是先來分析一下代碼,首先是事件注冊前的初始化工作:
//游戲開始初始化 function beginGamesInit() { aLi = output.getElementsByTagName("li"); for (var i = 0; i < aLi.length; i++) { var t = aLi[i].offsetTop; var l = aLi[i].offsetLeft; aLi[i].style.top = t + "px"; aLi[i].style.left = l + "px"; aPos[i] = {left: l, top: t}; aLi[i].index = i; //將位置信息記錄下來 nodePos.push(aLi[i].getAttribute("name")); } for (var i = 0; i < aLi.length; i++) { aLi[i].style.position = "absolute"; aLi[i].style.margin = 0; setDrag(aLi[i]); } }
可以看到這部分初始化綁定事件代碼所做的事情是:記錄每一個圖片切片對象的位置坐標相關信息記錄到對象屬性中,并為每一個對象都注冊拖拽事件,對象的集合由aLi數組統一管理。這里值得一提的是圖片切片的位置信息index記錄的是切片現在所處的位置,而我們前邊所提到的圖片切片name屬性所保存的信息n則是圖片切片原本應該所處的位置,在游戲還沒有結束之前,它們不一定相等。待所有的圖片切片name屬性所保存的值和其屬性index都相等時,游戲才算結束(因為用戶已經正確完成了圖片的拼接),下面的代碼就是用來判斷游戲狀態是否結束的,看起來更直觀一些:
//判斷游戲是否結束 function gameIsEnd() { for (var i = 0, len = aLi.length; i < len; i++) { if (aLi[i].getAttribute("name") != aLi[i].index) { return false; } } //后續處理代碼省略...... }
下面我們還是詳細說一說拖拽交換代碼相關邏輯吧,拖拽交換的代碼如下圖所示:
//拖拽 function setDrag(obj) { obj.onmouseover = function () { obj.style.cursor = "move"; console.log(obj.index); } obj.onmousedown = function (event) { var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; obj.style.zIndex = minZindex++; //當鼠標按下時計算鼠標與拖拽對象的距離 disX = event.clientX + scrollLeft - obj.offsetLeft; disY = event.clientY + scrollTop - obj.offsetTop; document.onmousemove = function (event) { //當鼠標拖動時計算div的位置 var l = event.clientX - disX + scrollLeft; var t = event.clientY - disY + scrollTop; obj.style.left = l + "px"; obj.style.top = t + "px"; for (var i = 0; i < aLi.length; i++) { aLi[i].className = ""; } var oNear = findMin(obj); if (oNear) { oNear.className = "active"; } } document.onmouseup = function () { document.onmousemove = null; //當鼠標彈起時移出移動事件 document.onmouseup = null; //移出up事件,清空內存 //檢測是否普碰上,在交換位置 var oNear = findMin(obj); if (oNear) { oNear.className = ""; oNear.style.zIndex = minZindex++; obj.style.zIndex = minZindex++; startMove(oNear, aPos[obj.index]); startMove(obj, aPos[oNear.index], function () { gameIsEnd(); }); //交換index var t = oNear.index; oNear.index = obj.index; obj.index = t; //交換本次存儲中的位置信息 var tmp = nodePos[oNear.index]; nodePos[oNear.index] = nodePos[obj.index]; nodePos[obj.index] = tmp; localStorage.setItem("nodePos", nodePos); } else { startMove(obj, aPos[obj.index]); } } clearInterval(obj.timer); return false;//低版本出現禁止符號 } }
這段代碼所實現的功能是這樣子的:拖動一個圖片切片,當它與其它的圖片切片有碰撞重疊的時候,就和與其左上角距離最近的一個圖片切片交換位置,并交換其位置信息index,更新本地存儲信息中的nodePos。移動完成之后判斷游戲是否結束,若沒有,則期待下一次用戶的拖拽交換。
下面我來解釋一下這段代碼中比較難理解的幾個點:
1.圖片切片在被拖動的過程中是怎樣判斷是否和其它圖片切片發生碰撞的?這就是典型的碰撞檢測問題。
程序中實現碰撞檢測的代碼是這樣的:
//碰撞檢測 function colTest(obj1, obj2) { var t1 = obj1.offsetTop; var r1 = obj1.offsetWidth + obj1.offsetLeft; var b1 = obj1.offsetHeight + obj1.offsetTop; var l1 = obj1.offsetLeft; var t2 = obj2.offsetTop; var r2 = obj2.offsetWidth + obj2.offsetLeft; var b2 = obj2.offsetHeight + obj2.offsetTop; var l2 = obj2.offsetLeft; `if (t1 > b2 || r1 < l2 || b1 < t2 || l1 > r2)` { return false; } else { return true; } }
這段代碼看似信息量很少,其實也很好理解,判斷兩個圖片切片是否發生碰撞,只要將它們沒有發生碰撞的情形排除掉就可以了。這有點類似與邏輯中的非是即否,兩個切片又確實只可能存在兩種情況:碰撞、不碰撞。圖中的這段代碼是判斷不碰撞的情況:if (t1 > b2 || r1 < l2 || b1 < t2 || l1 > r2),返回false, else 返回true。
2.碰撞檢測完成了之后,圖片切片之間又是怎樣尋找左上角定點距離最近的元素呢?
代碼是這個樣子的:
//勾股定理求距離(左上角的距離) function getDis(obj1, obj2) { var a = obj1.offsetLeft - obj2.offsetLeft; var b = obj1.offsetTop - obj2.offsetTop; return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); } //找到距離最近的 function findMin(obj) { var minDis = 999999999; var minIndex = -1; for (var i = 0; i < aLi.length; i++) { if (obj == aLi[i]) continue; if (colTest(obj, aLi[i])) { var dis = getDis(obj, aLi[i]); if (dis < minDis) { minDis = dis; minIndex = i; } } } if (minIndex == -1) { return null; } else { return aLi[minIndex]; } }
因為都是矩形區塊,所以計算左上角的距離使用勾股定理,這點相信大家都能明白。查找距離最近的元素原理也很簡單,就是遍歷所有已經碰撞的元素,然后比較根據勾股定理計算出來的最小值,返回元素就可以了。代碼中也是使用了比較通用的方法,先聲明一個很大的值最為最小值,當有碰撞元素比其小時,再將更小的值最為最小值,遍歷完成后,返回最小值的元素就可以了。
3.圖片區塊每次交換之后,是怎樣監控判斷游戲是否已經結束的呢?
答案是回調函數,圖片切片交換函數通過回調函數來判斷游戲是否已經結束,游戲是否結束的判斷函數前面我們已經說過。圖片切片交換函數就是通過添加gameIsEnd作為回調函數,這樣在每次圖片切片移動交換完成之后,就判斷一下游戲是否結束。圖片切片的交換函數還是比較復雜的,有興趣的同學可以研究一下,下面是其實現代碼,大家重點理解其中添加了回調函數監控游戲是否結束就好了。
//通過class獲取元素 function getClass(cls){ var ret = []; var els = document.getElementsByTagName("*"); for (var i = 0; i < els.length; i++){ //判斷els[i]中是否存在cls這個className;.indexOf("cls")判斷cls存在的下標,如果下標>=0則存在; if(els[i].className === cls || els[i].className.indexOf("cls")>=0 || els[i].className.indexOf(" cls")>=0 || els[i].className.indexOf(" cls ")>0){ ret.push(els[i]); } } return ret; } function getStyle(obj,attr){//解決JS兼容問題獲取正確的屬性值 return obj.currentStyle?obj.currentStyle[attr]:getComputedStyle(obj,false)[attr]; } function gameEnd() { alert("游戲結束!"); } function startMove(obj,json,fun){ clearInterval(obj.timer); obj.timer = setInterval(function(){ var isStop = true; for(var attr in json){ var iCur = 0; //判斷運動的是不是透明度值 if(attr=="opacity"){ iCur = parseInt(parseFloat(getStyle(obj,attr))*100); }else{ iCur = parseInt(getStyle(obj,attr)); } var ispeed = (json[attr]-iCur)/8; //運動速度如果大于0則向下取整,如果小于0想上取整; ispeed = ispeed>0?Math.ceil(ispeed):Math.floor(ispeed); //判斷所有運動是否全部完成 if(iCur!=json[attr]){ isStop = false; } //運動開始 if(attr=="opacity"){ obj.style.filter = "alpha:(opacity:"+(json[attr]+ispeed)+")"; obj.style.opacity = (json[attr]+ispeed)/100; }else{ obj.style[attr] = iCur+ispeed+"px"; } } //判斷是否全部完成 if(isStop){ clearInterval(obj.timer); if(fun){ fun(); } } },30); }4 補充和總結 4.1 游戲中值得完善的功能
我認為該游戲中值得優化的地方有兩個:
1.為拼圖小游戲添加縮略圖,因為縮略圖有利于為玩游戲的用戶提供思路。我們又在瀏覽器本地存儲中保存了縮略圖的base64內容,所以實現起來也很容易。
2.緩存有的時候也讓人很痛苦,就比如說在游戲中有些用戶就想要重新開始,而我們的小游戲只有在游戲完成之后才清空緩存,刷新頁面,游戲才能夠重新開始。這給用戶的體驗很不好,我們可以加一個重置游戲按鈕,清空緩存并優化游戲結束后的一些邏輯。
這些功能感興趣的小伙伴可以嘗試一下。
4.2 總結雖然花了周末幾乎一天的時間寫了幾百行代碼才實現了一個功能不是很強大的小游戲,但是在這個過程中查閱了很多資料,總算把自己喜歡做的一件事情給完成了,還是很開心的。寫這篇博客的目的是為了和更多有相同興趣愛好的小伙伴分享一下自己的見解,筆者水平有限,希望大家對代碼有好的建議或者有更好的思路留言相告。感謝大家!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97584.html
摘要:最近公司剛好有個活動是要做一版的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來吧,大家一起加油。。。 最近公司剛好有個活動是要做一版 html5的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...
摘要:最近公司剛好有個活動是要做一版的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來吧,大家一起加油。。。 最近公司剛好有個活動是要做一版 html5的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...
摘要:最近公司剛好有個活動是要做一版的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來吧,大家一起加油。。。 最近公司剛好有個活動是要做一版 html5的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...
摘要:一簡介九宮格小游戲,可從本地圖庫載入一張圖片,填充到個,另涉及計時圖庫控件。每個格子都是相同的控件,動態添加到首頁中的,在初始化后,響應事件,之后通過多次消息傳遞,來完成整個拼圖過程。二效果圖三相關下載四相關討論五更多案例六關于 一、簡介 九宮格小游戲,可從本地圖庫載入一張圖片,填充到9個ImageView,另涉及Timer計時、圖庫控件。 每個格子都是相同的控件,動態添加到首頁中的,...
閱讀 3694·2021-11-11 10:58
閱讀 2476·2021-09-22 15:43
閱讀 2867·2019-08-30 15:44
閱讀 2186·2019-08-30 13:08
閱讀 1821·2019-08-29 17:28
閱讀 884·2019-08-29 10:54
閱讀 674·2019-08-26 11:46
閱讀 3506·2019-08-26 11:43