自Android P发布以来,陆陆续续的有用户向我反映Android P下输入法存在导航栏变黑的问题,情况如下所示。
于是我抽时间研究了一下这个问题。
经过一番搜索,我在Simple Keyboard下找到了解决方案,其代码大致如下:
private int mOriginalNavBarColor = 0;
private int mOriginalNavBarFlags = 0;
......
private void setNavigationBarColor() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
......
final Window window = getWindow().getWindow();
if (window == null) {
return;
}
mOriginalNavBarColor = window.getNavigationBarColor();
window.setNavigationBarColor(keyboardColor);
final View view = window.getDecorView();
mOriginalNavBarFlags = view.getSystemUiVisibility();
if (ResourceUtils.isBrightColor(keyboardColor)) {
view.setSystemUiVisibility(mOriginalNavBarFlags | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
} else {
view.setSystemUiVisibility(mOriginalNavBarFlags & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
}
}
private void clearNavigationBarColor() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
final Window window = getWindow().getWindow();
if (window == null) {
return;
}
window.setNavigationBarColor(mOriginalNavBarColor);
final View view = window.getDecorView();
view.setSystemUiVisibility(mOriginalNavBarFlags);
}
}
具体的原理,我也不班门弄斧了,想要深入了解的同学请自行百度(代码中的ResourceUtils.isBrightColor实现,我在后面的C#代码中给出)。而因为我使用Xamarin开发的岁寒输入法,所以不能简单地Ctrl+V、Ctrl+C,需要将其转写成C#代码,说白了,就是抄。但这一抄,抄出了大问题。
Xamarin.Android会对Android的原生API进行二次封装,一般而言,get/set方法会转换成属性,flag值会封装成枚举型。
在这个场景下,具体就是:
View.getSystemUiVisibility()/View.setSystemUiVisibility(int) =>StatusBarVisibility View.SystemUiVisibility {get;set;}
其中StatusBarVisibility就是setSystemUiVisibility所能接受的flag值的封装。然而这里出现了错误!
setSystemUiVisibility方法所能接受flag值如下图:
而StatusBarVisibility的内容是这样的:
显然,这两者是对不上的。
经过研究,我发现有另一个枚举类型——SystemUiFlags,他的内容是这样的:
显而易见,SystemUiFlags正是对setSystemUiVisibility方法所能接受的flag值的正确封装。可是为什么 View.SystemUiVisibility的类型会是StatusBarVisibility呢?
我在Xamarin.Android的GitHub项目的下发现了一些相关的issue,才确认,这确实是Xamarin.Android的的封装错误,而且年代久远,因为无法修复。这里有bugzilla上的bug反馈记录。
从bug记录中的官方回复可知,如果对这个问题进行更正,会破坏Xamarin.Android的二进制接口(ABI)。
官方如此解释这种破坏的原因:
所以这个封装错误将不会得到修正,只能将错就错。
如此一来,难道这个接口就不能用了吗?倒也不是。
官方有云:
The fact that the type is an enum is *irrelevant*. The fact that values of the type can be cast to other types is irrelevant. What matters are the actual types, as contained in the assembly: System.StringComparison != System.TypeCode. (Note in particular that both StringComparison and TypeCode are enumeration types with
intas the underlying enum type.)
简单而言就是,枚举型本质上还是int型,对其进行强制类型转换不会改变其本质。所以,解决方案就是在适当的时候使用强制类型转换来解决问题,虽然代码会因此看上去有点丑。
int originalNavBarColor;
int originalNavBarFlags;
private void setNavigationBarColor() {
if (Build.VERSION.SdkInt >= BuildVersionCodes.P) {
Window window = this.Window.Window;
if (window == null) return;
originalNavBarColor = window.NavigationBarColor;
window.SetNavigationBarColor(newColor);
var view = window.DecorView;
originalNavBarFlags = (int)view.SystemUiVisibility;
if (isBrightColor(color)) {
view.SystemUiVisibility = (StatusBarVisibility)(originalNavBarFlags | (int)SystemUiFlags.LightNavigationBar);
} else {
view.SystemUiVisibility = (StatusBarVisibility)(originalNavBarFlags & ~(int)SystemUiFlags.LightNavigationBar);
}
}
}
private void clearNavigationBarColor() {
if (Build.VERSION.SdkInt >= BuildVersionCodes.P) {
Window window = this.Window.Window;
if (window == null) return;
window.SetNavigationBarColor(new Color(originalNavBarColor));
window.DecorView.SystemUiVisibility = (StatusBarVisibility)originalNavBarFlags;
}
}
static bool isBrightColor(int color) {
if (Android.Resource.Color.Transparent == color) {
return true;
}
// See http://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx
bool bright = false;
int[] rgb = { Color.GetRedComponent(color), Color.GetGreenComponent(color), Color.GetBlueComponent(color) };
int brightness = (int)Math.Sqrt(rgb[0] * rgb[0] * .241 + rgb[1] * rgb[1] * .691 + rgb[2] * rgb[2] * .068);
if (brightness >= 210) {
bright = true;
}
return bright;
}
以上。
希望本文能对你有所帮助,感谢阅读。