mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add support for system text scale factor. (#4124)
Adds support for system text scale factor, including hooks for Android system settings changes. iOS hooks will be added in another PR.
This commit is contained in:
parent
b930f107c3
commit
b2a7f4bf8f
@ -32,6 +32,11 @@ void _updateLocale(String languageCode, String countryCode) {
|
||||
_invoke(window.onLocaleChanged, window._onLocaleChangedZone);
|
||||
}
|
||||
|
||||
void _updateTextScaleFactor(double textScaleFactor) {
|
||||
window._textScaleFactor = textScaleFactor;
|
||||
_invoke(window.onTextScaleFactorChanged, window._onTextScaleFactorChangedZone);
|
||||
}
|
||||
|
||||
void _updateSemanticsEnabled(bool enabled) {
|
||||
window._semanticsEnabled = enabled;
|
||||
_invoke(window.onSemanticsEnabledChanged, window._onSemanticsEnabledChangedZone);
|
||||
|
||||
@ -254,6 +254,38 @@ class Window {
|
||||
_onLocaleChangedZone = Zone.current;
|
||||
}
|
||||
|
||||
/// The system-reported text scale.
|
||||
///
|
||||
/// This establishes the text scaling factor to use when rendering text,
|
||||
/// according to the user's platform preferences.
|
||||
///
|
||||
/// The [onTextScaleFactorChanged] callback is called whenever this value
|
||||
/// changes.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
|
||||
/// observe when this value changes.
|
||||
double get textScaleFactor => _textScaleFactor;
|
||||
double _textScaleFactor = 1.0;
|
||||
|
||||
/// A callback that is invoked whenever [textScaleFactor] changes value.
|
||||
///
|
||||
/// The framework invokes this callback in the same zone in which the
|
||||
/// callback was set.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
|
||||
/// observe when this callback is invoked.
|
||||
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
|
||||
VoidCallback _onTextScaleFactorChanged;
|
||||
Zone _onTextScaleFactorChangedZone;
|
||||
set onTextScaleFactorChanged(VoidCallback callback) {
|
||||
_onTextScaleFactorChanged = callback;
|
||||
_onTextScaleFactorChangedZone = Zone.current;
|
||||
}
|
||||
|
||||
/// A callback that is invoked to notify the application that it is an
|
||||
/// appropriate time to provide a scene using the [SceneBuilder] API and the
|
||||
/// [render] method. When possible, this is driven by the hardware VSync
|
||||
|
||||
@ -163,6 +163,18 @@ void Window::UpdateLocale(const std::string& language_code,
|
||||
});
|
||||
}
|
||||
|
||||
void Window::UpdateTextScaleFactor(double text_scale_factor) {
|
||||
tonic::DartState* dart_state = library_.dart_state().get();
|
||||
if (!dart_state)
|
||||
return;
|
||||
tonic::DartState::Scope scope(dart_state);
|
||||
|
||||
DartInvokeField(library_.value(), "_updateTextScaleFactor",
|
||||
{
|
||||
ToDart(static_cast<double>(text_scale_factor)),
|
||||
});
|
||||
}
|
||||
|
||||
void Window::UpdateSemanticsEnabled(bool enabled) {
|
||||
tonic::DartState* dart_state = library_.dart_state().get();
|
||||
if (!dart_state)
|
||||
|
||||
@ -45,6 +45,7 @@ class Window {
|
||||
void UpdateWindowMetrics(const ViewportMetrics& metrics);
|
||||
void UpdateLocale(const std::string& language_code,
|
||||
const std::string& country_code);
|
||||
void UpdateTextScaleFactor(double text_scale_factor);
|
||||
void UpdateSemanticsEnabled(bool enabled);
|
||||
void DispatchPlatformMessage(fxl::RefPtr<PlatformMessage> message);
|
||||
void DispatchPointerDataPacket(const PointerDataPacket& packet);
|
||||
|
||||
@ -66,6 +66,13 @@ void RuntimeController::SetLocale(const std::string& language_code,
|
||||
GetWindow()->UpdateLocale(language_code_, country_code_);
|
||||
}
|
||||
|
||||
void RuntimeController::SetTextScaleFactor(double text_scale_factor) {
|
||||
if (text_scale_factor_ == text_scale_factor)
|
||||
return;
|
||||
text_scale_factor_ = text_scale_factor;
|
||||
GetWindow()->UpdateTextScaleFactor(text_scale_factor_);
|
||||
}
|
||||
|
||||
void RuntimeController::SetSemanticsEnabled(bool enabled) {
|
||||
if (semantics_enabled_ == enabled)
|
||||
return;
|
||||
|
||||
@ -35,6 +35,7 @@ class RuntimeController : public WindowClient, public IsolateClient {
|
||||
void SetViewportMetrics(const ViewportMetrics& metrics);
|
||||
void SetLocale(const std::string& language_code,
|
||||
const std::string& country_code);
|
||||
void SetTextScaleFactor(double textScaleFactor);
|
||||
void SetSemanticsEnabled(bool enabled);
|
||||
|
||||
void BeginFrame(fxl::TimePoint frame_time);
|
||||
@ -66,6 +67,7 @@ class RuntimeController : public WindowClient, public IsolateClient {
|
||||
RuntimeDelegate* client_;
|
||||
std::string language_code_;
|
||||
std::string country_code_;
|
||||
double text_scale_factor_ = 1.0;
|
||||
bool semantics_enabled_ = false;
|
||||
std::unique_ptr<DartController> dart_controller_;
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ constexpr char kAssetChannel[] = "flutter/assets";
|
||||
constexpr char kLifecycleChannel[] = "flutter/lifecycle";
|
||||
constexpr char kNavigationChannel[] = "flutter/navigation";
|
||||
constexpr char kLocalizationChannel[] = "flutter/localization";
|
||||
constexpr char kSystemChannel[] = "flutter/system";
|
||||
|
||||
bool PathExists(const std::string& path) {
|
||||
return access(path.c_str(), R_OK) == 0;
|
||||
@ -74,6 +75,7 @@ Engine::Engine(PlatformView* platform_view)
|
||||
platform_view->GetVsyncWaiter(),
|
||||
this)),
|
||||
load_script_error_(tonic::kNoError),
|
||||
text_scale_factor_(1.0),
|
||||
activity_running_(false),
|
||||
have_surface_(false),
|
||||
weak_factory_(this) {}
|
||||
@ -312,7 +314,12 @@ void Engine::DispatchPlatformMessage(
|
||||
if (HandleLifecyclePlatformMessage(message.get()))
|
||||
return;
|
||||
} else if (message->channel() == kLocalizationChannel) {
|
||||
if (HandleLocalizationPlatformMessage(std::move(message)))
|
||||
if (HandleLocalizationPlatformMessage(message.get()))
|
||||
return;
|
||||
} else if (message->channel() == kSystemChannel) {
|
||||
// This only handles textScaleFactor changes: other system messages are
|
||||
// handled by DispatchPlatformMessage below.
|
||||
if (HandleSystemPlatformMessage(message.get()))
|
||||
return;
|
||||
}
|
||||
|
||||
@ -367,7 +374,7 @@ bool Engine::HandleNavigationPlatformMessage(
|
||||
}
|
||||
|
||||
bool Engine::HandleLocalizationPlatformMessage(
|
||||
fxl::RefPtr<blink::PlatformMessage> message) {
|
||||
blink::PlatformMessage* message) {
|
||||
const auto& data = message->data();
|
||||
|
||||
rapidjson::Document document;
|
||||
@ -396,6 +403,37 @@ bool Engine::HandleLocalizationPlatformMessage(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Engine::HandleSystemPlatformMessage(blink::PlatformMessage* message) {
|
||||
const auto& data = message->data();
|
||||
rapidjson::Document document;
|
||||
document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
if (document.HasParseError() || !document.IsObject())
|
||||
return false;
|
||||
|
||||
auto root = document.GetObject();
|
||||
auto type = root.FindMember("type");
|
||||
if (type == root.MemberEnd() || type->value != "systemSettings")
|
||||
return false;
|
||||
|
||||
// This only handles textScaleFactor changes: other system messages
|
||||
// are handled by DispatchPlatformMessage.
|
||||
auto text_scale_factor = root.FindMember("textScaleFactor");
|
||||
if (text_scale_factor == root.MemberEnd() ||
|
||||
!text_scale_factor->value.IsDouble()) {
|
||||
return false;
|
||||
}
|
||||
text_scale_factor_ = text_scale_factor->value.GetDouble();
|
||||
if (runtime_) {
|
||||
runtime_->SetTextScaleFactor(text_scale_factor_);
|
||||
if (have_surface_)
|
||||
ScheduleFrame();
|
||||
}
|
||||
// If the only members were "type" and "textScaleFactor", then we're done.
|
||||
// If there are more members, then we need to send it on to other handlers.
|
||||
return root.MemberCount() == 2;
|
||||
}
|
||||
|
||||
void Engine::DispatchPointerDataPacket(const PointerDataPacket& packet) {
|
||||
if (runtime_)
|
||||
runtime_->DispatchPointerDataPacket(packet);
|
||||
@ -445,6 +483,7 @@ void Engine::ConfigureRuntime(const std::string& script_uri,
|
||||
default_isolate_snapshot_instr, platform_kernel);
|
||||
runtime_->SetViewportMetrics(viewport_metrics_);
|
||||
runtime_->SetLocale(language_code_, country_code_);
|
||||
runtime_->SetTextScaleFactor(text_scale_factor_);
|
||||
runtime_->SetSemanticsEnabled(semantics_enabled_);
|
||||
}
|
||||
|
||||
|
||||
@ -92,8 +92,8 @@ class Engine : public blink::RuntimeDelegate {
|
||||
bool HandleLifecyclePlatformMessage(blink::PlatformMessage* message);
|
||||
bool HandleNavigationPlatformMessage(
|
||||
fxl::RefPtr<blink::PlatformMessage> message);
|
||||
bool HandleLocalizationPlatformMessage(
|
||||
fxl::RefPtr<blink::PlatformMessage> message);
|
||||
bool HandleLocalizationPlatformMessage(blink::PlatformMessage* message);
|
||||
bool HandleSystemPlatformMessage(blink::PlatformMessage* message);
|
||||
|
||||
void HandleAssetPlatformMessage(fxl::RefPtr<blink::PlatformMessage> message);
|
||||
bool GetAssetAsBuffer(const std::string& name, std::vector<uint8_t>* data);
|
||||
@ -106,6 +106,7 @@ class Engine : public blink::RuntimeDelegate {
|
||||
blink::ViewportMetrics viewport_metrics_;
|
||||
std::string language_code_;
|
||||
std::string country_code_;
|
||||
double text_scale_factor_;
|
||||
bool semantics_enabled_ = false;
|
||||
// TODO(abarth): Unify these two behind a common interface.
|
||||
fxl::RefPtr<blink::ZipAssetStore> asset_store_;
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true" />
|
||||
|
||||
<application android:label="Flutter Shell" android:name="FlutterApplication" android:debuggable="true">
|
||||
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
|
||||
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="standard"
|
||||
android:name="FlutterActivity"
|
||||
|
||||
@ -181,6 +181,7 @@ public class FlutterView extends SurfaceView
|
||||
mTextInputPlugin = new TextInputPlugin(this);
|
||||
|
||||
setLocale(getResources().getConfiguration().locale);
|
||||
setTextScaleFactor(getResources().getConfiguration().fontScale);
|
||||
|
||||
if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||
mDiscoveryReceiver = new DiscoveryReceiver();
|
||||
@ -278,6 +279,13 @@ public class FlutterView extends SurfaceView
|
||||
mFlutterNavigationChannel.invokeMethod("popRoute", null);
|
||||
}
|
||||
|
||||
private void setTextScaleFactor(float textScaleFactor) {
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("type", "systemSettings");
|
||||
message.put("textScaleFactor", textScaleFactor);
|
||||
mFlutterSystemChannel.send(message);
|
||||
}
|
||||
|
||||
private void setLocale(Locale locale) {
|
||||
mFlutterLocalizationChannel.invokeMethod("setLocale",
|
||||
Arrays.asList(locale.getLanguage(), locale.getCountry()));
|
||||
@ -287,6 +295,7 @@ public class FlutterView extends SurfaceView
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
setLocale(newConfig.locale);
|
||||
setTextScaleFactor(newConfig.fontScale);
|
||||
}
|
||||
|
||||
float getDevicePixelRatio() {
|
||||
|
||||
@ -34,6 +34,7 @@ void main() {
|
||||
VoidCallback originalOnSemanticsEnabledChanged;
|
||||
SemanticsActionCallback originalOnSemanticsAction;
|
||||
PlatformMessageCallback originalOnPlatformMessage;
|
||||
VoidCallback originalOnTextScaleFactorChanged;
|
||||
|
||||
setUp(() {
|
||||
originalOnMetricsChanged = window.onMetricsChanged;
|
||||
@ -44,6 +45,7 @@ void main() {
|
||||
originalOnSemanticsEnabledChanged = window.onSemanticsEnabledChanged;
|
||||
originalOnSemanticsAction = window.onSemanticsAction;
|
||||
originalOnPlatformMessage = window.onPlatformMessage;
|
||||
originalOnTextScaleFactorChanged = window.onTextScaleFactorChanged;
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@ -55,55 +57,65 @@ void main() {
|
||||
window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged;
|
||||
window.onSemanticsAction = originalOnSemanticsAction;
|
||||
window.onPlatformMessage = originalOnPlatformMessage;
|
||||
window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged;
|
||||
});
|
||||
|
||||
test('onMetricsChanged preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
double devicePixelRatio;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onMetricsChanged = () {
|
||||
runZone = Zone.current;
|
||||
devicePixelRatio = window.devicePixelRatio;
|
||||
};
|
||||
});
|
||||
|
||||
window.onMetricsChanged();
|
||||
_updateWindowMetrics(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
_updateWindowMetrics(0.1234, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
expect(devicePixelRatio, equals(0.1234));
|
||||
});
|
||||
|
||||
test('onLocaleChanged preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
Locale locale;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onLocaleChanged = () {
|
||||
runZone = Zone.current;
|
||||
locale = window.locale;
|
||||
};
|
||||
});
|
||||
|
||||
_updateLocale('en', 'US');
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
expect(locale, equals(const Locale('en', 'US')));
|
||||
});
|
||||
|
||||
test('onBeginFrame preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
Duration start;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onBeginFrame = (_) {
|
||||
window.onBeginFrame = (Duration value) {
|
||||
runZone = Zone.current;
|
||||
start = value;
|
||||
};
|
||||
});
|
||||
|
||||
_beginFrame(0);
|
||||
_beginFrame(1234);
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
expect(start, equals(const Duration(microseconds: 1234)));
|
||||
});
|
||||
|
||||
test('onDrawFrame preserves callback zone', () {
|
||||
@ -125,65 +137,99 @@ void main() {
|
||||
test('onPointerDataPacket preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
PointerDataPacket data;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onPointerDataPacket = (_) {
|
||||
window.onPointerDataPacket = (PointerDataPacket value) {
|
||||
runZone = Zone.current;
|
||||
data = value;
|
||||
};
|
||||
});
|
||||
|
||||
_dispatchPointerDataPacket(new ByteData.view(new Uint8List(0).buffer));
|
||||
final ByteData testData = new ByteData.view(new Uint8List(0).buffer);
|
||||
_dispatchPointerDataPacket(testData);
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
expect(data.data, equals(_unpackPointerDataPacket(testData).data));
|
||||
});
|
||||
|
||||
test('onSemanticsEnabledChanged preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
bool enabled;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onSemanticsEnabledChanged = () {
|
||||
runZone = Zone.current;
|
||||
enabled = window.semanticsEnabled;
|
||||
};
|
||||
});
|
||||
|
||||
_updateSemanticsEnabled(window._semanticsEnabled);
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
expect(enabled, isNotNull);
|
||||
expect(enabled, equals(window._semanticsEnabled));
|
||||
});
|
||||
|
||||
test('onSemanticsAction preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
int action;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onSemanticsAction = (_, __) {
|
||||
window.onSemanticsAction = (int value, _) {
|
||||
runZone = Zone.current;
|
||||
action = value;
|
||||
};
|
||||
});
|
||||
|
||||
_dispatchSemanticsAction(0, 0);
|
||||
_dispatchSemanticsAction(1234, 0);
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
expect(action, equals(1234));
|
||||
});
|
||||
|
||||
test('onPlatformMessage preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
String name;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onPlatformMessage = (_, __, ___) {
|
||||
window.onPlatformMessage = (String value, _, __) {
|
||||
runZone = Zone.current;
|
||||
name = value;
|
||||
};
|
||||
});
|
||||
|
||||
_dispatchPlatformMessage(null, null, null);
|
||||
_dispatchPlatformMessage('testName', null, null);
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
expect(name, equals('testName'));
|
||||
});
|
||||
|
||||
test('onTextScaleFactorChanged preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
double textScaleFactor;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onTextScaleFactorChanged = () {
|
||||
runZone = Zone.current;
|
||||
textScaleFactor = window.textScaleFactor;
|
||||
};
|
||||
});
|
||||
|
||||
window.onTextScaleFactorChanged();
|
||||
_updateTextScaleFactor(0.5);
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
expect(textScaleFactor, equals(0.5));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user