React-Native WebView動態加載字體


背景

使用react-native構建的iOS/Android雙端APP,通過WebView加載本地頁面,需要根據服務器提供的字體列表實現下載和動態加載。

本地字體檢查

有些字體手機操作系統已經提供了,可以不需要下載和加載。

iOS

UIFont.familyNames提供了所有系統自帶字體的familyNames,直接將結果返回給RN處理即可。

SHMFontsModul.h

//
//  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

//
//  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版本原因,低版本系統代碼不同無法通過此方法得到。繼續對這個靜態變量順藤摸瓜,發現Android通過解析字體xml文件來設置此變量的值,根據系統不同,字體配置xml文件的位置和結構也有所不同。

  • Android 5.1及以下
    • 路徑:/system/etc/system_fonts.xml
    • 結構樣例請直接查看源文件
  • Android 5.1以上
    • 路徑:/system/etc/fonts.xml
    • 結構樣例請直接查看源文件

Android源碼中有個FontListParser類用來解析此字體配置文件,我們可以參考此類完成自己的parser,分兩種配置路徑和結構獲取系統的Font Families,然后傳給RN處理。

FontListParser.java

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;
            }
        }
    }
}

FontsModule.java

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);

    }
}

FontsPackage.java

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

RN端通過對比項目需要加載的字體和調用原生iOS/Android模塊獲取到的系統字體列表交叉對比即可知道哪些字體系統以及存在了。對比時注意一下FamilyName的大小寫/空格以及「-」連接符。

下載字體

下載不是本topic的主題,就不細講了。下載到App目錄中即可,下載前判斷一下是否已經下載雲雲。
由於iOS的WKWebView沒有讀取Documents目錄權限導致真機無法加載字體資源,根據調研需要拷貝字體文件到tmp/www/fonts目錄中。參考

WebView動態加載字體

字體可以通過CSS的font-face來加載,這里就簡單了,通過insertRule傳入本地字體的familyName和path即可動態加載

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。:-p

博客園的MarkDown沒有預覽功能嗎?難道大神們寫文章都這么牛X了,排版了然於心?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM