安卓中图片一般有两种加载方式: ImageView和图片框架。普通的固定资源图片用ImageView就行;网络图片或者列表中的图片,UIL、Fresco、Picasso等框架也能解决下载、缓存管理、滑动优化这些基本问题。除了一些特殊情况,比如超长图片的完整展示,实际情况就是一些做成图片形式的长图文:比如宽度1000、高度10000的长图。
安卓底层的显示是由OpenGL ES支持的,渲染的图片有4096*4096的大小限制,因此ImageView的任何一边不能超过 4096 个 像素。当上面这张长图放进ImageView中,是显示不出来的,log里会警告:
W/OpenGLRenderer: Bitmap too large to be uploaded into a texture (1000x10000, max=4096x4096)
用图片框架也一样,因为后者也是对ImageView的包装。
那么,只能使用BitmapRegionDecoder将图片切分了(BitmapRegionDecoder基本用法这里不再赘述),将长图切分成一条条,每条的高度控制在4096以内,分别放进一个ImageView中,这样暂时能显示了。不过还是有问题,这几个ImageView得放进一个能滑动的控件中才能正常显示。如果放进ScrollView中,这些图片轻松占几十M内存,小内存手机很容易OOM;如果放进ListView/RecyclerView中,内存是能在滑动时及时回收了,这样代码就复杂一些了,如果这张图外面还有个列表什么的,就更麻烦了。再考虑到图片可能来自网络地址,显示前又得加上下载、本地缓存等处理。
有没有一个办法能解决以上所有问题,能加载超长图片、不会OOM、不需要下载、代码简单、轻松嵌入到页面中?当然是有的,就是使用原生控件WebView加载,其实想到就简单了。
WebView本质是一个Chromium内核的浏览器,用来加载网页,既然能展示网页H5,展示一张图片也就不在话下。
使用方法如下:
在layout中放一个WebView控件,将图片地址imageUrl用html包裹起来,再使用webView.loadData()方法加载图片。
String imageUrl = "http://att.bbs.duowan.com/forum/201405/22/154840fovfma9ayappk4fp.jpg";
String html = "<html>" +
"<body>" +
" <style type=\"text/css\"> " +
" body{ " +
" margin:0px; " +
" } " +
" </style> " +
"<img id=\"img\" src=\""+imageUrl+"\" width=\""+screenWidthDp+"\"/>" +
"</body>" +
"</html>";
WebView webView = (WebView) findViewById(R.id.web_view);
webView.loadData(html, "text/html", "utf-8");
上面用String来表示html,便于在代码中动态修改,比较灵活。也可以将html写好放到assets中读取,那样可读性好一些。
html的img标签中除了动态设置src图片地址外,还动态设置了width图片宽度,这里的宽度screenWidthDp传的是屏幕的宽度的dp值,需要获取屏幕宽度px值,转换成dp传进去。之所以这么做,是为了合理缩放图片以适应屏幕宽度。也有其他适应屏幕的方法,比如:
WebSettings settings = webView.getSettings();
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
但是测试中发现这种方法在某些机型上会随机失效,比如小米3。因此在html中动态设置是最稳妥的方式。
图片经过缩放完美展示了,但有时会有这样的需求——获取图片的原始宽度,以计算图片的缩放倍数。图片是直接在WebView中以网页的形式缩放加载的,WebView并没有提供原始宽度的获取方法,那么只能在网页里想办法了。html中图片img的naturalWidth属性代表原始宽度,要把这个html中的数值传到java里,就需要使用JS接口回调了,方法也简单,完整代码如下
@SuppressLint("JavascriptInterface")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String imageUrl = "http://att.bbs.duowan.com/forum/201405/22/154840fovfma9ayappk4fp.jpg";
int screenWidthDp = px2dp(this, getScreenWidth(this));
String html = "<html>" +
"<body>" +
" <style type=\"text/css\"> " +
" body{ " +
" margin:0px; " +
" } " +
" </style> " +
"<img id=\"img\" src=\""+imageUrl+"\" width=\""+screenWidthDp+"\"/>" +
"<script>" +
" function getsize(){" +
" var img = document.getElementById(\"img\");" +
" javascript:stub.getNaturalSize(img.naturalWidth,img.naturalHeight);" +
" }" +
"</script>" +
"</body>" +
"</html>";
WebView webView = (WebView) findViewById(R.id.web_view);
webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:getsize()");
}
});
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new Object() {
@JavascriptInterface
public void getNaturalSize(int imgWidth, int imgHeight) {
Log.i("image size","width: "+imgWidth+" height: "+imgHeight);
}
},"stub");
webView.loadData(html, "text/html", "utf-8");
}
// 获取屏幕宽度
public static int getScreenWidth(Context context) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
return dm.widthPixels;
}
// 将px转换成dp值
public static int px2dp(Context context, int px) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (px / scale + 0.5f);
}
首先在html中添加一个JS方法getsize(),在方法里获取图片原始宽度img.naturalWidth;
然后使用webView.setWebViewClient监听页面加载完成,完成后使用view.loadUrl(“javascript:getsize()”)调用JS的getsize()方法;
接着使用webView.addJavascriptInterface,添加接口stub和接口方法getNaturalSize()供JS调用;
最后在JS的getsize()方法中调用Java中getNaturalSize()方法,并通过方法的参数将原始宽度传到Java中。
一句话总结就是Java调用JS方法,在JS方法里又调用Java方法,数值都是通过方法的参数传递。这就是Java跟html/JS的交互过程。