使用react-native构建的iOS/Android双端APP,经过WebView加载本地页面,须要根据服务器提供的字体列表实现下载和动态加载。javascript
有些字体手机操做系统已经提供了,能够不须要下载和加载。css
UIFont.familyNames
提供了全部系统自带字体的familyNames,直接将结果返回给RN处理便可。java
// // SHMFontsModule.h // shimo // // Created by Rex Rao on 2018/1/9. // Copyright © 2018年 shimo.im. All rights reserved. // #import <Foundation/Foundation.h> #import <React/RCTBridgeModule.h> @interface SHMFontsModule : NSObject <RCTBridgeModule> @end
// // SHMFontsModule.m // shimo // // Created by Rex Rao on 2018/1/9. // Copyright © 2018年 shimo.im. All rights reserved. // #import "SHMFontsModule.h" #import <UIKit/UIKit.h> @implementation SHMFontsModule RCT_EXPORT_MODULE(SHMFonts); RCT_REMAP_METHOD(fontFamilyNames, resolver : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { resolve(UIFont.familyNames); } @end
安卓系统没有直接提供接口返回系统字体列表,通过调研和阅读源代码,发现有一个类中的私有静态变量存储了字体信息,反射便可获得。但由于Android版本缘由,低版本系统代码不一样没法经过此方法获得。继续对这个静态变量顺藤摸瓜,发现Android经过解析字体xml文件来设置此变量的值,根据系统不一样,字体配置xml文件的位置和结构也有所不一样。react
Android源码中有个FontListParser类用来解析此字体配置文件,咱们能够参考此类完成本身的parser,分两种配置路径和结构获取系统的Font Families,而后传给RN处理。android
package chuxin.shimo.shimowendang.fonts; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; /** * Created by sohobloo on 2018/1/17. */ /** * Parser for font config files. * */ public class FontListParser { public static List<String> parse(InputStream in) throws XmlPullParserException, IOException { List<String> familyNames = new ArrayList<String>(); try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parser.nextTag(); parser.require(XmlPullParser.START_TAG, null, "familyset"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String tag = parser.getName(); switch (tag) { case "family": { String name = parser.getAttributeValue(null, "name"); if (name != null && !name.isEmpty()) { familyNames.add(name); skip(parser); } else { while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } tag = parser.getName(); if (tag.equals("nameset")) { while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } tag = parser.getName(); if (tag.equals("name")) { name = parser.nextText(); if (name != null && !name.isEmpty()) { familyNames.add(name); } } else { skip(parser); } } } else { skip(parser); } } } break; } case "alias": { String name = parser.getAttributeValue(null, "name"); if (name != null && !name.isEmpty()) { familyNames.add(name); } skip(parser); break; } default: skip(parser); break; } } } finally { in.close(); } return familyNames; } private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { int depth = 1; while (depth > 0) { switch (parser.next()) { case XmlPullParser.START_TAG: depth++; break; case XmlPullParser.END_TAG: depth--; break; default: break; } } } }
package chuxin.shimo.shimowendang.fonts; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableArray; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.List; /** * Created by sohobloo on 2018/1/9. */ public class FontsModule extends ReactContextBaseJavaModule { private static final String MODULE_NAME = "SHMFonts"; private static final String SYSTEM_CONFIG_LOCATION = "/system/etc/"; private static final String FONTS_CONFIG = "fonts.xml"; private static final String SYSTEM_FONTS_CONFIG = "system_fonts.xml"; FontsModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return MODULE_NAME; } @ReactMethod public void fontFamilyNames(Promise promise) { WritableArray familyNames = null; File systemFontConfigLocation = new File(SYSTEM_CONFIG_LOCATION); File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG); if (!configFilename.exists()) { configFilename = new File(systemFontConfigLocation, SYSTEM_FONTS_CONFIG); } if (configFilename.exists()) { try { FileInputStream fontsIn = new FileInputStream(configFilename); List<String> familyNameList = FontListParser.parse(fontsIn); familyNames = Arguments.fromList(familyNameList); } catch (XmlPullParserException | IOException e) { e.printStackTrace(); } } promise.resolve(familyNames); } }
package chuxin.shimo.shimowendang.fonts; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Created by sohobloo on 2018/1/9. */ public class FontsPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new FontsModule(reactContext)); return modules; } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
RN端经过对比项目须要加载的字体和调用原生iOS/Android模块获取到的系统字体列表交叉对比便可知道哪些字体系统以及存在了。对比时注意一下FamilyName的大小写/空格以及「-」链接符。ios
下载不是本topic的主题,就不细讲了。下载到App目录中便可,下载前判断一下是否已经下载云云。
因为iOS的WKWebView没有读取Documents目录权限致使真机没法加载字体资源,根据调研须要拷贝字体文件到tmp/www/fonts目录中。参考git
字体能够经过CSS的font-face
来加载,这里就简单了,经过insertRule传入本地字体的familyName和path便可动态加载github
function loadFontFace (name, path) { const sheet = document.styleSheets[0] sheet.insertRule(`@font-face {font-family: '${name}'; src:url('${path}');}`, sheet.cssRules.length || 0) }
经过调用此js函数注入webview便可实现动态加载字体,无需刷新更无需重启APP。:-pweb
博客园的MarkDown没有预览功能吗?难道大神们写文章都这么牛X了,排版了然于心?react-native