Dibujar texto multilínea en Canvas

Una pregunta con suerte rápida, pero parece que no puedo encontrar ningún ejemplo … Me gustaría escribir texto de varias líneas en una View personalizada a través de un Canvas , y en onDraw() tengo:

 ... String text = "This is\nmulti-line\ntext"; canvas.drawText(text, 100, 100, mTextPaint); ... 

Esperaba que esto produjera saltos de línea, pero en su lugar veo caracteres crípticos donde \n estaría.

Cualquier puntero apreciado.

Pablo

Lamentablemente, Android no sabe qué es \n . Lo que tiene que hacer es quitar el \n y luego compensar la Y para obtener su texto en la siguiente línea. Entonces algo como esto:

 canvas.drawText("This is", 100, 100, mTextPaint); canvas.drawText("multi-line", 100, 150, mTextPaint); canvas.drawText("text", 100, 200, mTextPaint); 

Encontré otra manera de usar diseños estáticos. El código está aquí para que cualquiera pueda consultar:

 TextPaint mTextPaint=new TextPaint(); StaticLayout mTextLayout = new StaticLayout(mText, mTextPaint, canvas.getWidth(), Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); canvas.save(); // calculate x and y position where your text will be placed textX = ... textY = ... canvas.translate(textX, textY); mTextLayout.draw(canvas); canvas.restre(); 

Simplemente itere a través de cada línea:

 int x = 100, y = 100; for (String line: text.split("\n")) { canvas.drawText(line, x, y, mTextPaint); y += mTextPaint.descent() - mTextPaint.ascent(); } 

Esta es mi solución basada en la respuesta de @ Dave (gracias por cierto ;-))

 import android.graphics.Canvas; import android.graphics.Paint; public class mdCanvas { private Canvas m_canvas; public mdCanvas(Canvas canvas) { m_canvas = canvas; } public void drawMultiline(String str, int x, int y, Paint paint) { for (String line: str.split("\n")) { m_canvas.drawText(line, x, y, paint); y += -paint.ascent() + paint.descent(); } } } 

Traté de heredar Canvas, pero realmente no te deja. ¡Así que esta es una clase intermedia!

Debo agregar aquí mi versión, que también considera ANCHO DE CARRERA.

 void drawMultiLineText(String str, float x, float y, Paint paint, Canvas canvas) { String[] lines = str.split("\n"); float txtSize = -paint.ascent() + paint.descent(); if (paint.getStyle() == Style.FILL_AND_STROKE || paint.getStyle() == Style.STROKE){ txtSize += paint.getStrokeWidth(); //add stroke width to the text size } float lineSpace = txtSize * 0.2f; //default line spacing for (int i = 0; i < lines.length; ++i) { canvas.drawText(lines[i], x, y + (txtSize + lineSpace) * i, paint); } } 

He escrito un ejemplo completo

enter image description here

colors.xml

  #64000000 

clase java

  public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.amit); ImageView imageView = (ImageView)findViewById(R.id.imageView); imageView.setImageBitmap(drawTextToBitmap(this, bm, "Name: Kolala\nDate: Dec 23 2016 12:47 PM, \nLocation: 440 Banquets & Restaurents")); } public Bitmap drawTextToBitmap(Context gContext, Bitmap bitmap, String gText) { Resources resources = gContext.getResources(); float scale = resources.getDisplayMetrics().density; android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig(); // set default bitmap config if none if(bitmapConfig == null) { bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888; } // resource bitmaps are imutable, // so we need to convert it to mutable one bitmap = bitmap.copy(bitmapConfig, true); Canvas canvas = new Canvas(bitmap); // new antialised Paint Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); // text color - #3D3D3D paint.setColor(Color.WHITE); // text size in pixels paint.setTextSize((int) (25 * scale)); // text shadow paint.setShadowLayer(1f, 0f, 1f, Color.WHITE); // draw text to the Canvas center Rect bounds = new Rect(); int noOfLines = 0; for (String line: gText.split("\n")) { noOfLines++; } paint.getTextBounds(gText, 0, gText.length(), bounds); int x = 20; int y = (bitmap.getHeight() - bounds.height()*noOfLines); Paint mPaint = new Paint(); mPaint.setColor(getResources().getColor(R.color.transparentBlack)); int left = 0; int top = (bitmap.getHeight() - bounds.height()*(noOfLines+1)); int right = bitmap.getWidth(); int bottom = bitmap.getHeight(); canvas.drawRect(left, top, right, bottom, mPaint); for (String line: gText.split("\n")) { canvas.drawText(line, x, y, paint); y += paint.descent() - paint.ascent(); } return bitmap; } } 

Sí. Use canvas.getFontSpacing() como el incremento. Lo he probado por curiosidad y funciona para cualquier tamaño de fuente.

Reutilicé la solución propuesta por GreenBee y realicé una función para dibujar texto de varias líneas en límites específicos con el “…” al final si ocurría un truncamiento:

 public static void drawMultiLineEllipsizedText(final Canvas _canvas, final TextPaint _textPaint, final float _left, final float _top, final float _right, final float _bottom, final String _text) { final float height = _bottom - _top; final StaticLayout measuringTextLayout = new StaticLayout(_text, _textPaint, (int) Math.abs(_right - _left), Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); int line = 0; final int totalLineCount = measuringTextLayout.getLineCount(); for (line = 0; line < totalLineCount; line++) { final int lineBottom = measuringTextLayout.getLineBottom(line); if (lineBottom > height) { break; } } line--; if (line < 0) { return; } int lineEnd; try { lineEnd = measuringTextLayout.getLineEnd(line); } catch (Throwable t) { lineEnd = _text.length(); } String truncatedText = _text.substring(0, Math.max(0, lineEnd)); if (truncatedText.length() < 3) { return; } if (truncatedText.length() < _text.length()) { truncatedText = truncatedText.substring(0, Math.max(0, truncatedText.length() - 3)); truncatedText += "..."; } final StaticLayout drawingTextLayout = new StaticLayout(truncatedText, _textPaint, (int) Math.abs(_right - _left), Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); _canvas.save(); _canvas.translate(_left, _top); drawingTextLayout.draw(_canvas); _canvas.restore(); } 

prueba esto

 Paint paint1 = new Paint(); paint1.setStyle(Paint.Style.FILL); paint1.setAntiAlias(true); paint1.setColor(Color.BLACK); paint1.setTextSize(15); TextView tv = new TextView(context); tv.setTextColor(Color.BLACK); LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); llp.setMargins(5, 2, 0, 0); // llp.setMargins(left, top, right, bottom); tv.setLayoutParams(llp); tv.setTextSize(10); String text="this is good to see you , i am the king of the team"; tv.setText(text); tv.setDrawingCacheEnabled(true); tv.measure(MeasureSpec.makeMeasureSpec(canvas.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(canvas.getHeight(), MeasureSpec.EXACTLY)); tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight()); canvas.drawBitmap(tv.getDrawingCache(), 5, 10, paint1); tv.setDrawingCacheEnabled(false); 

funcionará. probé

  public Bitmap drawMultilineTextToBitmap(Context gContext, int gResId, String gText) { // prepare canvas Resources resources = gContext.getResources(); float scale = resources.getDisplayMetrics().density; Bitmap bitmap = BitmapFactory.decodeResource(resources, gResId); android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig(); // set default bitmap config if none if(bitmapConfig == null) { bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888; } // resource bitmaps are imutable, // so we need to convert it to mutable one bitmap = bitmap.copy(bitmapConfig, true); Canvas canvas = new Canvas(bitmap); // new antialiased Paint TextPaint paint=new TextPaint(Paint.ANTI_ALIAS_FLAG); // text color - #3D3D3D paint.setColor(Color.rgb(61, 61, 61)); // text size in pixels paint.setTextSize((int) (14 * scale)); // text shadow paint.setShadowLayer(1f, 0f, 1f, Color.WHITE); // set text width to canvas width minus 16dp padding int textWidth = canvas.getWidth() - (int) (16 * scale); // init StaticLayout for text StaticLayout textLayout = new StaticLayout( gText, paint, textWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); // get height of multiline text int textHeight = textLayout.getHeight(); // get position of text's top left corner float x = (bitmap.getWidth() - textWidth)/2; float y = (bitmap.getHeight() - textHeight)/2; // draw text to the Canvas center canvas.save(); canvas.translate(x, y); textLayout.draw(canvas); canvas.restre(); return bitmap; } 

Fuente: http://www.skoumal.net/en/android-drawing-multiline-text-on-bitmap/

Solución sin StaticLayout

 //Get post text String text = post.getText(); //Get weight of space character in px float spaceWeight = paint.measureText(" "); //Start main algorithm of drawing words on canvas //Split text to words for (String line : text.split(" ")) { //If we had empty space just continue if (line.equals("")) continue; //Get weight of the line float lineWeight = paint.measureText(line); //If our word(line) doesn't have any '\n' we do next if (line.indexOf('\n') == -1) { //If word can fit into current line if (cnv.getWidth() - pxx - defaultMargin >= lineWeight) { //Draw text cnv.drawText(line, pxx, pxy, paint); //Move start x point to word weight + space weight pxx += lineWeight + spaceWeight; } else { //If word can't fit into current line //Move x point to start //Move y point to the next line pxx = defaultMargin; pxy += paint.descent() - paint.ascent(); //Draw cnv.drawText(line, pxx, pxy, paint); //Move x point to word weight + space weight pxx += lineWeight + spaceWeight; } //If line contains '\n' } else { //If '\n' is on the start of the line if (line.indexOf('\n') == 0) { pxx = defaultMargin; pxy += paint.descent() - paint.ascent(); cnv.drawText(line.replaceAll("\n", ""), pxx, pxy, paint); pxx += lineWeight + spaceWeight; } else { //If '\n' is somewhere in the middle //and it also can contain few '\n' //Split line to sublines String[] subline = line.split("\n"); for (int i = 0; i < subline.length; i++) { //Get weight of new word lineWeight = paint.measureText(subline[i]); //If it's empty subline that's mean that we have '\n' if (subline[i].equals("")) { pxx = defaultMargin; pxy += paint.descent() - paint.ascent(); cnv.drawText(subline[i], pxx, pxy, paint); continue; } //If we have only one word if (subline.length == 1 && i == 0) { if (cnv.getWidth() - pxx >= lineWeight) { cnv.drawText(subline[0], pxx, pxy, paint); pxx = defaultMargin; pxy += paint.descent() - paint.ascent(); } else { pxx = defaultMargin; pxy += paint.descent() - paint.ascent(); cnv.drawText(subline[0], pxx, pxy, paint); pxx = defaultMargin; pxy += paint.descent() - paint.ascent(); } continue; } //If we have set of words separated with '\n' //it is the first word //Make sure we can put it into current line if (i == 0) { if (cnv.getWidth() - pxx >= lineWeight) { cnv.drawText(subline[0], pxx, pxy, paint); pxx = defaultMargin; } else { pxx = defaultMargin; pxy += paint.descent() - paint.ascent(); cnv.drawText(subline[0], pxx, pxy, paint); pxx = defaultMargin; } } else { pxx = defaultMargin; pxy += paint.descent() - paint.ascent(); cnv.drawText(subline[i], pxx, pxy, paint); pxx += lineWeight + spaceWeight; } } } } } 

Mi ejemplo con el tamaño y espaciado de texto dynamic, funciona muy bien para mí …

 public Bitmap fontTexture(String string, final Context context) { float text_x = 512; float text_y = 512; final float scale = context.getResources().getDisplayMetrics().density; int mThreshold = (int) (THRESHOLD_DIP * scale + 0.5f); String[] splited = string.split("\\s+"); double longest = 0; for(String s:splited){ if (s.length() > longest) { longest = s.length(); } } if(longest > MAX_STRING_LENGTH) { double ratio = (double) MAX_STRING_LENGTH / longest; mThreshold = (int) ((THRESHOLD_DIP * ((float) ratio)) * scale + 0.5f); } Bitmap bitmap = Bitmap.createBitmap(1024, 1024, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/dotted_font.ttf"); TextPaint mTextPaint=new TextPaint(); mTextPaint.setColor(Color.YELLOW); mTextPaint.setTextAlign(Paint.Align.CENTER); mTextPaint.setTextSize(mThreshold); mTextPaint.setTypeface(font); StaticLayout mTextLayout = new StaticLayout(string, mTextPaint, canvas.getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); canvas.save(); canvas.translate(text_x, text_y); mTextLayout.draw(canvas); canvas.restre(); return bitmap; }