原文地址:http://www.jianshu.com/p/33e30da0385c
最近工作特忙,好久没静下心总结一些开发中的心得,后面会陆续写一些文章总结一下最近遇到的问题和一些收获吧~
闲话少说,今天想跟大家分享的是,在android中,如何后台将一个view绘制成图片,并简单梳理下其中遇到的坑。很多app都有这么一个功能,当用户完成了app的某个任务时,产品希望用户点击分享的时候,能动态绘制出一张图片,让用户的分享的内容更加生动化。举个例子,比如扇贝单词的打卡,点击分享到新浪微博的时候,app会动态在后台生成一张图片,用户确认分享就会将这张图片分享出去。首先确认一下分享的图片上包含的元素吧:
比如说向下图这样(这算个广告吧)
首先可以确认的是,直接在View上布局不是一件难事,需要在代码中操作的信息如前面提到的用户信息啊,本次任务的数据啊,和两张需要异步加载的图片(轨迹图和头像)。
首先这不是一个简单的截屏,有些app会将分享的图片先展示给用户,然后当前页面“截屏”,生成一张图片,然后调取第三方的图片分享,总结来说要么是通过View.getDrawingCache()方法拿到当前View的缓存,要么是直接调用Bitmap.createBitmap()生成Bitmap。我们简单分析一下这两种做法:
方法1 View.getDrawingCache() 只适用于分享的View已经完整展示在用户的屏幕上,超出屏幕范围内的内容是不在生成的Bitmap内的。因为android手机的屏幕尺寸差异太大,通常我们需要生成的图片不会很短,所以很难保证这点,同时如果当前展示的View和最终生成的图片有一些差异的话,比如某个按钮不显示,某个文字换个内容等,就没办法用这种办法了。
第二种其实也是我们最终采用的方式,不过没那么简单,先来看这样一种做法,在实践中证明存在挺多问题。不过确实有人在采用,还是说一下吧
假设我当前是在A页面,我要分享出去的B图片和A页面只需要隐藏分享按钮,这种方法的做法是:
vShare..setVisibility(INVISIBLE);
Bitmap b = Bitmap.createBitmap( v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
v.draw(c);
return b;
说一下问题在哪,首先生成Bitmap的操作应该是后台异步操作,当前app应该有一个阻断态,我们会发现,用户会观察到分享按钮消失了,然后生成图片后又再次出现,一个按钮也许还ok,如果界面上的差异很大,这种方式给用户的体验就很不好。其次,也是最重要的,通常我们View的布局的宽高都是类似于macth_parent,wrap_content, 分享出去的图片尺寸无法控制(完全取决于手机的屏幕尺寸),图片中的一些元素的宽度(例如异步加载的网络图片)经过试验发现是异常的,原因我暂时还不清楚,大家可以和我探讨一下。
像我这个项目中需要生成的图片,和分享页面可以说差别非常大,那么我们该如何处理呢?
首先单独写一个布局,宽高全都是固定值。我设置的宽高单位是dp,也就是说我生成的图片的实际宽高取决于用户手机的屏幕密度,好处在于低配手机通常性能是首要考虑目标,尺寸过大很容易导致OOM,这些低配手机的屏幕密度一般都不高,而同时高配手机上,生成的分享图片如果不够清晰,给用户的体验就很不好(想像一下高清屏幕上的颗粒图吧)。因为涉及到数据的展示,我这边采取自定义View的方式,假设名称叫ShareView,它需要对外暴露这样几个方法:
思路确定,这里只提供最核心的代码:
/**
* 创建分享的图片文件
*/
public String createShareFile() {
Bitmap bitmap = createBitmap();
//将生成的Bitmap插入到手机的图片库当中,获取到图片路径
String filePath = MediaStore.Images.Media.insertImage(getContext().getContentResolver(), bitmap, null, null);
//及时回收Bitmap对象,防止OOM
if (!bitmap.isRecycled()) {
bitmap.recycle();
}
//转uri之前必须判空,防止保存图片失败
if (TextUtils.isEmpty(filePath)) {
return "";
}
return getRealPathFromURI(getContext(), Uri.parse(filePath));
}
/**
* 创建分享Bitmap
*/
private Bitmap createBitmap() {
//自定义ViewGroup,一定要手动调用测量,布局的方法
measure(getLayoutParams().width, getLayoutParams().height);
layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
//如果图片对透明度无要求,可以设置为RGB_565
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
draw(canvas);
return bitmap;
}
private static String getRealPathFromURI(Context context, Uri contentUri) {
Cursor cursor = null;
try {
String[] proj = {MediaStore.Images.Media.DATA};
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
if (cursor == null) {
return "";
}
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
核心代码交代完了,说一下一些小点吧:
谢谢大家