美思 [Solar2D] 程式設計教學:在應用程式中存取偏好設定

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

[注意事項] Corona 已改名為 Solar2D

在先前的範例中,每次專案重開時,程式又會回到初始狀態,這時因為我們沒有永久性地 (persistently) 儲存程式的狀態 (states)。在實務上,我們會儲存程式的狀態,像是使用者的偏好 (preferences)、遊戲的最高分數等;下次程式使用者重開程式時,又會回到先前的狀態。

在電腦程式中,這類議題和持久性 (persistence) 相關。在實務上,我們會透過硬碟 (hard disc) 這類非揮發性 (non-volatile) 儲存裝置將程式的狀態儲存起來。近年來流行的雲端儲存 (cloud storage) 其實仍是存在某個遠端的 (remote) 伺服器上的硬碟,故這個概念仍可通用。

在本文中,我們探討 Solar2D 在本地端的 (local) 持久性方案。一般來說,有兩種方式:

  • 檔案 (flat files),像是 JSON 檔
  • 資料庫 (databases),常見的是 SQLite 這類內嵌性資料庫

這兩者的差別在於需不需要資料庫索引的特性,JSON 像是一個簡單的鍵-值資料檔,而 SQLite 則是一個具體而微的資料庫。一般來說,使用者偏好設定會存在 JSON 檔案裡,而 TODO 清單這類資料就會存在 SQLite 資料庫中。

由於存取系統偏好設定是一個常見的功能,Corona 團隊將這個功能進行進一步地封裝,程式人只要用 Corona 提供的 API 來存取偏好,不用煩惱內部使用什麼方式來儲存 (可看這裡)。本範例即是用這項特性來儲存程式狀態;我們將完整的程式碼放在這裡,本文僅節錄部分內容。

本範例程式的示意圖如下:

{{< figure src="/img/corona-sdk/preferences.png" alt="Solar2D 的 segmented control 和 slider" class="phone" >}}

基本上,這個範例和先前的範例在功能上大抵雷同,只是加上持久性狀態的功能。

一開始,我們設置一些程式會用到的基本數值:

-- Set valid colors.
local color = {}
color.red = {255, 0, 0}
color.orange = {255, 165, 0}
color.yellow = {255, 255, 0}
color.green = {0, 255, 0}
color.blue = {0, 0, 255}
color.purple = {128, 0, 128}

-- Set valid sizes for different shapes.
local sizeCircle = {}
sizeCircle.small = 30
sizeCircle.medium = 40
sizeCircle.large = 50

local sizeRect = {}
sizeRect.small = {60, 36}
sizeRect.medium = {80, 48}
sizeRect.large = {100, 60}

local sizeTriangle = {}
sizeTriangle.small = {-20, -25, -20, 25, 30, 0}
sizeTriangle.medium = {-30, -35, -30, 35, 40, 0}
sizeTriangle.large = {-40, -45, -40, 45, 50, 0}

local size = {}
size.circle = sizeCircle
size.rectangle = sizeRect
size.triangle = sizeTriangle

-- Set location for our item.
local itemLocX = 0.5
local itemLocY = 0.2

-- Set the list of valid options.
local sizes = { "small", "medium", "large" }
local colors = { "red", "orange", "yellow", "green", "blue", "purple" }
local shapes = { "circle", "rectangle", "triangle" }

在這個範例中,我們將生成多邊形的函式獨立出來,因為我們在程式啟動及事件處理器中都會用到這個函式。程式碼參考如下:

local function getShape(options)
  local item

  if options.shape == "circle" then
    item = display.newCircle(options.x, options.y, size[options.shape][options.size])
  elseif options.shape == "rectangle" then
    item = display.newRoundedRect(options.x, options.y,
      size[options.shape][options.size][1], size[options.shape][options.size][2], 10)
  elseif options.shape == "triangle" then
    item = display.newPolygon(options.x, options.y,
      {size[options.shape][options.size][1], size[options.shape][options.size][2],
        size[options.shape][options.size][3], size[options.shape][options.size][4],
        size[options.shape][options.size][5], size[options.shape][options.size][6]})
  end

  item:setFillColor(color[options.color][1] / 255, color[options.color][2] / 255, color[options.color][3] / 255)

  return item
end

這個函式是我們要從 PickerWheel 中取得索引值用的:

local function getIndex(list, item)
  for i = 1, #list do
    if item == list[i] then
      return i
    end
  end
end

在程式一開始時,我們讀取偏好值:

-- Get system prefs.
local mySize = system.getPreference("app", "mySize")
if mySize == nil then
  mySize = "medium"
end

local myColor = system.getPreference("app", "myColor")
if myColor == nil then
  myColor = "orange"
end

local myShape = system.getPreference("app", "myShape")
print("myShape: " .. myShape)
if myShape == nil then
  myShape = "circle"
end

由於程式第一次啟動時,偏好值為空值,故我們在程式中要檢查這個情形,並塞入起始值。

在程式啟動時建立起始的 item 多邊形物件:

-- Init `item`.
local item = getShape({
  x = display.contentWidth * itemLocX,
  y = display.contentHeight * itemLocY,
  size = mySize,
  color = myColor,
  shape = myShape,
})

建立 PickerWheel 物件:

-- Set initial columnData by system prefs.
local columnData = {
  {
    align = "left",
    width = 100,
    startIndex = getIndex(sizes, mySize),
    labels = sizes,
  },
  {
    align = "left",
    width = 110,
    startIndex = getIndex(colors, myColor),
    labels = colors,
  },
  {
    align = "left",
    startIndex = getIndex(shapes, myShape),
    labels = shapes,
  }
}

-- Create a PickerWheel object.

這裡和先前的範例不同,我們不把起始值寫死,而是藉由讀取系統偏好來動態地調整 PickerWheel 的起始位置。

同樣地,我們用 timer 物件來監看 PickerWheel 的值,並動態地改變 item 物件:

-- The event listener for `pickerWheel`.
local onValueSelected = function ()
  local values = pickerWheel:getValues()

  local _size = values[1].value
  local _color = values[2].value
  local _shape = values[3].value

  item:removeSelf()

  item = getShape({
    x = display.contentWidth * itemLocX,
    y = display.contentHeight * itemLocY,
    size = _size,
    color = _color,
    shape = _shape,
  })

  system.setPreferences("app", {
    mySize = _size,
    myColor = _color,
    myShape = _shape,
  })
end

-- Update `item` by current selection.
timer.performWithDelay(100, onValueSelected, -1)

在這段程式碼中,除了修改 item 外,我們也將程式狀態存在偏好中,下次啟動程式時就會從同樣的狀態回復。

關於作者

身為資訊領域碩士,美思認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

美思喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,美思將所學寫成文章,放在這個網站上和大家分享。