Android新字体安装技术咨询:下拉选单选择TTF字体的代码指导
Got it, let's walk through how to build this font selection and installation feature step by step. I’ll assume you’re using Flutter since you mentioned an assets/font folder—if you’re working with another framework (like Android native or iOS), just let me know and I can adjust the code examples!
First, let’s set up the dropdown with your font list. We’ll start by defining the list of font filenames (match what’s in your assets/font directory), then render a dropdown that lets users pick a font.
// Define your font list (update these to match your actual TTF files) final List<String> fontList = [ 'Roboto-Regular.ttf', 'OpenSans-Bold.ttf', 'Montserrat-Italic.ttf' ]; String? selectedFont; // Add this to your widget's build method DropdownButton<String>( value: selectedFont, hint: const Text('Pick a font to install'), onChanged: (String? newFont) { setState(() { selectedFont = newFont; // Trigger installation as soon as a font is selected if (newFont != null) handleFontInstallation(newFont); }); }, items: fontList.map((String fontName) { return DropdownMenuItem<String>( value: fontName, // Show a clean name (without the .ttf extension) to users child: Text(fontName.replaceAll('.ttf', '')), ); }).toList(), )
There are two main scenarios here: using the font only in your app, or installing it to the device’s system so other apps can use it too. Let’s cover both:
Option A: Load Font for App-Only Use (No System Installation)
If you just want the selected font to work within your app, you don’t need to install it system-wide. Use Flutter’s FontLoader to load the font on the fly:
Future<void> handleFontInstallation(String fontFileName) async { // Extract the font family name (remove .ttf) final fontFamilyName = fontFileName.replaceAll('.ttf', ''); try { // Load the font from assets final fontLoader = FontLoader(fontFamilyName) ..addFont(rootBundle.load('assets/font/$fontFileName')) ..load(); await fontLoader; // After loading, you can apply this font to your app's theme or specific widgets // Example: Update the app theme to use the new font setState(() { Theme.of(context).copyWith( textTheme: TextTheme( bodyLarge: TextStyle(fontFamily: fontFamilyName), titleLarge: TextStyle(fontFamily: fontFamilyName), ), ); }); // Show a success message to the user ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('$fontFamilyName loaded successfully!')), ); } catch (e) { // Handle errors (e.g., file not found) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to load font: ${e.toString()}')), ); } }
Option B: Install Font to Device System
If you want the font to be available across the entire device (for other apps too), you’ll need to write native code for Android and iOS, then call it from Flutter using a MethodChannel.
First, Set Up the MethodChannel in Flutter
final MethodChannel _fontInstallChannel = MethodChannel('com.yourapp/font_installer'); Future<void> handleFontInstallation(String fontFileName) async { try { await _fontInstallChannel.invokeMethod( 'installFont', {'fontFileName': fontFileName}, ); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Font installed successfully!')), ); } on PlatformException catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Installation failed: ${e.message}')), ); } }
Android Native Code (MainActivity.kt)
Add this to your Android MainActivity to handle copying the font to the system’s font directory and triggering a media scan:
import android.content.Intent import android.net.Uri import android.os.Environment import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import java.io.File import java.io.FileOutputStream import java.io.IOException class MainActivity : FlutterActivity() { private val CHANNEL = "com.yourapp/font_installer" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "installFont") { val fontFileName = call.argument<String>("fontFileName") if (fontFileName != null) { try { installFontToSystem(fontFileName) result.success(null) } catch (e: IOException) { result.error("INSTALL_FAILED", e.message, null) } } else { result.error("INVALID_ARG", "Font filename is null", null) } } else { result.notImplemented() } } } @Throws(IOException::class) private fun installFontToSystem(fontFileName: String) { // Copy font from assets to external storage's font directory val inputStream = assets.open("font/$fontFileName") val fontDir = getExternalFilesDir(Environment.DIRECTORY_FONTS) val fontFile = File(fontDir, fontFileName) FileOutputStream(fontFile).use { outputStream -> val buffer = ByteArray(1024) var length: Int while (inputStream.read(buffer).also { length = it } > 0) { outputStream.write(buffer, 0, length) } } // Tell the system to scan the new font file val scanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) scanIntent.data = Uri.fromFile(fontFile) sendBroadcast(scanIntent) } }
iOS Native Code (AppDelegate.swift)
Add this to your iOS AppDelegate to register the font with the system using CoreText:
import UIKit import Flutter import CoreText @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { private let CHANNEL = "com.yourapp/font_installer" override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let channel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger) channel.setMethodCallHandler { [weak self] call, result in guard call.method == "installFont" else { result.notImplemented() return } guard let args = call.arguments as? [String: String], let fontFileName = args["fontFileName"] else { result.error("INVALID_ARG", "Font filename is null", nil) return } self?.installFontToSystem(fontFileName) { success, error in if success { result.success(nil) } else { result.error("INSTALL_FAILED", error?.localizedDescription, nil) } } } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func installFontToSystem(_ fontFileName: String, completion: @escaping (Bool, Error?) -> Void) { guard let fontPath = Bundle.main.path(forResource: "font/\(fontFileName)", ofType: nil) else { completion(false, NSError(domain: "FontNotFound", code: 0, userInfo: [NSLocalizedDescriptionKey: "Font file not found"])) return } let fontURL = URL(fileURLWithPath: fontPath) do { let fontData = try Data(contentsOf: fontURL) guard let provider = CGDataProvider(data: fontData as CFData) else { completion(false, NSError(domain: "FontLoadFailed", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to create font provider"])) return } guard let font = CGFont(provider) else { completion(false, NSError(domain: "FontLoadFailed", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to load font"])) return } var error: Unmanaged<CFError>? if CTFontManagerRegisterGraphicsFont(font, &error) { completion(true, nil) } else { completion(false, error?.takeRetainedValue()) } } catch { completion(false, error) } } }
Make sure your font assets are declared in pubspec.yaml so Flutter can access them:
flutter: assets: - assets/font/
- Permissions: For Android versions older than 13, you’ll need to request the
WRITE_EXTERNAL_STORAGEpermission. On Android 13+, theDIRECTORY_FONTSis accessible without extra permissions. - Error Handling: Always catch exceptions (like missing files, permission denied) and show clear messages to users.
- Testing: Test on real devices—emulators might behave differently when it comes to system font installation.
内容的提问来源于stack exchange,提问作者Tony

