APP动态切换字体的实现
qq2519157 • 2019 年 01 月 10 日
需求
最近做完了新功能,突发奇想,想着如何处理app的动态切换字体
方案
方案1:通过反射机制,修改Typeface类的字体库引用
object FontUtils {
fun setDefaultFont(context: Context,staticTypefaceFieldName:String,fontAssetName:String){
val font = Typeface.createFromAsset(context.assets, fontAssetName)
replaceFont(staticTypefaceFieldName,font)
}
fun replaceFont(staticTypefaceFieldName: String, newTypeface: Typeface) {
try {
val staticField = Typeface::class.java.getDeclaredField(staticTypefaceFieldName)
staticField.isAccessible = true
staticField.set(null, newTypeface)
} catch (e: NoSuchFieldException) {
e.printStackTrace();
} catch (e: IllegalAccessException) {
e.printStackTrace();
}
}
}
不过现有项目都是kotlin写的,不太想用反射
方案2:通过修改特定的View或者自定义TextView
工作量太大,而且效果不好.
方案3:通过第三方库
实现
接入
2.0版本
api 'uk.co.chrisjenx:calligraphy:2.3.0'
3.0版本
api 'io.github.inflationx:calligraphy3:3.0.0'
api 'io.github.inflationx:viewpump:1.0.0'
因为我是在library模块引入的,所以使用的api方式
>初始化
我们可以选择在自定义的Application中初始化,也可以在LauncherActivity中,看个人喜好.
2.0版本
private fun initTypeFace() {
val path = PreferenceUtil.getPreference("fonts")!!.getString("fontPath", "fonts/FounderBlack.ttf")
CalligraphyConfig.initDefault(CalligraphyConfig.Builder()
.setDefaultFontPath(path)
.setFontAttrId(R.attr.fontPath)
.build()
)
}
3.0版本
ViewPump.init(ViewPump.builder()
.addInterceptor(CalligraphyInterceptor(
CalligraphyConfig.Builder()
.setDefaultFontPath(path)
.setFontAttrId(R.attr.fontPath)
.build()))
.build())
在onCreate()方法中调用initTypeFace()即可
这段代码是初始化我们的字体,其实在这里我们就可以选择自己喜欢的字体了,不过选完以后字体就固定了,这与我们的需求不符.
我们先去自己的BaseActivity中处理一下
2.0版本
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase))
}
3.0版本
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase?:return))
}
因为要让字体全局生效,必然要在每个Activity的attachBaseContext()方法中绑定我们的框架,所以BaseActivity是最好的选择了.
其实到这里,我们的三方字体就能正常显示了,当然,如果你想针对单个控件设置特别的字体,你可以这样做
android:id="@+id/semibold"
android:text="Bold"
fontPath="fonts/mySemibold.ttf"
android:padding="10dp"
android:gravity="center"
android:onClick="@{onclick}"
android:textSize="14sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
只需要给文本控件设置fontPath就好了
高级操作
起初,我们的字体是这样的
我们的目的是要让用户自己选择显示什么样的字体,比如这样:
因此我们还需要其他操作,新建一个FontActivity,在其伴生类中初始化所有字体
companion object {
private val fonts = arrayListOf(
FontStyle("杨任东竹石粗体", "YRDBold", "fonts/YRDBold.ttf"),
FontStyle("杨任东竹石细体", "TRDExtralight", "fonts/YRDExtralight.ttf"),
FontStyle("站酷快乐体2016", "ZhankuHappy2016", "fonts/zhankuHappy2016.ttf"),
FontStyle("站酷高端黑", "ZhankKuAdvancedBlack", "fonts/zhankuAdvancedBlack.ttf"),
FontStyle("王汉宗魏碑体", "WHZ_WB", "fonts/WHZ_WB.ttf"),
FontStyle("Oradano-Mincho名朝", "OradanoMincho", "fonts/Oradano-Mincho.ttf"),
FontStyle("阿里汉仪智能黑体", "AliSmartBlack", "fonts/AliSmartBlack.ttf"),
FontStyle("庞门正道标题体2.0增强版", "PMZD_Title", "fonts/PMZD-Title.ttf"),
FontStyle("思源柔黑体", "GenJyuuGothic", "fonts/GenJyuuGothic-Medium.ttf"),
FontStyle("方正黑体", "FounderBlack", "fonts/FounderBlack.ttf"),
FontStyle("TanukiMagic麦克笔手绘", "TanukiMagic", "fonts/TanukiMagic.ttf")
)
}
FontStyle是一个data class
data class FontStyle(val name:String,val tag :String,val path:String,var inUse:Boolean =false)
用于记录字体信息,包括字体名称,tag标记,以及字体路径,字体的选取与否保存在SharedPreference当中
val string = PreferenceUtil.readString("fonts", "fontPath", "fonts/FounderBlack.ttf")
for (font in fonts) {
val radioButton = RadioButton(this@FontsActivity)
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
layoutParams.gravity = Gravity.CENTER
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
layoutParams.bottomMargin = 30
radioButton.setPadding(10, 10, 10, 10)
radioButton.layoutParams = layoutParams
radioButton.tag = font.tag
if (font.path == string) {
font.inUse = true
radioButton.isChecked = true
mCheckRadioButton=radioButton
}
radioButton.text = font.name
radioButton.maxLines = 1
radioButton.ellipsize = TextUtils.TruncateAt.END
radioButton.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, resources.displayMetrics)
CalligraphyUtils.applyFontToTextView(radioButton, Typeface.createFromAsset(assets, font.path))
binding.rg.addView(radioButton)
radioButton.setOnClickListener {
if (radioButton==mCheckRadioButton) {
return@setOnClickListener
}else{
mCheckRadioButton?.isChecked=false
radioButton.isChecked=true
mCheckRadioButton=radioButton
}
}
}
遍历字体集合动态生成RadioButton,加字体tag设置给radioButton,并将已选中的字体选中
通过
CalligraphyUtils.applyFontToTextView(radioButton, Typeface.createFromAsset(assets, font.path))
方法动态设置字体,然后我们就得到了上面的显示效果了
选中后点击确定
val tag = findViewById<RadioButton>(binding.rg.checkedRadioButtonId)?.tag
for (font in fonts) {
if (font.tag == tag) {
if (font.inUse) {
toastShort("字体正在使用当中...")
} else {
PreferenceUtil.writeString("fonts", "fontPath", font.path)
PreferenceUtil.writeString("fonts", "fontName", font.name)
PreferenceUtil.writeString("fonts", "fontTag", font.tag)
BaseApplication.finishAll()
startActivity(Intent(this@FontsActivity, LaunchActivity::class.java))
this@FontsActivity.overridePendingTransition(0, 0)
}
break
}
}
通过选中的radiobutton的tag来判断我们想要换的是什么字体,将它的路径,name tag都保存起来,然后调用application的finishAll()方法关闭所有的Activity,最后再次启动我们的LauncherActivity,我们的APP就重新启动并加载了新的字体了
效果对比
切换前
切换后