实现根据字符个数可自动换行的Bitmap

package com.stackdump.bitmapautolinefeed;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;

import com.stackdump.bitmapautolinefeed.utils.SurfaceViewUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    //屏幕的宽度
    private int mDisplayX;
    //屏幕的高度
    private int mDisplayY;


    private EditText mEditText;
    private ImageView mImage;
    private Button mBtn;

    private TextPaint mPaint;
    //需要生成的图片
    private Bitmap mBitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEditText = (EditText)findViewById(R.id.text);
        mImage = (ImageView) findViewById(R.id.image);
        mBtn = (Button) findViewById(R.id.btn);

        //获取屏幕的宽度和高度,获取的宽度用于计算何时自动换行
        WindowManager manager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
        Display display = manager.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        mDisplayX = size.x;
        mDisplayY = size.y;

        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //存在内存泄露的风险,需自行优化
                new AsyncTask<Void, Void, Boolean>() {

                    @Override
                    protected void onPreExecute() {
                    }

                    @Override
                    protected Boolean doInBackground(Void... unused) {
                        Log.d("info","doInBackground");
                        return saveBmp(mEditText.getText().toString());
                    }

                    @Override
                    protected void onProgressUpdate(Void... unused) {
                    }

                    @Override
                    protected void onPostExecute(Boolean result) {
                        Log.d("info","getFilesDir:"+getFilesDir());//存储在内置存储
                        Log.d("info","getExternalStorageDirectory:"+Environment.getExternalStorageDirectory());//外置存储路径
                        Bitmap bmp = getLoacalBitmap(getFilesDir()+"/siteName.bmp");
                        if(bmp != null) {
                            mImage.setImageBitmap(bmp);
                        }

                    }
                }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            }
        });
    }

    private boolean saveBmp(String text) {
        try {
            initializePaint();

            //文字是单行时的总宽度(先计算单行的长度)
            int textWidth;
            //文字是单行时的总高度(先计算单行的高度)
            int textHeight;

            textWidth = (int) mPaint.measureText(text);

            Paint.FontMetrics fm = mPaint.getFontMetrics();
            textHeight = (int) Math.ceil(fm.bottom - fm.top);

            Log.d("info","textWidth:"+textWidth+" textHeight:"+textHeight);
            Log.d("info","mDisplayX:"+mDisplayX+" mDisplayY:"+mDisplayY);

            mBitmap = getNewBitMap(text, textWidth, textHeight, Math.abs(fm.top), mPaint);

            saveFileHighQuality(mBitmap);

        } catch (StringIndexOutOfBoundsException e) {
            return false;
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    public Bitmap getNewBitMap(String text, int textWidth, int textHeight, float top, TextPaint textPaint) {
        //水平偏移量,设置该值,文字可向右偏移一定距离
        int horizontalOffset = 60;
        int verticalOffset = 60;

        //The text may be more than one line,so calculate the number of rows
        int rows = textWidth / (mDisplayX-horizontalOffset);
        rows = (textWidth % (mDisplayX-horizontalOffset) > 0 ) ? rows + 1 : rows;

        //
        int bmpWidth = rows > 1 ? mDisplayX : (textWidth+horizontalOffset);
        int bmpHeight = Math.min(mDisplayY, textHeight*rows+verticalOffset);
        Bitmap newBitmap = Bitmap.createBitmap(bmpWidth,bmpHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(newBitmap);
        canvas.drawBitmap(newBitmap, 0, top, textPaint);

        canvas.translate(horizontalOffset, verticalOffset);
        /*
        参数含义:
            1.字符串子资源
            2 .画笔对象
            3.layout的宽度,字符串超出宽度时自动换行。(屏幕总宽度-水平平移量就是layout宽度)
            4.layout的样式,有ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 三种。
            5.相对行间距,相对字体大小,1.5f表示行间距为1.5倍的字体高度。
            6.相对行间距,0表示0个像素。
            实际行间距等于这两者的和。
            7.还不知道是什么意思,参数名是boolean includepad。
            需要指出的是这个layout是默认画在Canvas的(0,0)点的,如果需要调整位置只能在draw之前移Canvas的起始坐标
            canvas.translate(x,y);
         */
        StaticLayout sl= new StaticLayout(text, textPaint, mDisplayX-horizontalOffset, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        sl.draw(canvas);
        return newBitmap;
    }

    private void initializePaint() {
        mPaint = new TextPaint();
        //抗锯齿,设置为true,边界明显变模糊
        mPaint.setAntiAlias(true);
        //防抖动,设置为true,边界明显变模糊
        mPaint.setDither(true);
        mPaint.setColor(Color.RED);
        //透明度0-255,数值越小越透明
        mPaint.setAlpha(255);
        mPaint.setTextSize(100);
    }

    private boolean saveFileHighQuality(Bitmap bm) {
        if (!Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())) {
            return false;
        }

        FileOutputStream out = null;
        try {
            out = openFileOutput("siteName.bmp", Context.MODE_PRIVATE);

            int w = bm.getWidth();
            int h = bm.getHeight();
            int[] pixels = new int[w * h];
            bm.getPixels(pixels, 0, w, 0, 0, w, h);

            byte[] rgb = SurfaceViewUtils.addBMP_RGB_888(pixels, w, h);
            byte[] header = SurfaceViewUtils.addBMPImageHeader(rgb.length);
            byte[] infos = SurfaceViewUtils.addBMPImageInfosHeader(w, h);

            byte[] buffer = new byte[54 + rgb.length];
            System.arraycopy(header, 0, buffer, 0, header.length);
            System.arraycopy(infos, 0, buffer, 14, infos.length);
            System.arraycopy(rgb, 0, buffer, 54, rgb.length);
            out.write(buffer);
            //将输入流和输出流中的缓冲进行刷新,使缓冲区中的元素即时做输入和输出,而不必等缓冲区满
            out.flush();

        } catch (FileNotFoundException e) {
            return false;
        } catch (IOException e) {
            return false;
        } catch (Exception e) {
            return false;
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                }
            }
            if (!mBitmap.isRecycled()) {
                mBitmap.recycle();
                //作用只是提醒或告诉虚拟机,希望进行一次垃圾回收。
                //至于什么时候进行回收还是取决于虚拟机,而且也不能保证一定进行回收(如果-XX:+DisableExplicitGC设置成true,则不会进行回收)
                System.gc();
            }
        }
        return true;
    }

    /**
     * 加载本地图片
     * 本地图片路径
     * @param path
     * @return
     */
    public static Bitmap getLoacalBitmap(String path) {
        try {
            FileInputStream fis = new FileInputStream(path);
            return BitmapFactory.decodeStream(fis);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 从服务器取图片
     * @param url
     * @return
     */
    public static Bitmap getHttpBitmap(String url) {
        URL myFileUrl = null;
        Bitmap bitmap = null;
        try {
            Log.d("info", url);
            myFileUrl = new URL(url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        try {
            HttpURLConnection conn = (HttpURLConnection) myFileUrl.openConnection();
            conn.setConnectTimeout(10);
            conn.setReadTimeout(10);
            conn.setDoInput(true);
            conn.connect();
            InputStream is = conn.getInputStream();
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".MainActivity">

<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<EditText
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorAccent"
app:layout_constraintLeft_toRightOf="@+id/btn"/>

<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/btn"
/>

</androidx.constraintlayout.widget.ConstraintLayout>

效果:

1.单行且无平移:

5

2.多行且无平移

6

3.单行且有平移:

7

4.多行且有平移:

8
0