Magic Mistletoe Android多主题(换肤)切换框架
背景
时隔四年,在网易换肤之前的思路下,做了几点改进,现在完全通过反射创建View,并且在
SkinLoadManager
中提供一个configCustomAttrs
以支持自定义View的属性插队替换
摈弃了之前的
AsyncTask
,使用kotlin 协程进行主题包的资源转换
使用kotlin重构所有Java代码实现
效果展示
默认主题
点击切换主题
最佳使用方式
STEP 1 宿主项目(示例Demo为本项目
app
模块)依赖多主题框架AAR
//in root build.gradle
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
// in app/module build.gradle
dependencies {
implementation 'com.github.mistletoe5215:MagicMistletoe:1.0.0'
}
STEP 2 制作多主题包
找个壳工程(本Demo为
theme-pkg
模块),在res下放置资源文件,注意
资源文件名称需要与宿主app
内的资源文件名称保持一致,这样主题切换的时候才可以成功替换
执行打包命令
找到生成的资源apk
重命名为
你喜欢的名字.zip
(如果有强迫症)
这里是做演示,(实际商用过程中,可将zip包给服务端,进行主题包签名处理,后通过下载的形式down到本地,解签,应用)拷贝主题文件至
宿主app
的assets目录下
释放assets目录主题文件至私有路径
return true
}
}
val `is`: InputStream = assets.open(fileName)
val fos = FileOutputStream(outFile)
val buffer = ByteArray(1024)
var byteCount: Int
while (`is`.read(buffer).also { byteCount = it } != -1) {
fos.write(buffer, 0, byteCount)
}
fos.flush()
`is`.close()
fos.close()
return true
} catch (e: IOException) {
e.printStackTrace()
}
return false
}
“>
private fun copyAssetAndWrite(fileName: String): Boolean {
try {
val cacheDir = cacheDir
if (!cacheDir.exists()) {
cacheDir.mkdirs()
}
val outFile = File(cacheDir, fileName)
if (!outFile.exists()) {
val res = outFile.createNewFile()
if (!res) {
return false
}
} else {
if (outFile.length() > 10) {
return true
}
}
val `is`: InputStream = assets.open(fileName)
val fos = FileOutputStream(outFile)
val buffer = ByteArray(1024)
var byteCount: Int
while (`is`.read(buffer).also { byteCount = it } != -1) {
fos.write(buffer, 0, byteCount)
}
fos.flush()
`is`.close()
fos.close()
return true
} catch (e: IOException) {
e.printStackTrace()
}
return false
}
STEP 3 XML中需要更换主题的控件设置好多主题属性
multiTheme:enable="true"
<div class="highlight highlight-text-xml position-relative" data-snippet-clipboard-copy-content="
“>
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" xmlns:multiTheme="http://schemas.android.com/apk/multi.theme" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/main_bg" multiTheme:enable="true" tools:context=".MainActivity"> <com.google.android.material.imageview.ShapeableImageView android:src="@drawable/ic_avatar" android:layout_marginTop="20dp" android:layout_width="220dp" android:layout_height="220dp" android:scaleType="centerCrop" android:id="@+id/avatar" android:background="@color/main_text" multiTheme:enable="true" app:shapeAppearance="@style/CircleStyle" app:layout_constraintBottom_toTopOf="@+id/copy" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:background="@color/teal_200" android:layout_marginTop="20dp" android:layout_width="match_parent" android:layout_height="100dp" android:gravity="center" android:id="@+id/copy" android:text="释放主题包到应用内存(切换前必点)" android:textColor="@color/main_text" multiTheme:enable="true" app:layout_constraintBottom_toTopOf="@+id/change_skin" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/avatar" /> <TextView android:background="@color/teal_200" android:layout_marginTop="20dp" android:layout_width="match_parent" android:layout_height="100dp" android:gravity="center" android:id="@+id/change_skin" android:text="切换主题" android:textColor="@color/main_text" multiTheme:enable="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/copy" /> </androidx.constraintlayout.widget.ConstraintLayout>