Android?上實(shí)現(xiàn)DragonBones換裝功能
前言
最近在預(yù)研一款換裝的小游戲,通過在積分樂園中兌換服裝,就可以在不同場(chǎng)景中展示穿上新服裝的角色。對(duì)于這類有主題形象的動(dòng)畫,自然就想到了骨骼動(dòng)畫,通過網(wǎng)格自由變形和蒙皮技術(shù)就能在視覺上呈現(xiàn)所需要的動(dòng)畫效果,并且骨骼動(dòng)畫也支持皮膚替換,或者插槽的圖片替換,對(duì)于換裝的需求比較友好。因此決定使用骨骼動(dòng)畫來實(shí)現(xiàn)換裝小游戲的Demo,以下就是在Android平臺(tái)上實(shí)現(xiàn)DragonBones換裝的過程。
技術(shù)選型
對(duì)于DragonBones在Android端的渲染顯示,有多個(gè)方案可以選擇,例如:白鷺引擎或者Cocos2d游戲引擎。最終選擇使用korge來進(jìn)行渲染,為什么拋棄Cocos2d這個(gè)廣泛使用的游戲引擎來渲染呢?主要理由是:
- Cocos2d 游戲引擎加載比較耗時(shí),其首次加載時(shí)間無法接受;
- Cocos2d 編譯出來的底層依賴需要單獨(dú)裁剪,裁剪后的libcocos.so依然較大;
- Cocos2d 對(duì)于游戲動(dòng)畫的渲染,其渲染的載體是Activity,也就是編譯出來的CocosActivity,這個(gè)是無法滿足業(yè)務(wù)需要的。因此需要自定義游戲容器,并且需要改動(dòng)畫加載的容器載體和加載路徑。簡(jiǎn)單點(diǎn)來說,可以從任意路徑來加載游戲資源(例如網(wǎng)絡(luò)或者本地,不僅僅是assets目錄),并且可以在自定義View中進(jìn)行渲染。解決思路可以參考:Android實(shí)戰(zhàn)之Cocos游戲容器搭建
最終,還是在官方的Github上發(fā)現(xiàn)這條Issue,從而找到了Android上渲染DragonBones的方式。Korge的介紹是這樣的
Modern Multiplatform Game Engine for Kotlin.
Korge的基本用法
1)創(chuàng)建 DragonBones Scene
class DisplayChangeImgScene : BaseDbScene() {
companion object {
? ? ? ?private const val SKE_JSON = "mecha_1004d_show/mecha_1004d_show_ske.json"
? ? ? ?private const val TEX_JSON = "mecha_1004d_show/mecha_1004d_show_tex.json"
? ? ? ?private const val TEX_PNG = "mecha_1004d_show/mecha_1004d_show_tex.png"
? }
? ?private val factory = KorgeDbFactory()
? ?override suspend fun Container.createSceneArmatureDisplay(): KorgeDbArmatureDisplay {
? ? ? ?val skeDeferred = asyncImmediately { res[SKE_JSON].readString() }
? ? ? ?val texDeferred = asyncImmediately { res[TEX_JSON].readString() }
? ? ? ?val imgDeferred = asyncImmediately { res[TEX_PNG].readBitmap().mipmaps() }
?
? ? ? ?val skeJsonData = skeDeferred.await()
? ? ? ?val texJsonData = texDeferred.await()
? ? ? ?factory.parseDragonBonesData(Json.parse(skeJsonData)!!)
? ? ? ?factory.parseTextureAtlasData(Json.parse(texJsonData)!!, imgDeferred.await())
?
? ? ? ?val armatureDisplay = factory.buildArmatureDisplay("mecha_1004d")!!.position(500, 700)
? ? ? ?armatureDisplay.animation.play("idle")
?
? ? ? ?return armatureDisplay
? }
}2)使用KorgeAndroidView加載 Scene Module
class MainActivity : AppCompatActivity() {
? ?private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }?
? ?private val slotDisplayModule by sceneModule<DisplayChangeImgScene>()?
? ?override fun onCreate(savedInstanceState: Bundle?) {
? ? ? ?super.onCreate(savedInstanceState)
? ? ? ?setContentView(binding.root)
? ? ? ?binding.root.addView(KorgeAndroidView(this).apply {
? ? ? ? ? ?loadModule(slotDisplayModule)
? ? ? })
? }
}
3)sceneModule 函數(shù)
@MainThread
inline fun <reified DS : BaseDbScene> Activity.sceneModule(
? ?windowWidth: Int = resources.displayMetrics.widthPixels,
? ?windowHeight: Int = resources.displayMetrics.heightPixels
): Lazy<Module> {
? ?return SceneModuleLazy(DS::class, windowWidth, windowHeight)
}
class SceneModuleLazy<DS : BaseDbScene>(
? ?private val dbSceneClass: KClass<DS>,
? ?private val width: Int,
? ?private val height: Int
) : Lazy<Module> {
? ?private var cached: Module? = null?
? ?override val value: Module
? ? ? ?get() {
? ? ? ? ? ?return cached ?: object : Module() {
? ? ? ? ? ? ? ?override val mainScene = dbSceneClass
? ? ? ? ? ? ? ?override suspend fun AsyncInjector.configure() {
? ? ? ? ? ? ? ? ? ?mapPrototype(dbSceneClass) {
? ? ? ? ? ? ? ? ? ? ? ?val sceneInstance = Class.forName(dbSceneClass.qualifiedName!!).newInstance()
? ? ? ? ? ? ? ? ? ? ? ?sceneInstance as DS
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?override val fullscreen = true?
? ? ? ? ? ? ? ?override val size: SizeInt
? ? ? ? ? ? ? ? ? ?get() = SizeInt(width, height)
? ? ? ? ? ? ? ?override val windowSize: SizeInt
? ? ? ? ? ? ? ? ? ?get() = SizeInt(width, height)
? ? ? ? ? }
? ? ? }?
? ?override fun isInitialized(): Boolean = cached != null
}
上面就是最簡(jiǎn)單的Demo,通過加載DragonBones的配置數(shù)據(jù)即可顯示骨骼動(dòng)畫。
實(shí)現(xiàn)換裝的多種實(shí)現(xiàn)
靜態(tài)換裝 vs 動(dòng)態(tài)換裝
靜態(tài)換裝
如果換裝的素材是固定的,可以預(yù)先放置在插槽里,通過切換插槽的displayIndex實(shí)現(xiàn)換裝。
在骨骼動(dòng)畫設(shè)計(jì)時(shí),每個(gè)slot可對(duì)應(yīng)多個(gè)display,例如:
{
?"name": "weapon_hand_l",
?"display": [
? {
? ? ?"name": "weapon_1004_l",
? ? ?"transform": {
? ? ? ?"x": 91.22,
? ? ? ?"y": -30.21
? ? }
? },
? {
? ? ?"name": "weapon_1004b_l",
? ? ?"transform": {
? ? ? ?"x": 122.94,
? ? ? ?"y": -44.14
? ? }
? },
? {
? ? ?"name": "weapon_1004c_l",
? ? ?"transform": {
? ? ? ?"x": 130.95,
? ? ? ?"y": -56.95
? ? }
? },
? {
? ? ?"name": "weapon_1004d_l",
? ? ?"transform": {
? ? ? ?"x": 134.67,
? ? ? ?"y": -55.25
? ? }
? },
? {
? ? ?"name": "weapon_1004e_l",
? ? ?"transform": {
? ? ? ?"x": 155.62,
? ? ? ?"y": -59.2
? ? }
? }
]
}
在代碼中,可直接切換display進(jìn)行換裝,即:
? ?private var leftWeaponIndex = 0
? ?private val leftDisplayList = listOf(
? ? ? ?"weapon_1004_l", "weapon_1004b_l", "weapon_1004c_l", "weapon_1004d_l", "weapon_1004e_l"
? )
? ?override suspend fun Container.createSceneArmatureDisplay(): KorgeDbArmatureDisplay {
? ? ? ?val skeDeferred = asyncImmediately { Json.parse(res["mecha_1004d_show/mecha_1004d_show_ske.json"].readString())!! }
? ? ? ?val texDeferred = asyncImmediately { res["mecha_1004d_show/mecha_1004d_show_tex.json"].readString() }
? ? ? ?val imgDeferred = asyncImmediately { res["mecha_1004d_show/mecha_1004d_show_tex.png"].readBitmap().mipmaps() }
? ? ? ?factory.parseDragonBonesData(skeDeferred.await())
? ? ? ?factory.parseTextureAtlasData(Json.parse(texDeferred.await())!!, imgDeferred.await())?
? ? ? ?val armatureDisplay = factory.buildArmatureDisplay("mecha_1004d")!!.position(500, 700)
? ? ? ?armatureDisplay.animation.play("idle")
?
? ? ? ?val slot = armatureDisplay.armature.getSlot("weapon_hand_l")!!
? ? ? ?mouse {
? ? ? ? ? ?upAnywhere {
? ? ? ? ? ? ? ?leftWeaponIndex++;
? ? ? ? ? ? ? ?leftWeaponIndex %= leftDisplayList.size
?
? ? ? ? ? ? ? ?factory.replaceSlotDisplay(
? ? ? ? ? ? ? ? ? ?dragonBonesName = "mecha_1004d_show",
? ? ? ? ? ? ? ? ? ?armatureName = "mecha_1004d",
? ? ? ? ? ? ? ? ? ?slotName = "weapon_hand_l",
? ? ? ? ? ? ? ? ? ?displayName = leftDisplayList[leftWeaponIndex],
? ? ? ? ? ? ? ? ? ?slot = slot
? ? ? ? ? ? ? )
? ? ? ? ? }
? ? ? }?
? ? ? ?return armatureDisplay
? }動(dòng)態(tài)換裝
如果換裝的素材是不固定的,需要?jiǎng)討B(tài)獲取資源,或者通過一張外部圖片來實(shí)現(xiàn)換裝效果,可以通過修改slot的顯示紋理即可實(shí)現(xiàn)。
```
// 換裝原理是:通過factory.parseTextureAtlasData來解析紋理數(shù)據(jù),紋理為外部圖片,紋理配置為Mock數(shù)據(jù)
private fun changeSlotDisplay(slot: Slot, replaceBitmap: Bitmap) {
? ?// 使用 HashCode 來作為 骨架名稱 和 骨骼名稱
? ?val replaceArmatureName = replaceBitmap.hashCode().toString()
? ?// 需要替換的插槽所包含的顯示對(duì)象
? ?val replaceDisplayName = slot._displayFrames.first { it.rawDisplayData != null }.rawDisplayData!!.name
? ?// 通過factory解析紋理數(shù)據(jù)
? ?val mockTexModel = mockTexModel(replaceArmatureName, replaceDisplayName, replaceBitmap.width, replaceBitmap.height)
? ?val textureAtlasData = Json.parse(gson.toJson(mockTexModel))!!
? ?factory.parseTextureAtlasData(textureAtlasData, replaceBitmap.mipmaps())
?
? ?// 替換 Display 的紋理,替換的圖片和原圖大小、位置一致
? ?val replaceTextureData = getReplaceDisplayTextureData(replaceArmatureName, replaceDisplayName)
? ?slot.replaceTextureData(replaceTextureData)
?
? ?slot._displayFrame?.displayData?.transform?.let {
? ? ? ?// 修改 display 相對(duì)于 slot 的位置、初始縮放等配置
? }
}
private fun getReplaceDisplayTextureData(replaceArmatureName: String, replaceDisplayName: String): TextureData {
? ?val data = factory.getTextureAtlasData(replaceArmatureName)
? ?data!!.fastForEach { textureAtlasData ->
? ? ? ?val textureData = textureAtlasData.getTexture(replaceDisplayName)
? ? ? ?if (textureData != null) {
? ? ? ? ? ?return textureData
? ? ? }
? }
? ?throw Exception("getNewDisplayTextureData null")
}
private fun mockTexModel(armatureName: String, displayName: String, imgW: Int, imgH: Int): DragonBonesTexModel {
? ?val originTexModel = gson.fromJson(texJsonData, DragonBonesTexModel::class.java)
?
? ?val subTexture: DragonBonesTexModel.SubTexture = run loop@{
? ? ? ?originTexModel.subTexture.forEach { subTexture ->
? ? ? ? ? ?if (subTexture.name == displayName) {
? ? ? ? ? ? ? ?return@loop subTexture.apply {
? ? ? ? ? ? ? ? ? ?this.x = 0
? ? ? ? ? ? ? ? ? ?this.y = 0
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
? ? ? ?throw Exception("Can not find replace display!")
? }
? ?return DragonBonesTexModel(
? ? ? ?name = armatureName,
? ? ? ?width = imgW,
? ? ? ?height = imgH,
? ? ? ?subTexture = listOf(subTexture)
? )
}
```
包含動(dòng)畫 vs 不包含動(dòng)畫
如果換裝的部位不包含動(dòng)畫,則可以使用圖片做為換裝素材,具體實(shí)現(xiàn)方法如上。 如果換裝的部位包含動(dòng)畫,則可以使用子骨架做為換裝的素材,API調(diào)用方法和換圖片是一樣的,只不過換進(jìn)去的是子骨架的顯示對(duì)象,在引擎層面,圖片和子骨架的顯示對(duì)象都是顯示對(duì)象,所以處理起來是一樣的,唯一不同的是子骨架不需要考慮軸點(diǎn),也不能重新設(shè)置軸點(diǎn),因?yàn)樗陨碛袆?dòng)畫數(shù)據(jù)相當(dāng)于已經(jīng)包含軸點(diǎn)信息。
先將原始骨骼動(dòng)畫文件中,該slot的display信息定義為空。例如:
{
?"name": "1036",
?"display": [
? {
? ? ?"name": "blank"
? }
]
},
{
?"name": "1082",
?"display": [
? {
? ? ?"name": "blank"
? }
]
},
在子骨架中定義 slot 的 display 信息。例如:
? ? ? ? ? "slot": [
? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ?"name": "1019",
? ? ? ? ? ? ? ? ? ?"parent": "root"
? ? ? ? ? ? ? }
? ? ? ? ? ],
? ? ? ? ? ?"skin": [
? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ?"name": "",
? ? ? ? ? ? ? ? ? ?"slot": [
? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ?"name": "1019",
? ? ? ? ? ? ? ? ? ? ? ? ? ?"display": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"type": "mesh",
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"name": "glove/2080500b",
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"width": 159,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"height": 323,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"vertices": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?104.98,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?-1078.6,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?108.08,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?-1094.03
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"uvs": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0.45257,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0.1035,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0.4721,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0.15156,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0.4234,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0.05575
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"triangles": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?7,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?11,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?18,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?20
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"weights": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?3,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0.92
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"slotPose": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1,
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"bonePose": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?6,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0.193207,
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?139.903737,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?-897.076346
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"edges": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?19,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?18,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?18,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?20,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?19
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"userEdges": [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?16,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?11,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?7
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ]
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ]
? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ]
? ? ? ? ? ? ? }
? ? ? ? ? ],
使用子骨架的顯示對(duì)象進(jìn)行替換,以下是使用直接替換 skin 的方式,和替換 display 的原理相同。
private suspend fun replaceDragonBonesDisplay(armatureDisplay: KorgeDbArmatureDisplay) {
? ?val path = "you_xin/suit1/replace/"
? ?val dragonBonesJSONPath = path + "xx_ske.json"
? ?val textureAtlasJSONPath = path + "xx_tex.json"
? ?val textureAtlasPath = path + "xx_tex.png"
? ?// 加載子骨架數(shù)據(jù)
? ?factory.parseDragonBonesData(Json.parse(res[dragonBonesJSONPath].readString())!!)
? ?factory.parseTextureAtlasData(
? ? ? ?Json.parse(res[textureAtlasJSONPath].readString())!!,
? ? ? ?res[textureAtlasPath].readBitmap().mipmaps()
? )
? ?// 獲取解析后的骨骼數(shù)據(jù)
? ?val replaceArmatureData = factory.getArmatureData("xx")
? ?// 通過 replaceSkin 的方式修改 slot display
? ?factory.replaceSkin(armatureDisplay.armature, replaceArmatureData!!.defaultSkin!!)
}
局部換裝 vs 全局換裝
之前說的都是局部換裝,替換的是紋理集中的一塊子紋理,如果希望一次性替換整個(gè)紋理集也是支持的。但是紋理集的配置文件不能換(如果配置文件也要換的話,就直接重新構(gòu)建骨架就好) 也就是說游戲中可以有一套紋理集配置文件對(duì)應(yīng)多個(gè)紋理集圖片,實(shí)現(xiàn)配置文件不變的情況下?lián)Q整個(gè)紋理集。利用這個(gè)技術(shù)可以實(shí)現(xiàn)例如格斗游戲中同樣的角色穿不同顏色的衣服的效果。
全局換裝之Skin修改
DragonBones支持多套皮膚的切換,如果皮膚時(shí)固定的,可預(yù)先配置在骨骼動(dòng)畫文件中,需要時(shí)直接切換即可。
private fun changeDragonBonesSkin(armatureDisplay: KorgeDbArmatureDisplay) {
? ?val replaceSkin = factory.getArmatureData("xxx")?.getSkin("xxx") ?: return
? ?factory.replaceSkin(armatureDisplay.armature, replaceSkin)
}
全局換裝之紋理修改
如果皮膚并未固定的,需要?jiǎng)討B(tài)配置或者網(wǎng)絡(luò)下發(fā),那么可以使用紋理替換的方式。
private suspend fun changeDragonBonesSkin() {
? ?val texDeferred = asyncImmediately { res["body/texture_01.png"].readBitmap().mipmaps() }
? ?factory.updateTextureAtlases(texDeferred.await(), "body")
}
總結(jié)
對(duì)于一款換裝小游戲來講,使用Spine或者是DragonBones的差異不大,其設(shè)計(jì)思路基本相同,而且Korge同樣也是支持Spine的渲染。從技術(shù)實(shí)現(xiàn)上,換裝的功能并不難實(shí)現(xiàn),只是需要考慮的細(xì)節(jié)方面還有很多,例如:
- 服裝商城的在線配置和管理,并且有些服裝還可能自帶動(dòng)畫
- 某些服裝可能涉及多個(gè)插槽,例如:一套裙子,有一部分的層級(jí)在身體前面,另一部分的層級(jí)在身體后面,那就意味需要兩個(gè)插槽才能實(shí)現(xiàn)
- 如果該人物形象在多個(gè)界面或者應(yīng)用中出現(xiàn),動(dòng)畫效果不同,但是身上的服裝相同,需要考慮處理?yè)Q裝后服裝同步的問題
到此這篇關(guān)于Android 上實(shí)現(xiàn)DragonBones換裝功能的文章就介紹到這了,更多相關(guān)Android DragonBones換裝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android 加載本地聯(lián)系人實(shí)現(xiàn)方法
在android開發(fā)過程中,有些功能需要訪問本地聯(lián)系人列表,本人搜集整理了一番,拿出來和大家分享一下,希望可以幫助你們2012-12-12
Android頁(yè)面中引導(dǎo)蒙層的使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android頁(yè)面中的引導(dǎo)蒙層使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
Android activity動(dòng)畫不生效原因及解決方案總結(jié)
android activity動(dòng)畫是一個(gè)比較簡(jiǎn)單的功能。但是使用時(shí)總會(huì)由于各種小問題導(dǎo)致動(dòng)畫失效,筆者根據(jù)自己經(jīng)驗(yàn),整理了各種可能導(dǎo)致的原因,期望能對(duì)你有所幫助2021-11-11
Android如何實(shí)現(xiàn)設(shè)備的異顯功能詳解
這篇文章主要給大家介紹了關(guān)于Android如何實(shí)現(xiàn)設(shè)備的異顯功能的相關(guān)資料,這篇文章通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
android實(shí)現(xiàn)拍照或從相冊(cè)選取圖片
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)拍照或從相冊(cè)選取圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03
android實(shí)現(xiàn)簡(jiǎn)單進(jìn)度條ProgressBar效果
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)簡(jiǎn)單進(jìn)度條ProgressBar效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07

