100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Android自定义View分享——仿微信朋友圈图片合并效果

Android自定义View分享——仿微信朋友圈图片合并效果

时间:2022-03-13 18:16:04

相关推荐

Android自定义View分享——仿微信朋友圈图片合并效果

写在前面

笔者近来在学习Android自定义View,收集了一些不算复杂但又“长得”还可以的自定义View效果实现,之前分享过两个效果:一个水平的进度条,一个圆形温度显示器,如果你有兴趣的话可以看看:

一个水平的进度条

一个圆形温度显示器

今天我要来分享的是这样的效果——图片合并

当你使用微信进入某一个人的朋友圈列表,如果他们某条信息所携带的图片超过一张,就会做成如图所示这种显示效果,是将多张图片合并在一起形成的预览效果。当然对于朋友圈的实现方式我不清楚,有可能是服务器端已经合并好图片了,返回给客户端的就是一张合并之后的图片,只要一个ImageView就可以了。不过对于自定义View的学习,你是否想过,如果服务器给你的是若干张图片,你要自己拼接成一张,该怎么搞,今天我们来讲这个。

所涉及的知识点

其实在绘图方面本文所牵扯到的知识点还没有多少,反而是关于bitmap的内存占用,控制显得更加重要。总的来说大概如下:

canvas.drawBitmap()方法里面各个参数含义。如果要显示的图片区域(或者像素点)明显的比原图片小(因为我们只是做缩略图),怎么节约内存使用。这将涉及到加载"bitmap"时利用"BitmapFactory.Options"对象计算压缩的比例等等知识。一些很简单的数学计算,如前面的图片所示,是需要在一、二、三、四张图片的情况下,计算裁剪图片大小以及摆放在什么样的位置上。

设计思路

想象一下,如果我现在给你一张白纸(canvas,其实如果我说一个相框,可能更方便你想象),再给你一到四张照片,你要怎么“摆”出我的截图里的效果。其实很简单,笔者提出思路如下:

首先你需要判断下图片张数,然后分别进行处理。如果是一张图片,我们就将整张原图“绘制”到我们的canvas上面。如果是两张图片,我们就将它们分别横向压缩一半,然后分别绘制到canvas里面,每张图片占一半位置。如果是三张图片,将第一张压缩一半,绘制到canvas的左半边,另外两张图片压缩成原来的四分之一,绘制到canvas右上角,右下角。如果是四张图片,将四张图片全部都压缩成原来的四分之一,绘制到canvas的四个角上。为了美观,图片之间画条白线分隔一下。

一步一步,切分代码

关于图片源,在你的项目当中,图片来源大多应该来自网络,不过作为一个样例,贪图方便(方便偷懒),图片来源直接来自本地,在drawable文件夹下面,所以我在生成bitmap时,调用的是"BitmapFactory.decodeResource()"方法。如果你的图片来自网络,可能需要别的方法,还有,如果你是用glide之类网络框架下载图片,请搞清楚这些框架下载图片之后对图片所做的事情,笔者曾经在实际项目里,使用一些框架导致数据错乱,需要另外进行其他调试。

一张图片时的代码片段

decodeSampledBitmapFromResource()方法是个自定义的内部方法,根据实际需要尺寸加载图片,用来防止内存耗尽,这个将在稍后展开来讲。

if(length == 1){//如果只有一张图片,则将该图片裁剪成合适大小,直接绘制就可以了bitmap = decodeSampledBitmapFromResource(drawableIds[0], measuredWidth, measuredHeight);//要截取的原图片的范围srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());//图片绘制在canvas上的范围dstRect.set(0, 0, measuredWidth, measuredHeight);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);}

两张图片时的代码片段

两张图片,图片的最终宽度就成了控件宽度减去白色分隔线的宽度,再除以2。

if(length == 2){//如果有两张图片,则两张图片各占左右一半位置,中间画一条分隔线//两张图片中间分隔线的宽度int lineWidth = 4;//图片的目标宽度int dstWidth = (measuredWidth-lineWidth)/2;//绘制第一张图片bitmap = decodeSampledBitmapFromResource(drawableIds[0], dstWidth, measuredHeight);srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(0, 0, dstWidth, measuredHeight);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);//绘制分割线linePaint.setColor(Color.WHITE);canvas.drawLine(dstWidth, 0, dstWidth+lineWidth, getMeasuredHeight(), linePaint);//绘制第二张图片bitmap = decodeSampledBitmapFromResource(drawableIds[1], dstWidth, getMeasuredHeight());srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(dstWidth+lineWidth, 0, getMeasuredWidth(), getMeasuredHeight());canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);}

三张图片和四张图片的情况,类似,只要按着上面的逻辑来,就差不多。是不是感觉很简单?确实,其实相当一部分的自定义View,不像想象中的那么复杂。而且这次分享的这一个效果,其重点也并不在绘制的逻辑上面,而是在于从一个来源(或者说叫做大小,尺寸)不确定的图片上面,根据你自己需要的大小,加载、裁剪出合适尺寸的图片,同时还要考虑内存占用,不要发生OOM。

加载尺寸不确定的Bitmap时的内存占用问题

解决这个问题的思路是:

先将"BitmapFactory.Options"对象的"inJustDecodeBounds"属性设置为true,这样子能获取图片相关信息。根据我们所需要的最终尺寸,以及图片原来信息,计算以及设置压缩比例。设置好压缩比例,将刚才的那个属性设置为false,将一个“比较小的”bitmap给加载进来。通过"Bitmap.createScaledBitmap(Bitmap, int , int , boolean)"方法,得到最终我们要的尺寸的Bitmap。

关于这个解决思路背后的原理,推荐参考这篇博客:

根据ImageView的大小来压缩Bitmap,避免OOM

具体代码跟下面的完整用例贴在一起。

完整用例

类代码

public class MergePictureView extends View{//要显示的图片资源数组(即要合并的图片)private int[] drawableIds;//裁剪图片时的裁剪区域private Rect srcRect = new Rect();//要将图片绘制到哪一个区域private Rect dstRect = new Rect();private Paint bitmapPaint = new Paint();private Paint linePaint = new Paint();public MergePictureView(Context context) {super(context);}public MergePictureView(Context context, AttributeSet attrs) {super(context, attrs);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getMeasureSize(widthMeasureSpec), getMeasureSize(heightMeasureSpec));}@Overrideprotected void onDraw(Canvas canvas) {if(drawableIds == null || drawableIds.length == 0){super.onDraw(canvas);return;}int length = drawableIds.length;int measuredWidth = getMeasuredWidth();int measuredHeight = getMeasuredHeight();Bitmap bitmap;if(length == 1){//如果只有一张图片,则将该图片裁剪成合适大小,直接绘制就可以了bitmap = decodeSampledBitmapFromResource(drawableIds[0], measuredWidth, measuredHeight);srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(0, 0, measuredWidth, measuredHeight);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);}else if(length == 2){//如果有两张图片,则两张图片各占左右一半位置,中间画一条分隔线//两张图片中间分隔线的宽度int lineWidth = 4;//图片的目标宽度int dstWidth = (measuredWidth-lineWidth)/2;//绘制第一张图片bitmap = decodeSampledBitmapFromResource(drawableIds[0], dstWidth, measuredHeight);srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(0, 0, dstWidth, measuredHeight);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);//绘制分割线linePaint.setColor(Color.WHITE);canvas.drawLine(dstWidth, 0, dstWidth+lineWidth, getMeasuredHeight(), linePaint);//绘制第二张图片bitmap = decodeSampledBitmapFromResource(drawableIds[1], dstWidth, getMeasuredHeight());srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(dstWidth+lineWidth, 0, getMeasuredWidth(), getMeasuredHeight());canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);}else if(length == 3){//如果有三张图片,则第一张图片在左边占一半位置,其余两张在右边占四分之一位置,图片之间画线分隔//左右分割线宽度,上下分割线高度int leftRightWidth = 4, topBottomHeight = 4;//每一张图片的宽度int dstWidth = (getMeasuredWidth()-leftRightWidth)/2;//绘制第一张图片bitmap = decodeSampledBitmapFromResource(drawableIds[0], dstWidth, getMeasuredHeight());srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(0, 0, dstWidth, getMeasuredHeight());canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);//绘制左右分割线linePaint.setColor(Color.WHITE);canvas.drawLine(dstWidth, 0, dstWidth+leftRightWidth, getMeasuredHeight(), linePaint);//绘制第二张图片bitmap = decodeSampledBitmapFromResource(drawableIds[1], dstWidth, getMeasuredHeight()/2);srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(dstWidth+leftRightWidth, 0, getMeasuredWidth(), (getMeasuredHeight()-topBottomHeight)/2);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);//回执上下分割线canvas.drawLine(measuredWidth/2, measuredHeight/2, measuredWidth, measuredHeight/2, linePaint);//绘制第三张图片Bitmap thirdBitmap = decodeSampledBitmapFromResource(drawableIds[2], dstWidth, getMeasuredHeight());srcRect.set(0, 0, thirdBitmap.getWidth(), thirdBitmap.getHeight());dstRect.set(dstWidth+leftRightWidth, (measuredHeight-topBottomHeight)/2+topBottomHeight, measuredWidth, getMeasuredHeight());canvas.drawBitmap(thirdBitmap, srcRect, dstRect, bitmapPaint);}else{//四张以及以上图片统一处理,最多只能显示四张,将四张图片已“田”字形分布//这是分割线的尺寸,横线的高,竖线的宽,都等于他int lineSize = 4;//四张图片都是相同宽度,相同高度int dstWidth = (measuredWidth-lineSize)/2;int dstHeight = (measuredHeight-lineSize)/2;//先将四张图画上去bitmap = decodeSampledBitmapFromResource(drawableIds[0], dstWidth, dstHeight);srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(0, 0, dstWidth, dstHeight);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);bitmap = decodeSampledBitmapFromResource(drawableIds[1], dstWidth, dstHeight);srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(dstWidth+lineSize, 0, measuredWidth, dstHeight);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);bitmap = decodeSampledBitmapFromResource(drawableIds[2], dstWidth, dstHeight);srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(0, dstHeight+lineSize, dstWidth, measuredHeight);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);bitmap = decodeSampledBitmapFromResource(drawableIds[3], dstWidth, dstHeight);srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dstRect.set(dstWidth+lineSize, dstHeight+lineSize, measuredWidth,measuredHeight);canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint);//最后画两条分割线canvas.drawLine(dstWidth, 0, dstWidth, measuredHeight, linePaint);canvas.drawLine(0, dstHeight, measuredWidth, dstHeight, linePaint);}super.onDraw(canvas);}public void setDrawableIds(int[] drawableIds){this.drawableIds = drawableIds;invalidate();}/*** 从Resources中加载图片* @param resId 图片资源* @param reqWidth 目标宽度* @param reqHeight 目标高度* @return*/private Bitmap decodeSampledBitmapFromResource(int resId, int reqWidth, int reqHeight) {final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true; // 设置成了true,不占用内存,只获取bitmap宽高BitmapFactory.decodeResource(getResources(), resId, options); // 读取图片长宽,目的是得到图片的宽高options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 调用上面定义的方法计算inSampleSize值// 使用获取到的inSampleSize值再次解析图片options.inJustDecodeBounds = false;Bitmap src = BitmapFactory.decodeResource(getResources(), resId, options); // 载入一个稍大的缩略图return createScaleBitmap(src, reqWidth, reqHeight, options.inSampleSize); // 通过得到的bitmap,进一步得到目标大小的缩略图}//根据"measureSpec"返回具体尺寸值private static int getMeasureSize(int measureSpec){int measureMode = MeasureSpec.getMode(measureSpec);//先给一个默认值int measureSize = 200;if(measureMode == MeasureSpec.EXACTLY){measureSize = MeasureSpec.getSize(measureSpec);}else if(measureMode == MeasureSpec.AT_MOST){measureSize = Math.min(measureSize, MeasureSpec.getSize(measureSpec));}return measureSize;}/*** 计算图片的压缩比率* @param options 参数* @param reqWidth 目标的宽度* @param reqHeight 目标的高度* @return inSampleSize 压缩比率*/private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {//源图片的高度和宽度final int height = options.outHeight;final int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {final int halfHeight = height / 2;final int halfWidth = width / 2;// Calculate the largest inSampleSize value that is a power of 2 and keeps both// height and width larger than the requested height and width.while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {inSampleSize *= 2;}}return inSampleSize;}/*** 通过传入的bitmap,进行压缩,得到符合标准的bitmap* @param src 原图片Bitmap* @param dstWidth 目标宽度* @param dstHeight 目标高度* @return 压缩后的图片Bitmap*/private static Bitmap createScaleBitmap(Bitmap src, int dstWidth, int dstHeight, int inSampleSize) {// 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响,我们这里是缩小图片,所以直接设置为falseBitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);//如果图片有缩放,回收原来的图片if (src != dst) src.recycle();return dst;}}

在XML文件里

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><!--四张图片展示效果--><com.jf.simplecustomview.view.MergePictureViewandroid:id="@+id/merge_picture_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="start"/></LinearLayout>

Activity里面

public class MergePictureActivity extends AppCompatActivity{@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_merge_picture);//展示四张图片合并效果int[] resourcesIdsFirst = new int[]{R.mipmap.t1, R.mipmap.t2, R.mipmap.t3, R.mipmap.t4};((MergePictureView)findViewById(R.id.merge_picture_view)).setDrawableIds(resourcesIdsFirst);}

项目源码:

/kingfarou/SimpleCustomView

这个项目里面集合了好几个自定义View,本文所对应的View类名叫做"MergePictureView"。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。