借之前的文章开发一个新的组件,用于在 app 内愉快的使用 webview。
比如在微信里,打开微信公众号文章的时候,这里文章的内容的渲染都是由 webview 控件完成的,这比在浏览器里打开友善多了,并且功能更强大,可以调用外部程序,识别二维码,支付等等。假如我们的 app 里有动态内容,会经常在服务端编辑好页面的方式更新,那么使用 webview 展示是最好不过了。
之前的那篇文章中提到过,可以动态传递对象到含有 webview 的 activity 页面,因为这个 activity 每次只会打开一个实例,为了简化对象的传递,用的是单例存储要传递的对象,代码参考如下:
public enum WebFrameSettings {
instance;
private String url;
private HashMap<String, Object> objs;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public HashMap<String, Object> getObjs() {
return objs;
}
public void addObject(String key, Object val) {
objs.put(key, val);
}
public void clearObjects() {
objs.clear();
}
public void removeObject(String key) {
objs.remove(key);
}
WebFrameSettings() {
url = "";
objs = new HashMap<>();
}
}
接下来创建一个 activity,放置 Toolbar 、ProgressBar、WebView 控件。
界面布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="me.xuzhi.webframemodule.WebFrameActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbarWebFrameModule"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:background="@color/webFrameToolbarBackgroundColor"
android:titleTextColor="@color/webFrameToolbarForcegroundColor"
app:titleTextColor="@color/webFrameToolbarForcegroundColor">
</android.support.v7.widget.Toolbar>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true">
<WebView
android:id="@+id/webViewWebFrameModule"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/progressBar"/>
<ProgressBar
android:id="@+id/progressBarWebFrameModule"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="2dp"
android:progress="50"
android:progressDrawable="@drawable/progress_bar_webframe"/>
</FrameLayout>
</LinearLayout>
为这个模块创建一个主题,否则 Toolbar 右上角的三个点都是黑色的。
<resources>
<style name="ModuleTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textColorSecondary">#ffffff</item>
</style>
</resources>
为 Toolbar 设置标题和返回图标。
toolbarWebFrameModule.setTitle(WebFrameSettings.instance.getUrl());
toolbarWebFrameModule.setNavigationIcon(R.drawable.ic_action_arrow_back);
注意 setNavigationOnClickListener 一定要在 setSupportActionBar 之后调用,否则不生效。
setSupportActionBar(toolbarWebFrameModule);
toolbarWebFrameModule.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (webViewWebFrameModule.canGoBack()) {
webViewWebFrameModule.goBack();
} else finish();
}
});
为 Toolbar 增加菜单。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<item
android:id="@+id/action_close"
android:icon="@drawable/ic_action_close"
android:title="关闭"
app:showAsAction="always"/>
<item
android:id="@+id/action_refresh"
android:title="刷新"
app:showAsAction="collapseActionView"/>
<item
android:id="@+id/action_share"
android:title="分享"
app:showAsAction="collapseActionView"/>
<item
android:id="@+id/action_copylink"
android:title="复制链接"
app:showAsAction="collapseActionView"/>
<item
android:id="@+id/action_openWithBrowser"
android:title="在浏览器中打开"
app:showAsAction="collapseActionView"/>
</menu>
重写 activity 的 onCreateOptionsMenu 方法,将 menu 添加进来。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_webframe, menu);
return true;
}
菜单按钮的事件:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_close) {
finish();
} else if (id == R.id.action_copylink) {
doCopyLink();
} else if (id == R.id.action_openWithBrowser) {
doOpenWithBrowser();
} else if (id == R.id.action_refresh) {
doRefresh();
} else if (id == R.id.action_share) {
doShare();
}
return true;
}
关于复制的问题,ClipboardManager 的 setText 方法已经过时了,应该换用 setPrimaryClip 方法。
private void doCopyLink() {
try {
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText("link", WebFrameSettings.instance.getUrl());
cm.setPrimaryClip(clipData);
Toast.makeText(getApplicationContext(), "复制链接成功", Toast.LENGTH_SHORT).show();
} catch (Exception ex) {
Toast.makeText(getApplicationContext(), "复制链接失败", Toast.LENGTH_SHORT).show();
}
}
如果 webview 中需要 js 调用 java 方法,可以在单例添加对应的类,webview 在执行 loadUrl 之前会检查是否有对象值。
HashMap<String, Object> invokeObjects = WebFrameSettings.instance.getObjs();
if (invokeObjects.size() > 0) {
Iterator iter = invokeObjects.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String key = entry.getKey().toString();
Object val = entry.getValue();
webViewWebFrameModule.addJavascriptInterface(val, key);
}
}
webview 的 onProgressChanged 和 onPageFinished 比较让人头疼,因为加载某个资源出错也会触发,所以只凭两个方法判断页面加载完成还是不科学,这就导致加载进度条的什么时候结束很让人纠结。
整体效果:
这个模块我已放到 github 上,戳 https://github.com/yahch/WebFrame
也可以直接在项目引用:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
compile 'com.github.yahch:WebFrame:1.1'
}
Intent intent = new Intent(MainActivity.this, WebFrameActivity.class);
WebFrameSettings.instance.setUrl("http://weibo.cn");
startActivity(intent);