I'm working on an Android app utilizing xamarin and the oxyplot library. I ran into a problem where I cannot add multiple lines of text in TextAnnotation.
I tried the following options:
var sb = new StringBuilder();
sb.Append("Line1");
sb.AppendLine(); // which is equal to Append(Environment.NewLine);
sb.Append("\n");
sb.Append("\r\n");
sb.Append(System.Environment.NewLine);
sb.Append("Line2");
and added it like so to the TextAnnotation text object:
var textAnnotation1 = new TextAnnotation();
textAnnotation1.Text = sb.ToString();
textAnnotation1.Background = OxyColors.White;
textAnnotation1.TextColor = OxyColors.Black;
textAnnotation1.FontSize = 18;
textAnnotation1.TextPosition = new DataPoint(4,_vericalLineYaxis);
plotModel.Annotations.Add(textAnnotation1);
but all to avail.
My goal is to have the text appear like so:
Line 1
Line 2
Currently it's appearing as:
Line 1 Line2
Any help would be much appreciated.
Multi-line Annotations is not currently supported on the Android platform.
OxyPlot is invoking Android.Canvas.DrawText and that function does not support text wrapping, it is a fairly low-level primitive drawing routine.
Google's Doc: public void drawText (String text, float x, float y, Paint paint)
If you feel like mod'ing the source, this could be done by using a static layout vs. the current canvas.DrawText.
Something like this would get you started (but is untested):
public void DrawText (string text, float x, float y, Paint paint)
{
TextPaint textPaint = new TextPaint();
StaticLayout textLayout = new StaticLayout(text, textPaint, canvas.getWidth(), Alignment.ALIGN_NORMAL, 1.0f, 0.0f, False);
canvas.Save();
canvas.Translate(x, y);
textLayout.Draw(canvas);
canvas.Restore();
}
FYI: Oxyplot's SVG renderer manually handles multi-line text by string splitting on "\r\n" and rendering a separate element for each line so the same thing could be done for the Android instead of using a StaticLayout (slower performance wise, but easy to mod/test):
var lines = Regex.Split(text, "\r\n");
if (valign == VerticalAlignment.Bottom)
{
for (var i = lines.Length - 1; i >= 0; i--)
{
var line = lines[i];
var size = this.MeasureText(line, fontFamily, fontSize, fontWeight);
this.w.WriteText(p, line, c, fontFamily, fontSize, fontWeight, rotate, halign, valign);
p += new ScreenVector(Math.Sin(rotate / 180.0 * Math.PI) * size.Height, Math.Cos(rotate / 180.0 * Math.PI) * size.Height);
}
}
Related
I've got a problem with a custom view i use. It draws a grid that i use to represent a floorplan, with a start and current position on it (colored rectangles).
(Code here: https://pastebin.com/8SExmtAp).
In short, i initialize different paints like this:
private void initPaints()
{
waypointPaint = new Paint(Color.parseColor("#800080"));
currentCoordinatePaint = new Paint(Color.RED);
linePaint = new Paint(Color.BLACK);
startCoordinatePaint = new Paint(Color.BLUE);
}
and use them in onDraw() like this:
// color the current coordinates
Coordinates currentCoords = Model.getCurrentCoordinates();
if (currentCoords != null)
{
canvas.drawRect((float) currentCoords.getX() * cellWidth, (float) currentCoords.getY() * cellHeight,
(float) (currentCoords.getX() + 1) * cellWidth, (float) (currentCoords.getY() + 1) * cellHeight,
currentCoordinatePaint);
}
Coordinates startCoordinate = Model.startCoordinate;
if (startCoordinate != null && startCoordinate != currentCoords)
{
canvas.drawRect((float) startCoordinate.getX() * cellWidth, (float) startCoordinate.getY() * cellHeight,
(float) (startCoordinate.getX() + 1) * cellWidth, (float) (startCoordinate.getY() + 1) * cellHeight,
startCoordinatePaint);
}
However, instead of getting a blue one for the startposition and a red one for the current position, both of them are black, see:
Screenshot of app
The documentation on the drawRect(...) Method i use just states the following:
Draw the specified Rect using the specified paint. The rectangle will be filled or framed based on the Style in the paint.
So..i don't really see where the code is wrong and why i am getting the result i get. Maybe someone of you knows why?
Paint constructor you are using expects int flags as a parameter, not the fill color.
Try:
currentCoordinatePaint = new Paint();
currentCoordinatePaint.setStyle(Paint.Style.FILL);
currentCoordinatePaint.setColor(Color.RED);
Like josef.adamcik statet, i was wrong about the constructors i used for the paint objects. Changing the code to
private void initPaints()
{
waypointPaint = new Paint();
waypointPaint.setColor(Color.GREEN);
waypointPaint.setStyle(Paint.Style.FILL);
currentCoordinatePaint = new Paint();
currentCoordinatePaint.setColor(Color.RED);
currentCoordinatePaint.setStyle(Paint.Style.FILL);
linePaint = new Paint();
linePaint.setColor(Color.BLACK);
linePaint.setStyle(Paint.Style.STROKE);
startCoordinatePaint = new Paint();
startCoordinatePaint.setColor(Color.BLUE);
startCoordinatePaint.setStyle(Paint.Style.FILL);
}
did the trick.
I'm trying to do something like this, but I have a little bit of flexibility with how it looks. Essentially either a pie chart with only part of the pie filled (and the rest left blank), or some sort of dial chart.
It would also be relatively easy to use a polar graph to draw two arrows, one at 0 degrees and one at -92 degrees, but I can't find any libraries that will let you do this for Android. I do need it to make 0 degrees actually look like 0 polar degrees.
I've used an AChartEngine DialChart and managed to get something close, but I can't figure out how to get the labels to show up for each arrow. I've tried renderer.setDisplayValues(true); and series.setDisplayChartValues(true);
but it won't show the values for my two arrows, so I'm not sure if it's even possible with a DialChart. I realize that if I showed labels for the dial in the background, my users wouldn't need to have labels on the arrows, but I'm rotating the LinearLayout that the DialChart is added to in order to get 0 to look like 0 degrees in a polar graph. I am also struggling to hide labels for the dial in the background, despite using renderer.setShowLabels(false); and setting just about every other thing you can show to false. My hack is to set the label color to the background color, but if there is a better way to do it, please let me know.
Here is my code for the DialChart.
CategorySeries category = new CategorySeries("Angle");
category.add("Extension", 0);
category.add("Flexion", 90);
renderer = new DialRenderer();
renderer.setLabelsColor(getActivity().getResources().getColor(R.color.background));
renderer.setInScroll(true);
renderer.setDisplayValues(true);
renderer.setShowLegend(false);
renderer.setShowAxes(false);
renderer.setShowLabels(false);
renderer.setShowGrid(false);
renderer.setMargins(new int[] {20, 30, 15, 0});
renderer.setVisualTypes(new DialRenderer.Type[] {Type.ARROW, Type.ARROW});
renderer.setMinValue(-20);
renderer.setMaxValue(280);
renderer.setPanEnabled(false);
renderer.setZoomEnabled(false);
SimpleSeriesRenderer r = new SimpleSeriesRenderer();
series.setColor(getActivity().getResources().getColor(R.color.green));
series.setDisplayChartValues(true);
series.setChartValuesTextSize(30);
visualizationRenderer.addSeriesRenderer(r);
r = new SimpleSeriesRenderer();
series.setColor(getActivity().getResources().getColor(R.color.green));
series.setDisplayChartValues(true);
series.setChartValuesTextSize(30);
renderer.addSeriesRenderer(r);
visualization = ChartFactory.getDialChartView(getActivity(), category, renderer);
LinearLayout layout = (LinearLayout) this.getView().findViewById(R.id.sessions_visualization);
layout.addView(visualization);
layout.setRotation(220.0f);
I'm open to either modifying this code to get something that works, or other libraries that will help me accomplish what I'm trying to do. Thanks!
I'm answering my own question for anyone who wants to do something like this later.
You can create custom views in Android and draw whatever you want to display. There is good documentation here.
Here's a relevant code snippet. It's not perfect but it does the job.
public class AngleVisualization extends View {
private Paint textPaint;
private Paint arcPaint;
private Paint linePaint;
RectF oval;
private float extension;
private float flexion;
private int textColor;
private int arcColor;
private float extensionLabelX;
private float extensionLabelY;
private float flexionLabelX;
private float flexionLabelY;
private Rect extensionBounds = new Rect();
public AngleVisualization(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.AngleVisualization,
0, 0);
try {
extension = a.getFloat(R.styleable.AngleVisualization_extensionValue, 0);
flexion = a.getFloat(R.styleable.AngleVisualization_flexionValue, 0);
textColor = a.getColor(R.styleable.AngleVisualization_textColor, Color.BLACK);
arcColor = a.getColor(R.styleable.AngleVisualization_arcColor, context.getResources().getColor(R.color.green));
extensionLabelX = a.getDimension(R.styleable.AngleVisualization_extensionLabelX, 190);
extensionLabelY = a.getDimension(R.styleable.AngleVisualization_extensionLabelY, 150);
flexionLabelX = a.getDimension(R.styleable.AngleVisualization_flexionLabelX, 50);
extensionLabelY = a.getDimension(R.styleable.AngleVisualization_flexionLabelY, 190);
} finally {
a.recycle();
}
oval = new RectF();
init();
}
private void init() {
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(textColor);
textPaint.setTextSize(30);
arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
arcPaint.setColor(arcColor);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(arcColor);
linePaint.setStrokeWidth(3);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String extensionString = decimalFormat.format(extension) + "˚";
textPaint.getTextBounds(extensionString, 0, extensionString.length(), extensionBounds);
canvas.drawArc(oval, extension, flexion - extension, true, arcPaint);
canvas.drawLine(0.0f, extensionBounds.height(), oval.right / 2, extensionBounds.height(), linePaint);
canvas.drawText(extensionString, extensionLabelX, extensionLabelY, textPaint);
canvas.drawText(decimalFormat.format(flexion) + "˚", flexionLabelX, flexionLabelY, textPaint);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// Account for padding
float xpad = (float)(getPaddingLeft() + getPaddingRight());
float ypad = (float)(getPaddingTop() + getPaddingBottom());
float ww = (float)w - xpad;
float hh = (float)h - ypad;
String extensionString = decimalFormat.format(extension) + "˚";
textPaint.getTextBounds(extensionString, 0, extensionString.length(), extensionBounds);
float diameter = Math.min(ww, (hh - extensionBounds.height()) * 2.0f) - extensionBounds.height();
oval = new RectF(
0,
diameter / -2.0f,
diameter,
diameter / 2.0f);
oval.offsetTo(getPaddingLeft(), getPaddingTop() - diameter / 2.0f + extensionBounds.height());
flexionLabelY = diameter / 2.0f + extensionBounds.height();
flexionLabelX = 0;
extensionLabelY = extensionBounds.height();
extensionLabelX = ww / 2;
}
}
I have a small problem with ploting my graph. On a picture below is what I have already done.
The graph should represent the actual signal strength of available Wi-Fi network(s). It's a simple XYPlot here data are represented with SimpleXYSeries (values are dynamically created).
Here is a little snippet of code (only for example):
plot = (XYPlot) findViewById(R.id.simplexyPlot);
series1 = new SimpleXYSeries(Arrays.asList(series1Numbers),
SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Link 1");
f1 = new LineAndPointFormatter(color.getColor(), null,
Color.argb(60, color.getRed(), color.getGreen(), color.getBlue()), null);
plot.addSeries(series1, f1);
The example in the picture is a dynamic simulation of dB changes. Everything works, I guess, correctly, but what I want to achieve is to have line with "rounded" corners (see the picture to see what I mean).
I already tried to customize LineFormatter:
f1.getFillPaint().setStrokeJoin(Join.ROUND);
f1.getFillPaint().setStrokeWidth(8);
But this didn't work as expected.
Note: The Wifi Analyzer application has a similar graph and its graph has the rounded corners I want. It looks like this:
You can use Path.cubicTo() method. It draws a line using cubic spline algorithm which results in the smoothing effect you want.
Checkout the answer to a similar question here, where a guy is talking about cubic splines. There is a short algorithm showing how to calculate input parameters for Path.cubicTo() method. You can play with divider values to achieve required smoothness. For example, in the picture below I divided by 5 instead of 3. Hope this helps.
I have spent some time and implemented a SplineLineAndPointFormatter class, which does the stuff you need in androidplot library. It uses same technics. Here is how androidplot example applications looks like. You just need to use it instead of LineAndPointFormatter.
Here is code example and the class I wrote.
f1 = new SplineLineAndPointFormatter(color.getColor(), null,
Color.argb(60, color.getRed(), color.getGreen(), color.getBlue()), null);
plot.addSeries(series1, f1);
Here is the class doing the magic. It is based on version 0.6.1 of androidplot library.
package com.androidplot.xy;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import com.androidplot.ui.SeriesRenderer;
import com.androidplot.util.ValPixConverter;
public class SplineLineAndPointFormatter extends LineAndPointFormatter {
public SplineLineAndPointFormatter() { }
public SplineLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor) {
super(lineColor, vertexColor, fillColor, null);
}
public SplineLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor, FillDirection fillDir) {
super(lineColor, vertexColor, fillColor, null, fillDir);
}
#Override
public Class<? extends SeriesRenderer> getRendererClass() {
return SplineLineAndPointRenderer.class;
}
#Override
public SeriesRenderer getRendererInstance(XYPlot plot) {
return new SplineLineAndPointRenderer(plot);
}
public static class SplineLineAndPointRenderer extends LineAndPointRenderer<BezierLineAndPointFormatter> {
static class Point {
public float x, y, dx, dy;
public Point(PointF pf) { x = pf.x; y = pf.y; }
}
private Point prev, point, next;
private int pointsCounter;
public SplineLineAndPointRenderer(XYPlot plot) {
super(plot);
}
#Override
protected void appendToPath(Path path, final PointF thisPoint, PointF lastPoint) {
pointsCounter--;
if (point == null) {
point = new Point(thisPoint);
point.dx = ((point.x - prev.x) / 5);
point.dy = ((point.y - prev.y) / 5);
return;
} else if (next == null) {
next = new Point(thisPoint);
} else {
prev = point;
point = next;
next = new Point(thisPoint);
}
point.dx = ((next.x - prev.x) / 5);
point.dy = ((next.y - prev.y) / 5);
path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y);
if (pointsCounter == 1) { // last point
next.dx = ((next.x - point.x) / 5);
next.dy = ((next.y - point.y) / 5);
path.cubicTo(point.x + point.dx, point.y + point.dy, next.x - next.dx, next.y - next.dy, next.x, next.y);
}
}
#Override
protected void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAndPointFormatter formatter) {
Number y = series.getY(0);
Number x = series.getX(0);
if (x == null || y == null) throw new IllegalArgumentException("no null values in xyseries permitted");
XYPlot p = getPlot();
PointF thisPoint = ValPixConverter.valToPix(x, y, plotArea,
p.getCalculatedMinX(), p.getCalculatedMaxX(), p.getCalculatedMinY(), p.getCalculatedMaxY());
prev = new Point(thisPoint);
point = next = null;
pointsCounter = series.size();
super.drawSeries(canvas, plotArea, series, formatter);
}
}
}
1- I guess that you only use a few points to draw graphs of signals. All graph/chart applications try to connect points with direct lines and then your chart will be shown. So if you only use three points, your graph will looks like a triangle! If you want your graph to be curved, you have to add more points. Then it comes out like a curve.
2- Or you can find any library that can draw sin graph, for example GraphView Library. Then try to draw this function:
So it looks like to this:
Then translate it to (a,0), so result seems like what you want.
3- And another way, you can use built in Math.sin in Java:
Chose for example 1000 point in range a to b and compute value of above function for each point and finally create a path and show them in a canvas.
You can use quadTo (float x1, float y1, float x2, float y2) that simplify drawing quad curves for you. The documentation says:
Add a quadratic bezier from the last point, approaching control point
(x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
this contour, the first point is automatically set to (0,0).
Parameters
x1 The x-coordinate of the control point on a quadratic curve
y1 The y-coordinate of the control point on a quadratic curve
x2 The x-coordinate of the end point on a quadratic curve
y2 The y-coordinate of the end point on a quadratic curve
Finally, I add a simple class that extends View and can draw a curve that looks like what you want:
public class SinWave extends View {
private float first_X = 50;
private float first_Y = 230;
private float end_X = 100;
private float end_Y = 230;
private float Max = 50;
public SinWave(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint() {
{
setStyle(Paint.Style.STROKE);
setStrokeCap(Paint.Cap.ROUND);
setStrokeWidth(0.7f);
setAntiAlias(true);
setColor(0xFFFF00FF);
}
};
final Path path = new Path();
path.moveTo(first_X, first_Y);
path.quadTo((first_X + end_X)/2, Max, end_X, end_Y);
canvas.drawPath(path, paint);
}
}
The result must look like this:
You can add more methods to the class and change it to increase performance!
There's always been a smooth line renderer in Androidplot: BezierLineAndPointRenderer, which like the implementations above uses Android's built in Bezier drawing routines cubicTo(...) & quadTo(...). The problem is that using Beziers to draw smooth lines in this way creates a false line that overshoots the actual control points by varying amounts, which you can see happening if you look closely at the image above.
The solution is to use the Catmull-Rom spline interpolation, which is now finally supported by Androidplot. Details here: http://androidplot.com/smooth-curves-and-androidplot/
Just use ChartFactory.getCubeLineChartView instead of ChartFactory.getLineChartView using achart engine
In some simple cases, this could help:
mPaint.pathEffect = CornerPathEffect(radius)
even in combination with
path.lineTo(x,y)
try this:
symbol = new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(2);
paint.setColor(-7829368);
paint.setStrokeJoin(Paint.Join.ROUND); // set the join to round you want
paint.setStrokeCap(Paint.Cap.ROUND); // set the paint cap to round too
paint.setPathEffect(new CornerPathEffect(10) );
paint.setStyle(Paint.Style.STROKE);
symbol.moveTo(50.0F, 230.0F);
symbol.lineTo(75.0F, 100.0F);
symbol.lineTo(100.0F, 230.0F);
most of the info found here
I have an app that handels Arabic too, but my Arabic users have a problem that the drawText flip the word .. Arabic must be from right to left. How do I make the canvas drawText from right to left?
See in the picture the highlighted text is the right text its a textView and it's fine. But the canvas DrawText the one in a circle is wrong. It must be from right to left, how do I make the canvas drawText from right to left?
On the canvas just create two points on sides where you want to draw text, and then create path between them. use this method it will work fine
Path path = new Path();
Paint paint = new Paint();
path.moveTo(p2.x, p2.y);
path.lineTo(p1.x, p1.y);
canvas.drawTextOnPath(String.valueOf(txt), path, (float) (c.getWidth() / (2.3)), (float) (c.getHeight()/2 + paint.getTextSize()/1.5), paint);
you can get subString from your string and draw in your canvas:
Paint textPaint = new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
textPaint.setTextSize(20);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setStrokeWidth(1);
String subString = mString;
float textWidth = textPaint.measureText(mString);
int endOffset = Math.round(rectWidth * (mString.length() - 1) / textWidth);
if (textWidth > rectWidth) {
endOffset =endOffset - 2;
subString = mString.substring(0, endOffset);
subString = subString + "..";
}else{
for(int j=mString.length();j<endOffset+1;j++){
subString+=" ";
}
}
canvas.drawText(subString, padding , (float) (startHeight + eachHeight / 3 + textPaint.getTextSize() / 1.5), textPaint);
in this way we have a same result even in RTL or LTR string .
Make sure that Android emulator that contains the Arabic language, I had the same problem but when I tried the application on an actual mobile device,It solved.
There are no problems in your application in the language, make sure Android emulator supports the Arabic language
If your target device is api level greater than 11 you can use rotateY=180 attribute in TextView element. Also the parent view should set to rotateY = 180.
I would like to get height too if possible.
You can use the getTextBounds(String text, int start, int end, Rect bounds) method of a Paint object. You can either use the paint object supplied by a TextView or build one yourself with your desired text appearance.
Using a Textview you Can do the following:
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
int height = bounds.height();
int width = bounds.width();
If you just need the width you can use:
float width = paint.measureText(string);
http://developer.android.com/reference/android/graphics/Paint.html#measureText(java.lang.String)
There are two different width measures for a text. One is the number of pixels which has been drawn in the width, the other is the number of 'pixels' the cursor should be advanced after drawing the text.
paint.measureText and paint.getTextWidths returns the number of pixels (in float) which the cursor should be advanced after drawing the given string. For the number of pixels painted use paint.getTextBounds as mentioned in other answer. I believe this is called the 'Advance' of the font.
For some fonts these two measurements differ (alot), for instance the font Black Chancery have letters which extend past the other letters (overlapping) - see the capital 'L'. Use paint.getTextBounds as mentioned in other answer to get pixels painted.
I have measured width in this way:
String str ="Hiren Patel";
Paint paint = new Paint();
paint.setTextSize(20);
Typeface typeface = Typeface.createFromAsset(getAssets(), "Helvetica.ttf");
paint.setTypeface(typeface);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
Rect result = new Rect();
paint.getTextBounds(str, 0, str.length(), result);
Log.i("Text dimensions", "Width: "+result.width());
This would help you.
Most likely you want to know the painted dimensions for a given string of text with a given font (i.e. a particular Typeface such as the “sans-serif” font family with a BOLD_ITALIC style, and particular size in sp or px).
Rather than inflating a full-blown TextView, you can go lower level and work with a Paint object directly for single-line text, for example:
// Maybe you want to construct a (possibly static) member for repeated computations
Paint paint = new Paint();
// You can load a font family from an asset, and then pick a specific style:
//Typeface plain = Typeface.createFromAsset(assetManager, pathToFont);
//Typeface bold = Typeface.create(plain, Typeface.DEFAULT_BOLD);
// Or just reference a system font:
paint.setTypeface(Typeface.create("sans-serif",Typeface.BOLD));
// Don't forget to specify your target font size. You can load from a resource:
//float scaledSizeInPixels = context.getResources().getDimensionPixelSize(R.dimen.mediumFontSize);
// Or just compute it fully in code:
int spSize = 18;
float scaledSizeInPixels = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
spSize,
context.getResources().getDisplayMetrics());
paint.setTextSize(scaledSizeInPixels);
// Now compute!
Rect bounds = new Rect();
String myString = "Some string to measure";
paint.getTextBounds(myString, 0, myString.length(), bounds);
Log.d(TAG, "width: " + bounds.width() + " height: " + bounds.height());
For multi-line or spanned text (SpannedString), consider using a StaticLayout, in which you provide the width and derive the height. For
a very elaborate answer on measuring and drawing text to a canvas in a custom view doing that, see: https://stackoverflow.com/a/41779935/954643
Also worth noting #arberg's reply below about the pixels painted vs the advance width ("number of pixels (in float) which the cursor should be advanced after drawing the given string"), in case you need to deal with that.
I'd like to share a better way (more versatile then the current accepted answer) of getting the exact width of a drawn text (String) with the use of static class StaticLayout:
StaticLayout.getDesiredWidth(text, textPaint))
this method is more accurate than textView.getTextBounds(), since you can calculate width of a single line in a multiline TextView, or you might not use TextView to begin with (for example in a custom View implementation).
This way is similar to textPaint.measureText(text), however it seems to be more accurate in rare cases.
simplay i tack max charcter in the line and defieded it with max space and create new line
v_y = v_y + 30;
String tx = "مبلغ وقدرة : "+ AmountChar+" لا غير";
myPaint.setTextAlign(Paint.Align.RIGHT);
int pxx = 400;
int pxy = v_y ;
int word_no = 1;
int word_lng = 0;
int max_word_lng = 45;
int new_line = 0;
int txt_lng = tx.length();
int words_lng =0;
String word_in_line = "" ;
for (String line : tx.split(" "))
{
word_lng = line.length() ;
words_lng += line.length() + 1;
if (word_no == 1 )
{word_in_line = line;
word_no += 1;
}
else
{ word_in_line += " " + line;
word_no += 1;
}
if (word_in_line.length() >= max_word_lng)
{
canvas.drawText(word_in_line, pxx, pxy, myPaint);
new_line += 1;
pxy = pxy + 30;
word_no = 1;
word_in_line = "";
}
if (txt_lng <= words_lng )
{ canvas.drawText(word_in_line, pxx, pxy, myPaint); }
}
v_y = pxy;