给大家介绍一下简单的 WebView 交互、夜间模式以及 Material Design 风格搜索的实现。
WebView 交互
在详情页面我是用 WebView 展示的,我想实现的交互是,点击 WebView 的内容跳转另一个页面。实现过程是,让 HTML 代码调用 JavaScript 代码,再让 JavaScript 代码调用 Android 的代码,下面看看如何实现。
HTML
先看 HTML 代码,假如在文本内容里有一个可以跳转的「凯特琳·徒利」,让他去调用 skip.js 的代码,指定 CatelynTully() 方法:
凯特琳·徒利
JavaScript
这个 skip.js 文件我是放在客户端的,放在 assets 目录下,代码如下:
function CatelynTully(){
javascript:Android.goDetail('Catelyn_Tully');
}
意思就是去调用 Android 的 goDetail(String id) 方法。
Android
在客户端添加 goDetail 方法,我把 JavaScript 和 Java 交互的代码写在一个类里,记得给方法加上 @JavascriptInterface 注解:
public class Js2Java {
private Context mContext;
public Js2Java(Context context) {
this.mContext = context;
}
@JavascriptInterface
public void goDetail(String id) {
// 根据 id 跳转页面
}
}
来到显示 WebView 的页面,添加以下代码让 WebView 支持 JavaScript:
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new Js2Java(this), "Android");
使用 loadDataWithBaseURL 来展示数据:
binding.webView.loadDataWithBaseURL("file:///android_asset/", htmlData, "text/html", "utf-8", null);
这样就完成了一个简单的 JavaScript 和 Android 的交互,效果如下:
夜间模式
关于夜间模式的实现,主要是参考了 D_clock爱吃葱花 大神的这篇文章,简单说一下实现过程如下:
- 在 styles 中添加「DayTheme」和「NightTheme」两个主题;
- 在布局文件中使用类似 android:background="?attr/colorBackground" 来设置颜色,使其跟随当前主题颜色;
- 编写 DayNightHelper,利用 SharePreferences 保存、获取当前模式;
- 在页面 setContentView 之前,判断当前模式,并通过 setTheme 设置当前模式;
- 将屏幕内容转为 Bitmap,对其执行一个渐隐动画,实现切换时渐变的效果;
- 监听模式切换,通过 TypedValue 和 Theme.resolveAttribute 在代码中获取 Theme 中的颜色,重新设置控件的颜色。
更详细的内容可以查看原文,下面再补充几个控件的颜色设置方法。
Toolbar
假设已经拿到了切换后的颜色 color,修改 Toolbar 的背景颜色和字体颜色:
toolbar.setBackground(color);
toolbar.setTitleTextColor(color);
除了这两项,Toolbar 上可能还有操作按钮,像我这里左边的菜单和右边的搜索按钮。它们的颜色可以这样设置:
// 菜单按钮
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,涉及到的颜色有背景颜色、文字颜色(选中和未选中)、指示条:
tabLayout.setBackgroundResource(color);
tabLayout.setTabTextColors(normalColor, selectedColor);
tabLayout.setSelectedTabIndicatorColor(color);
NavigationView
NavigationView 存在一个头部,需要的话可以可以这样修改头部的背景和字体颜色:
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);
接下来是目录部分的背景、字体颜色及图表颜色:
navigationView.setBackgroundResource(color);
navigationView.setItemTextColor(color);
navigationView.setItemIconTintList(color);
RecyclerView
通过遍历所有的 ChildView,对每一项进行颜色设置:
for (int position = 0; position < recyclerView.getChildCount(); position++) {
ViewGroup childView = (ViewGroup) binding.recyclerView.getChildAt(position);
// 设置颜色
}
但要注意的是,RecyclerView 的内部使用 Recycler 和 RecyclerViewPool 实现了缓存,有可能出现当前使用的 item 颜色改变了,但是缓存里的没有变化。
解决方法是清理缓存,调用 Recycler 和 RecyclerViewPool 的 Clear() 方法,但前者无法直接调用,只能通过反射实现:
Class 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 以上,允许我们修改状态栏的颜色:
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));
}
夜间模式的实现就到此,在重新设置颜色的部分比较繁琐,但这是我目前看到效果比较好的实现方式。效果如下:
项目地址