React NativeでMaterial SymbolsをIcon Componentとして使えるようにする
これをreact-native-vector-iconのようにつかいたい
超雑だと、フォントファイルを createIconSet()
で読み込ませればいい
Material Symbolsを使うのに困るポイント
- font fileがvariable fontになっている
-
createIconSet()
はvariable fontに未対応のため、propsとかでFILL軸とかwght軸を指定できない
やったこと
- variable fontからfilled/outlinedのfontをつくる
- codepointをglyphMapに変換
-
createIconSet()
して、componentをつくる
variable fontからfilled/outlinedのfontをつくる
これを参考にvariable fontでFILL軸をいじったフォントファイルをつくる
Sliceをインストールする
以下から好きなフォントをダウンロードする
自分はMaterialSymbolsRounded
をダウンロードしてた
ダウンロードしたfontをSliceにいれて、Fillの値をいじって、FilledとOutlinedのttfファイルをつくる
これでFilledとOutlinedのアイコンを出すためのフォントファイルが準備できた
memo
この工程、いらんかったかもしれん
記事書きながら見てたら、
MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf
とかいう outlinedぽいフォントあるやんけとなった
後述する .codepoint
のファイルも、同一っぽいから、この作業なくてよさそう
私はRoundedのアイコンを使いたかったから、MaterialSymbolsOutlinedとMaterialSymbolsRoundedだと見た目が違うはずなので、書いた手順が適切そうだった ☺️よかった
codepointをglyphMapに変換
createIconSet()
の引数に必要な glyphMap
を .codepoint
から作る
You can also use createIconSet() directly in your project. This returns your own custom font based on the glyphMap where the key is the icon name and the value is either a UTF-8 character or it's character code.
https://docs.expo.dev/guides/icons/#createiconset
.codepoint
は以下にあるもの
これをChatGPTにぶちこんで変換した
https://github.com/google/material-design-icons/blob/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.codepoints
のcodepointsファイルをjsonにしたいです
- 1行の内容をspaceで区切る
- 0番目をiconname、1番目をcodepointとします
- { [iconname]: [codepoint] } の形式で出力する
- codepointは、UTG-8文字に変換してください
出力されたスクリプトはこんなかんじ
import json
uploaded_file_path_utf8 = "/mnt/data/MaterialSymbolsRounded.codepoints"
output_file_path_utf8 = "/mnt/data/MaterialSymbolsRounded_glyphMap.json"
glyph_map_utf8 = {}
with open(uploaded_file_path_utf8, "r") as file:
for line in file:
parts = line.split() # 行をスペースで分割
if len(parts) == 2:
iconname, codepoint = parts
utf8_char = chr(int(codepoint, 16)) # コードポイントを16進数として解析し、UTF-8文字に変換
glyph_map_utf8[iconname] = utf8_char # 辞書に追加
with open(output_file_path_utf8, "w") as json_file:
json.dump(glyph_map_utf8, json_file, ensure_ascii=False, indent=2)
こんな感じのJSONが得られるはずで、これをglyphMapとして使う
createIconSet() して、componentをつくる
あとは、最初に分割したfontとglyphMapを createIconSet()
に食わせて、それを元にしてcomponentつくってexportすれば終わり
ほぼChatGPTにかかせた
import React, { FC, memo } from "react";
import createIconSet from "@expo/vector-icons/createIconSet";
import glyphMap from "@/assets/fonts/MaterialSymbolsRounded/GlyphMap.json";
import {
IconButtonProps,
IconProps,
} from "@expo/vector-icons/build/createIconSet";
// Filled and Outlined Icon definitions
const FilledIcon = createIconSet(
glyphMap,
"MaterialSymbolsRounded-Regular-Filled", // SliceのName Editorで指定した名前を入れる
require("@/assets/fonts/MaterialSymbolsRounded/MaterialSymbolsRounded-Regular-Filled.ttf")
);
const OutlinedIcon = createIconSet(
glyphMap,
"MaterialSymbolsRounded-Regular-Outlined", // SliceのName Editorで指定した名前を入れる
require("@/assets/fonts/MaterialSymbolsRounded/MaterialSymbolsRounded-Regular-Outlined.ttf")
);
// glyphMapは共通なので、suffixを指定して表示等を分岐させる
type FilledIconName = `${keyof typeof glyphMap}`;
type OutlinedIconName = `${keyof typeof glyphMap}-outlined`;
type FilledIconProps = IconProps<FilledIconName>;
type OutlinedIconProps = IconProps<OutlinedIconName>;
// Helper function to check if the icon is outlined
const isOutlinedIcon = (
props: FilledIconProps | OutlinedIconProps
): props is OutlinedIconProps => {
return props.name.endsWith("-outlined");
};
// MaterialSymbolsIcon definition
const MaterialSymbolsIconComponent: FC<FilledIconProps | OutlinedIconProps> = (
props
) => {
if (isOutlinedIcon(props)) {
return (
<OutlinedIcon
{...props}
name={props.name.replace("-outlined", "") as unknown as FilledIconName}
/>
);
} else {
return <FilledIcon {...props} />;
}
};
// Button props for Filled and Outlined Icons
type FilledIconButtonProps = IconButtonProps<FilledIconName>;
type OutlinedIconButtonProps = IconButtonProps<OutlinedIconName>;
// Helper function to check if the button is outlined
const isOutlinedIconButton = (
props: FilledIconButtonProps | OutlinedIconButtonProps
): props is OutlinedIconButtonProps => {
return props.name.endsWith("-outlined");
};
const MaterialSymbolsIconButton: FC<
FilledIconButtonProps | OutlinedIconButtonProps
> = memo((props) => {
if (isOutlinedIconButton(props)) {
return (
<OutlinedIcon.Button
{...props}
name={props.name.replace("-outlined", "") as unknown as FilledIconName}
/>
);
} else {
return <FilledIcon.Button {...props} />;
}
});
type MaterialSymbolsIconType = typeof MaterialSymbolsIconComponent & {
Button: typeof MaterialSymbolsIconButton;
};
type MaterialSymbolsIconType = typeof MaterialSymbolsIconComponent & {
Button: typeof MaterialSymbolsIconButton;
};
const MaterialSymbolsIcon = memo(
MaterialSymbolsIconComponent
) as unknown as MaterialSymbolsIconType;
MaterialSymbolsIcon.Button = MaterialSymbolsIconButton;
export { MaterialSymbolsIcon };
こんな感じで使えるはず
const Sample = () => {
return (
<>
<MaterialSymbolsIcon
size={24}
color={"gray"}
name={"more_vert"}
/>
<MaterialSymbolsIcon.Button
size={24}
color={"gray"}
name={"more_vert"}
/>
</>
)
}