Android之文字转图片(输入文字,转成聊天气泡形式图片,并存入sd卡)

需求

  • 实现将文字转换为图片。
  • 图片为聊天框形式。
  • 用户可以选择文字字体,选择颜色。
  • 聊天框自适应文字。
  • 存入sd卡。

两种方案

  • 方案一:使用canvas绘制图片,使用staticLayout自动换行绘制文字,聊天框采取canvas画一个矩形,让矩形自适应文字。
  • 方案二:使用TextView绘制文字,TextView背景采取聊天气泡样式的.9图,将TextVIew存入手机中。

方案一

1.学习canvans

首先放几个写的很好的博客

需求中需要绘制矩形,所以详细写一下绘制圆角矩形。首先是绘制矩形,其中包含五个参数,分别是矩形的左右上下与绘制所用的画笔。这里的左右的基准是以左上角原点为基准的,遇到了一个坑,之后会说。简单说一下rect和rectf的区别,精度不同,rect是int型,rectf是float型。

        // 第一种
        canvas.drawRect(100,100,800,400,mPaint);

        // 第二种
        Rect rect = new Rect(100,100,800,400);
        canvas.drawRect(rect,mPaint);

        // 第三种
        RectF rectF = new RectF(100,100,800,400);
        canvas.drawRect(rectF,mPaint);

接下来是圆角矩形的绘制。这里主要复现了第四个博客做的事情,然后查阅了第五个博客FontMetrics类的参数。我实现的是绘制一个在屏幕中居中的矩形,然后文字在矩形上下左右居中,同时矩形大小要随文字的多少变换。整体效果见下图。

wrap_content下的情况 match_parent下的情况
圆角矩形的绘制代码如下,代码很好理解,与矩形相似,只不过圆角矩形多一个20,可以简单理解为圆角的半径,其实画的应该是一个椭圆形。下述代码是在ondraw()方法中,所以可以获取布局的宽度,以保持矩形始终居中对齐。这里的startX与endX是为了让矩形适配文字。
	int width=getWidth();
    int textsize=40;  
    String text = "哈哈啊哈哈哈哈哈哈哈";  
    int textwidth=text.length()*textsize;  
    float startX = width/2-textwidth/2 ;
    float startY = 100 ;
    float endX = width/2+textwidth/2 ;
    float endY = 200 ;
    RectF rectF = new RectF(startX, startY, endX, endY);
    //20是圆角半径
    canvas.drawRoundRect(rectF, 20, 20, paint);

接下来要用画笔Paint使用drawText()函数来绘制文字,代码如下:

	paint.setTextSize(textsize);//设置画笔的大小
 	Paint.FontMetrics fontMetrics=paint.getFontMetrics();
	float baseline = (rectF.bottom + rectF.top -fontMetrics.bottom - fontMetrics.top) / 2;
	canvas.drawText(text,(endX-startX)/2+startX-textwidth/2,baseline,paint);  

这里要说的比较多,首先是Paint.FontMetrics,它可以理解为一个字体度量。通过getFontMetrics()方法获取,其包含几个字符属性参数(top,ascent,descent,bottom)。这几个属性参数都是距离baseline的距离。
值得一提的是,我阅读源码时,有这样一段话,大概意思是值向下增加值向上减少。其实就是说,ascent与top的值是的,descent与bottom的值是的。ascent和descent是一个字体的建议距离(最高,最低)。
在这里插入图片描述
这里即将讲到第一个坑,很多新手在使用drawText的时候会遇到问题。首先,我们看下这个方法参数的含义:canvas.drawText(text, x, y, paint),第一个参数是我们需要绘制的文本,第四个参数是我们的画笔,这两个不用多说,主要是第二和第三个参数的含义,这两个参数在不同的情况下的值还是不一样的,x是这个字符串的左边在屏幕的位置,如果设置了paint.setTextAlign(Paint.Align.CENTER);那就是字符串的中心;y是指定这个字符串baseline在屏幕上的位置。大家记住了,不要混淆,y不是这个字符中心在屏幕上的位置,而是baseline在屏幕上的位置。

那么baseline的位置要如何设定呢,首先要获取你要居中的位置,比如上述的(rectF.bottom+rectF.top)/2,也就是150,要文字中间在150的位置。即以150画一条横线,文字上下的距离相等。那么就要获取top与buttom的差了,获取差之后除二,让baseline与150的距离为差/2。那么top距离150的距离=top-差/2,bottom距离150的距离为descent+差/2。这样二者就相等了,需要注意的就是正负要考虑。最后可以看到,成功将文字居中与圆角方框中间。

想详细了解可以看如下博客。
https://blog.csdn.net/flyeek/article/details/43934945https://blog.csdn.net/lovexieyuan520/article/details/43153275

2.学习使用StaticLayout

运用StaticLayout可以实现换行功能,并可以设置换行的宽度。StaticLayout拥有三个构造函数。参考StaticLayout 源码分析。我们一般使用第一个构造函数即可。

public StaticLayout(CharSequence source,
   TextPaint paint,
   int width,
   Layout.Alignment align,
   float spacingmult,
   float spacingadd,
   boolean includepad)
    
public StaticLayout(CharSequence source,
   int bufstart,
   int bufend,
   TextPaint paint,
   int outerwidth,
   Layout.Alignment align,
   float spacingmult,
   float spacingadd,
   boolean includepad)
    
public StaticLayout(CharSequence source,
   int bufstart,
   int bufend,
   TextPaint paint,
   int outerwidth,
   Layout.Alignment align,
   float spacingmult,
   float spacingadd,
   boolean includepad,
   TextUtils.TruncateAt ellipsize,
   int ellipsizedWidth)

主要能用上的就是这几个参数,我们主要需要设定好布局宽度。

参数名 含义
source 字符串
paint 画笔工具
width 布局宽度(超过该宽度换行)
bufstart 字符串开始分行的位置
bufend 字符串结束分行的位置
align 对齐方式(包括左对齐,居中,右对齐)
spacingmult 相对于字体高度的行间距
spacingadd 在基础行距上添加的值
includepad 文本顶部和底部是否留白
ellipsize 文本省略方式,有 START、MIDDLE、 END、MARQUEE 四种省略方式
ellipsizedWidth 省略宽度
maxLines 最大行数

3.图片存入sd卡

可以通过下述代码完成功能。主要涉及FileOutputStream类,以及bitmap的compress()方法,设置权限。同时我们为了查看图片,我们还要完成从路径中获取图片,并显示在屏幕上。

/**
     * 保存bitmap到SD卡
     * @param bitmap
     * @param imagename
     */
    public static String saveBitmapToSDCard(Bitmap bitmap, String imagename) {
        String path = "/sdcard/" + "img-" + imagename + ".png";
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(path);
            if (fos != null) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
                fos.close();
            }

            return path;
        } catch (Exception e) {
            e.printStackTrace();
        }
      return null;
    }

3.1 FileOutputStream类

要构造一个 FileOutputStream 对象,使用下图所示构造方法。如果这个文件不存在, 就会创建一个新文件。 如果这个文件已经存在, 前两个构造方法将会删除文件的当前内容。 为了既保留文件现有的内容又可以给文件追加新数据, 将最后两个构造方法中的参数 append 设置为 true。
在这里插入图片描述

3.2 compress()方法

该方法是压缩图片的方法。阅读源码的注释,可以发现,该函数的作用是将位图的压缩版本写入指定的outputstream。该函数有三个参数,第一个参数是压缩的格式,第二个参数是压缩的比例,第三个参数是OutputStream。我们使用FileOutputStream,将其写入文件(指定路径)。
如果该函数返回true,则可以通过将相应的inputstream传递给BitmapFactory.decodeStream()来重建位图。注:并非所有格式都直接支持所有bitmap configs,所以BitmapFactory返回的bitmap可能位于不同的位深,并且/或者可能丢失了每个像素的alpha值(例如,JPEG只支持不透明像素)。

public boolean compress(CompressFormat format, int quality, OutputStream stream)
 - @param format   The format of the compressed image
     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
     *                 small size, 100 meaning compress for max quality. Some
     *                 formats, like PNG which is lossless, will ignore the
     *                 quality setting
     * @param stream   The outputstream to write the compressed data.
     * @return true if successfully compressed to the specified stream.

3.3 设置权限

当然,如果我们想要存储的话就需要申请权限。安卓虚拟机有时会不好用,我们需要进入设置,应用,手动打开他的权限就好了。

 <!-- 安卓读写sd权限 -->   
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />    
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission    android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 

3.4 从sd卡读取图片

我们首先要获取到从之前存储的路径,之后要加载图片。

public Bitmap getBitmap(Bitmap bitmap) {
        String path = saveBitmapToSDCard(bitmap, "txttoimage");
        //打开
        Bitmap bm=bitmap;
        Log.d("存储在", path + "");
        //FileInputStream inputStream=null;
        File file = new File(path);
        if (file.exists()) {
            //这里读取到的是png格式,所以上面要改为png
           bm = BitmapFactory.decodeFile(path);
            //将图片显示到ImageView中
            // imageView.setImageBitmap(bm);
        }
        return bm;
    }

这里扩展一下如何正确加载图像。主要参考自这篇文章Android 多种方式正确的加载图像,有效避免oom

  • 安卓加载的方式
    Android开发中消耗内存较多一般都是在图像上面,正确的展现图像可以减少对内存的开销,有效的避免oom现象。首先我们知道我的获取图像的来源一般有三种源头:1.从网络加载2.从文件读取3.从资源文件加载。
    针对这三种情况我们一般使用BitmapFactory的decodeStream,decodeFile,decodeResource,这三个函数来获取到bitmap然后再调用ImageView的setImageBitmap函数进行展现。
  • 正确加载的方法
    假如我们的手机屏幕的分辨率(比如800*800)小于我们这张图片的分辨率(1080*1600),这样就会浪费内存。为了减少内存的开销,我们在加载图像时就应该参照控件的宽高像素来获取合适大小的bitmap。

采用这篇博客的方法,重写了一个BitmapUtils类,博主Github地址戳这里
这里只拿出一个方法来讲解, BitmapFactory提供了BitmapFactory.Option,用于设置图像相关的参数,在调用decode的时候我们可以将其传入来对图像进行相关设置。
option对象有两个数据域变量:inJustDecodeBounds(Boolean类型) 和inSampleSize(int类型)。

  • inJustDecodeBounds :如果设置为true则表示decode函数不会生成bitmap对象,仅是将图像相关的参数填充到option对象里,这样我们就可以在不生成bitmap而获取到图像的相关参数了。
  • inSampleSize:表示对图像像素的缩放比例。假设值为2,表示decode后的图像的像素为原图像的1/2。在下面的代码中封装了个简单的getFitInSampleSize函数(将传入的option.outWidth和option.outHeight与控件的width和height对应相除再取其中较小的值)来获取一个适当的inSampleSize

在设置了option的inSampleSize后我们将inJustDecodeBounds设置为false再次调用decode函数时就能生成bitmap了。

public static Bitmap getFitSampleBitmap(String file_path, int width, int height) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(file_path, options);
    options.inSampleSize = getFitInSampleSize(width, height, options);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(file_path, options);
}
public static int getFitInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) {
    int inSampleSize = 1;
    if (options.outWidth > reqWidth || options.outHeight > reqHeight) {
        int widthRatio = Math.round((float) options.outWidth / (float) reqWidth);
        int heightRatio = Math.round((float) options.outHeight / (float) reqHeight);
        inSampleSize = Math.min(widthRatio, heightRatio);
    }
    return inSampleSize;
}

4.实现方法

首先放一下最终效果图。设置了两种可选择的颜色,设置了一种字体,所以没有输入转换文字的字体。可以实现文字转换为图片,图片为聊天气泡样式,图片中文字超过十个自动换行,图片中聊天框动态适应文字,进行伸缩,最终将图片存储至sd卡。

wrap_content下的情况
发布了55 篇原创文章 · 获赞 28 · 访问量 9229

猜你喜欢

转载自blog.csdn.net/weixin_41796401/article/details/87862328
今日推荐