LibGDX | Android | SVGAssetManager 🔩 🪄

Асинхронная загрузка ресурсов SVG

Vladislav Shesternin
4 min readFeb 26, 2022
Photo by Mike van den Bos on Unsplash

1️⃣Создайте LibGDX пример только для Android как описано в статье:
https://veldan1202.medium.com/libgdx-%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE-%D0%B4%D0%BB%D1%8F-android-4858e26734cf

2️⃣Настройте проэкт для работы с SVG как описано в статье:
https://veldan1202.medium.com/libgdx-svg-android-602323369175

3️⃣Подключете следующие зависимости:

dependencies {
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
def gdx_version = '1.10.0'
api "com.badlogicgames.gdx:gdx-backend-android:$gdx_version"
natives "com.badlogicgames.gdx:gdx-platform:$gdx_version:natives-armeabi-v7a"
natives "com.badlogicgames.gdx:gdx-platform:$gdx_version:natives-arm64-v8a"
natives "com.badlogicgames.gdx:gdx-platform:$gdx_version:natives-x86"
natives "com.badlogicgames.gdx:gdx-platform:$gdx_version:natives-x86_64"
def core_version = '1.7.0'
implementation "androidx.core:core-ktx:$core_version"
def appcompat_version = '1.3.1'
implementation "androidx.appcompat:appcompat:$appcompat_version"
def material_version = '1.4.0'
implementation "com.google.android.material:material:$material_version"
def constraintLayout_version = '2.1.1'
implementation "androidx.constraintlayout:constraintlayout:$constraintLayout_version"
def navigation_version = '2.3.5'
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
}

4️⃣Реализуйте 2 класса: SVGTextureData и SVGAssetManager

SVGTextureData:

import com.badlogic.gdx.graphics.Texture

data class SVGTextureData(
val path: String,
val width: Int,
val height: Int,
var texture: Texture? = null
)

SVGAssetManager:

import android.opengl.GLES20
import android.opengl.GLUtils
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.utils.Disposable
import com.scand.svg.SVGHelper
import com.veldan.svgassetmanager.activityContext
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

object SVGAssetManager : Disposable {

private val coroutineLoad = CoroutineScope(Dispatchers.Main)
private val coroutineProgress = CoroutineScope(Dispatchers.Main)
private val mutex = Mutex()

var loadListSVG = listOf<EnumSVG>()
var loadListSVGList = listOf<EnumSVGList>()



override fun dispose() {
coroutineLoad.cancel()
coroutineProgress.cancel()
}



private fun getAssetString(path: String) = activityContext.assets.open(path).bufferedReader().use { it.readText() }

private suspend fun SVGTextureData.generateTexture() = CompletableDeferred<Boolean>().also { continuation ->
val bitmap = SVGHelper.noContext().open(getAssetString(path)).setRequestBounds(width, height).bitmap
Gdx.app.postRunnable {
texture = Texture(bitmap.width, bitmap.height, Pixmap.Format.RGBA8888)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture!!.textureObjectHandle)
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
bitmap.recycle()
continuation.complete(true)
}
}
.await()

suspend fun load(
progress: (Int) -> Unit = { },
loaded: () -> Unit = { }
) = CompletableDeferred<Boolean>().also { continuation ->

if (loadListSVG.isEmpty() && loadListSVGList.isEmpty()) throw Exception("loadLists isEmpty = true")

val loadList = mutableListOf<SVGTextureData>().apply {
loadListSVG.onEach { add(it.svg) }
loadListSVGList.onEach { enumSvgList -> enumSvgList.svgList.onEach { add(it) } }
}

val progressFlow = MutableStateFlow(0f)
coroutineProgress.launch {
progressFlow.collect {
progress(it.toInt())
}
}

val onePercentProgress = 100f / loadList.size
val listJob = mutableListOf<Job>()

loadList.onEach { svg ->
coroutineLoad.launch {
svg.generateTexture()
mutex.withLock { progressFlow.value += onePercentProgress }
}
.apply { listJob.add(this) }
}
listJob.joinAll()
progressFlow.value = 100f
loaded()
continuation.complete(true)
}.await()



enum class EnumSVG(val svg: SVGTextureData) {
A(SVGTextureData("a.svg", 500, 500)),
}

enum class EnumSVGList(val svgList: List<SVGTextureData>) {
LIST(List(3) { SVGTextureData("list/${it.inc()}.svg", 500, 500) })
}

}

Как использовать SVGAssetManager:

1️⃣Добавляем свои SVG файлы в папку assets:

2️⃣В классе SVGAssetManager имеется 2 enum класса: EnumSVG и EnumSVGList

EnumSVG — enum предназначен для хранения одиночных SVGTextureData.
EnumSVGList — enum предназначен для хранения списков SVGTextureData.
SVGTextureData — data class содержит информацию о пути к файлу, размерах файла, и поле в которое будет присвоена сгенерированя текстура которая и будет использоваться.

В EnumSVG и EnumSVGList нужно поместить данные о загружаемых ресурсах.

3️⃣Устанавливаем SVGTextureData необходимые для загрузки:

SVGAssetManager содержит var поля для установки списка загружаемых элементов.

В классе где будет происходить загрузка элементов для начала необходимо установить в loadListSVG и loadListSVGList списки необходимых для загрузки элементов.

После установки необходимых для загрузки элементов необходимо вызвать метод непосредственной загрузки установленных ранее элементов.

SVGAssetManager.load — это функция высшего порядка, аргументами которой являются две лямбды: progress (Int) -> Unit = {} и loaded () -> Unit = {}.

progress — в её блоке есть возможность получить прогресс загрузки от 0 до 100.

loaded — выполняется при завершении загрузки.

Вот и всё, теперь Вы можете использовать загруженные SVG фаёлы как обычные текстуры:

PS. Vel_daN: Love what You DO 💚.

--

--