NanBox

打造自己的 APP「冰与火百科」(四):WebView 交互、夜间模式

给大家介绍一下简单的 WebView 交互和夜间模式在「冰与火百科」中的实现。

WebView 交互

在详情页面我是用 WebView 展示的,我想实现的交互是,点击 WebView 的内容跳转另一个页面。实现过程是,让 HTML 代码调用 JavaScript 代码,再让 JavaScript 代码调用 Android 的代码,下面看看如何实现。

HTML

先看 HTML 代码,假如在文本内容里有一个可以跳转的「凯特琳·徒利」,让他去调用 skip.js 的代码,指定 CatelynTully() 方法:

1
2
3
4
<body>
<a href="javascript:void(0)" onclick="CatelynTully()">凯特琳·徒利</a>
<script type="text/javascript" src="skip.js"></script>
</body>

JavaScript

这个 skip.js 文件我是放在客户端的,放在 assets 目录下,代码如下:

1
2
3
function CatelynTully(){
javascript:Android.goDetail('Catelyn_Tully');
}

意思就是去调用 Android 的 goDetail(String id) 方法。

Android

在客户端添加 goDetail 方法,我把 JavaScript 和 Java 交互的代码写在一个类里,记得给方法加上 @JavascriptInterface 注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Js2Java {
private Context mContext;
public Js2Java(Context context) {
this.mContext = context;
}
@JavascriptInterface
public void goDetail(String id) {
// 根据 id 跳转页面
}
}

来到显示 WebView 的页面,添加以下代码让 WebView 支持 JavaScript:

1
2
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new Js2Java(this), "Android");

使用 loadDataWithBaseURL 来展示数据:

1
binding.webView.loadDataWithBaseURL("file:///android_asset/", htmlData, "text/html", "utf-8", null);

这样就完成了一个简单的 JavaScript 和 Android 的交互,效果如下:

夜间模式

关于夜间模式的实现,主要是参考了 D_clock爱吃葱花 大神的这篇文章,简单说一下实现过程如下:

  1. 在 styles 中添加「DayTheme」和「NightTheme」两个主题;
  2. 在布局文件中使用类似 android:background=”?attr/colorBackground” 来设置颜色,使其跟随当前主题颜色;
  3. 编写 DayNightHelper,利用 SharePreferences 保存、获取当前模式;
  4. 在页面 setContentView 之前,判断当前模式,并通过 setTheme 设置当前模式;
  5. 将屏幕内容转为 Bitmap,对其执行一个渐隐动画,实现切换时渐变的效果;
  6. 监听模式切换,通过 TypedValue 和 Theme.resolveAttribute 在代码中获取 Theme 中的颜色,重新设置控件的颜色。

更详细的内容可以查看原文,下面再补充几个控件的颜色设置方法。

Toolbar

假设已经拿到了切换后的颜色 color,修改 Toolbar 的背景颜色和字体颜色:

1
2
toolbar.setBackground(color);
toolbar.setTitleTextColor(color);

除了这两项,Toolbar 上可能还有操作按钮,像我这里左边的菜单和右边的搜索按钮。它们的颜色可以这样设置:

1
2
3
4
5
6
7
8
9
10
11
// 菜单按钮
Drawable navigationIcon = toolbar.getNavigationIcon();
if (navigationIcon != null) {
navigationIcon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
// 搜索按钮
Menu toolbarMenu = toolbar.getMenu();
Drawable searchIcon = toolbarMenu.getItem(0).getIcon();
if (searchIcon != null) {
searchIcon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}

TabLayout

对于 TabLayout,涉及到的颜色有背景颜色、文字颜色(选中和未选中)、指示条:

1
2
3
tabLayout.setBackgroundResource(color);
tabLayout.setTabTextColors(normalColor, selectedColor);
tabLayout.setSelectedTabIndicatorColor(color);

NavigationView 存在一个头部,需要的话可以可以这样修改头部的背景和字体颜色:

1
2
3
4
5
6
7
8
View navigationHeader = navigationView.getHeaderView(0);
if (isDay) {
navigationHeader.setBackgroundResource(R.drawable.side_nav_bar_day);
} else {
navigationHeader.setBackgroundResource(R.drawable.side_nav_bar_night);
}
TextView tvHeader = (TextView) navigationHeader.findViewById(R.id.text_view);
tvHeader.setTextColor(color);

接下来是目录部分的背景、字体颜色及图表颜色:

1
2
3
navigationView.setBackgroundResource(color);
navigationView.setItemTextColor(color);
navigationView.setItemIconTintList(color);

RecyclerView

通过遍历所有的 ChildView,对每一项进行颜色设置:

1
2
3
4
for (int position = 0; position < recyclerView.getChildCount(); position++) {
ViewGroup childView = (ViewGroup) binding.recyclerView.getChildAt(position);
// 设置颜色
}

但要注意的是,RecyclerView 的内部使用 Recycler 和 RecyclerViewPool 实现了缓存,有可能出现当前使用的 item 颜色改变了,但是缓存里的没有变化。

解决方法是清理缓存,调用 Recycler 和 RecyclerViewPool 的 Clear() 方法,但前者无法直接调用,只能通过反射实现:

1
2
3
4
5
6
7
8
9
10
11
12
Class<RecyclerView> recyclerViewClass = RecyclerView.class;
try {
Field declaredField = recyclerViewClass.getDeclaredField("mRecycler");
declaredField.setAccessible(true);
Method declaredMethod = Class.forName(RecyclerView.Recycler.class.getName()).getDeclaredMethod("clear");
declaredMethod.setAccessible(true);
declaredMethod.invoke(declaredField.get(binding.recyclerView));
RecyclerView.RecycledViewPool recycledViewPool = recyclerView.getRecycledViewPool();
recycledViewPool.clear();
} catch (Exception e) {
e.printStackTrace();
}

StatusBar

在 SDK 21 以上,允许我们修改状态栏的颜色:

1
2
3
4
5
6
7
if (Build.VERSION.SDK_INT >= 21 {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = getTheme();
theme.resolveAttribute(R.attr.color, typedValue, true);
getWindow().setStatusBarColor(
ContextCompat.getColor(mContext, typedValue.resourceId));
}

夜间模式的实现就到此,在重新设置颜色的部分比较繁琐,但这是我目前看到效果比较好的实现方式。效果如下:

项目地址