Is it possible to get each line of a multiline text as it will appear in a textview of a fixed width, with a font, size, alignment without actually drawing the textview ?
My Text is stored in a string. I can apply different font, size, alignment for the text. When passing it to my api, I want to split it into a list of Strings just how it was displayed in the app. Is this possible ?
In the view the text is formatted using the following:
TextView textView = new TextView(getContext());
LayoutParams textLayoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (itemWidth > 0) {
textLayoutParams.width = itemWidth;
}
if (itemHeight == -1) {
textLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
}
textLayoutParams.leftMargin = xPosition;
textLayoutParams.topMargin = yPosition;
textView.setLayoutParams(textLayoutParams);
textView.setPaintFlags(Paint.ANTI_ALIAS_FLAG);
font = getFontName(font);
if (!StringUtils.isEmpty(font)) {
CalligraphyUtils.applyFontToTextView(getContext(), textView, CalligraphyConfig.get(), "fonts/" + font);
}
if (!StringUtils.isEmpty(color))
textView.setTextColor(Color.parseColor(color));
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int)size );
if (alignment.equalsIgnoreCase("center")) {
textView.setGravity(Gravity.CENTER);
} else if (alignment.equalsIgnoreCase("left")) {
textView.setGravity(Gravity.START);
} else if (alignment.equalsIgnoreCase("right")) {
textView.setGravity(Gravity.END);
} else if (alignment.equalsIgnoreCase("left_center")) {
textView.setGravity(Gravity.START | Gravity.CENTER);
}
addView(textView);
For multi line strings, it will display on different lines. In a background process, I want to know which substring was displayed on 1st line, which substring was displayed on second line and so on. Is this even possible ?
Related
I'm trying to make an android ESC/POS printer editor UI. ESC/POS has few options when printing text and they can be mixed together: small, underlined, bold, double width(wide), double height(tall).
While the ESC/POS printer part is no problem, I have some issues while trying to represent it with text views in android UI.
while 'small' could be solved changing the font size, i'm trying to achieve
wide and tall by setting View.setScaleX(1.5f) and View.setScaleY(1.5f).
I am creating those views programmaticaly and adding them to a linearLayout container.
After changing scale the view bounds does not get updated. and wide option goes out of view bounds. I have tried to use view.invalidate() on view and its parent thinking it would recalculate bounds but it did not work.
onClick(View v) {
PrinterCommands.FormatedText textComand = new PrinterCommands.FormatedText("Hello world");
textComand.setTall(r.nextBoolean());
textComand.setUnderlined(r.nextBoolean());
textComand.setBold(r.nextBoolean());
textComand.setWide(r.nextBoolean());
textComand.setSmall(r.nextBoolean());
TextView textView = new TextView(CommandsViewActivity.this);
StringBuilder sb = new StringBuilder("Hello world");
// v.setText(String.valueOf(position));
if (textComand.isSmall()) {
sb.append(" small");
textView.setScaleX(0.5f);
textView.setScaleY(0.5f);
}
if (textComand.isBold()) {
sb.append(" bold");
textView.setTypeface(null, Typeface.BOLD);
}
if (textComand.isTall()) {
sb.append(" tall");
textView.setScaleY(1.5f);
}
if (textComand.isWide()){
sb.append(" wide");
textView.setScaleX(1.5f);
}
if (textComand.isUnderlined()) {
sb.append(" underlined");
SpannableString s = new SpannableString(sb.toString());
s.setSpan(new UnderlineSpan(), 0, s.length(), 0);
textView.setText(s);
} else textView.setText(sb.toString());
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textView.setGravity(Gravity.START|Gravity.CENTER);
p.setMargins(16,16,16,16);
textView.setLayoutParams(p);
container.addView(textView);
}
});
Layout output I am getting
I have multiline TextView, I need to format only single line of it. Since font size and style is dynamic I need it to format "first line" automatically.
This TextView is displaying a string without linefeed, it is wrapped automatically, I need to format first wrapped line.
Please check attached image:
I found the solution that uses StaticLayout.
private TextView tvTitle; //bold title with maxLines = 1
private TextView tvText; //the rest of the text
//initialize views, etc...
private void showText(#Nonnull String text) {
tvTitle.setText(text);
tvTitle.post(() -> {
String ellipsizedText = getEllipsizedText(text, tvTitle);
tvText.setText(ellipsizedText);
});
}
private static String getEllipsizedText(String text, TextView textView) {
StaticLayout staticLayout = getStaticLayout(text,
textView.getPaint(),
textView.getWidth(),
1,
null);
return text.substring(staticLayout.getLineEnd(0));
}
private static StaticLayout getStaticLayout(CharSequence text,
TextPaint paint,
int width,
int maxLines,
TextUtils.TruncateAt ellipsize) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return StaticLayout.Builder.obtain(text, 0, text.length(), paint, width)
.setTextDirection(TextDirectionHeuristics.FIRSTSTRONG_LTR)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLineSpacing(0f, 1f)
.setIncludePad(false)
.setEllipsize(ellipsize)
.setEllipsizedWidth(width)
.setMaxLines(maxLines)
.build();
} else {
return new StaticLayout(text,
paint,
width,
Layout.Alignment.ALIGN_NORMAL,
1f,
0f,
false);
}
}
from #HiteshGehlot's answer:
myTextView.setText(Html.fromHtml("<b>This is a normal</b><br>second line<br>third line"));
but as your system is dynamic, this is not a good way to do it. This code generates the appropriate HTML:
String lines[] = string.split("\\n");
String fl = String.format(Locale.ENGLISH, "<b>%s</b><br>", lines[0]);
StringBuilder sb = new StringBuilder();
sb.append(fl);
for(int i = 1; i < lines.length; i++){
sb.append(lines[i] + "<br>");
}
String finalProduct = sb.toString();
then:
myTextView.setText(Html.fromHtml(finalProduct));
This should dynamically create the HTML tags for the first line
EXPLANATION
This is an improvement from a previous answer. Using HTML, the first line is formatted as bold.
Using regex, we split all the lines by newline(\n). The first String in the array is the first line, while the rest aren't important to format. So first we append the first line, and add the rest after that. The for-loop starts at 1 since 0 is the first line.
NOTE
If you do break the text dynamically, you pass that String into the method above. The method I have presented here uses those linebreaks to figure out which line is where, to be able to set the first line to be bold
try this:
myTextView.setText(Html.fromHtml("<b>This is a normal</b><br>second line<br>third line"));
Just use another TextView.
TextView1, TextView2, TextView3.
And set TextView1 to bold.
I have this layout
<ScrollView>
<TextView android:id="#+id/textView" />
</ScrollView>
When I try to set a too long text, this Textview doesn't show that.
//OnCreate
//...
TextView textView = (TextView) findViewById(R.id.textView);
textView.setText("..."); //Here is a text with more than 2500
//chars and at least have 10 \n char
//(it means has at least 10 paragraph)
How can do I show that text?
Edit One :
Even I set a background to that TextView and the TextView does'nt show that background
you can set
android:maxLines="1"
android:ellipsize="end"
to your textview, so the text which long than your textview will be hide.
Because android:singleLine="true" attribute has been deprecated, set the following attributes to your TextView:
android:ellipsize="end"
android:maxLines="1"
The TextView will then show only the text that can fit to your TextView, followed by an ellipsis ...
i dont know if this will help you but saw this on developer.android...
With Android 8.0 (API level 26) and higher, you can instruct a TextView to let the text size expand or contract automatically to fill its layout based on the TextView's characteristics and boundaries. This setting makes it easier to optimize the text size on different screens with dynamic content.
android:autoSizeTextType="uniform"
There are three ways you can set up the autosizing of TextView:
Default
Granularity
Preset Sizes
=================================================
readMore
Use this :
EditText thumbnailView = (EditText) findViewById(R.id.enterBearingNo_editText_id);
TextView messageView = (TextView) findViewById(R.id.string2);
String text = "LargeText";
Display display = getWindowManager().getDefaultDisplay();
FlowTextHelper.tryFlowText(text, thumbnailView, messageView, display);
FlowTextHelper.class
class FlowTextHelper {
static boolean mNewClassAvailable;
static {
if (Integer.valueOf(Build.VERSION.SDK) >= 8) { // Froyo 2.2, API level 8
mNewClassAvailable = true;
}
// Also you can use this trick if you don't know the exact version:
/*
* try {
* Class.forName("android.text.style.LeadingMarginSpan$LeadingMarginSpan2"
* ); mNewClassAvailable = true; } catch (Exception ex) {
* mNewClassAvailable = false; }
*/
}
public static void tryFlowText(String text, View thumbnailView,
TextView messageView, Display display) {
// There is nothing I can do for older versions, so just return
if (!mNewClassAvailable)
return;
// Get height and width of the image and height of the text line
thumbnailView.measure(display.getWidth(), display.getHeight());
int height = thumbnailView.getMeasuredHeight();
int width = thumbnailView.getMeasuredWidth();
float textLineHeight = messageView.getPaint().getTextSize();
// Set the span according to the number of lines and width of the image
int lines = (int) Math.round(height / textLineHeight);
// For an html text you can use this line: SpannableStringBuilder ss =
// (SpannableStringBuilder)Html.fromHtml(text);
SpannableString ss = new SpannableString(text);
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
messageView.setText(ss);
// Align the text with the image by removing the rule that the text is
// to the right of the image
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) messageView
.getLayoutParams();
int[] rules = params.getRules();
rules[RelativeLayout.RIGHT_OF] = 0;
}
public static void tryFlowTextPrice(String text, TextView messageView,
Display display) {
// There is nothing I can do for older versions, so just return
if (!mNewClassAvailable)
return;
// Get height and width of the image and height of the text line
// thumbnailView.measure(display.getWidth(), display.getHeight());
// int height = thumbnailView.getMeasuredHeight();
// int width = thumbnailView.getMeasuredWidth();
float textLineHeight = messageView.getPaint().getTextSize();
// Set the span according to the number of lines and width of the image
// int lines = (int) Math.round(height / textLineHeight);
// For an html text you can use this line: SpannableStringBuilder ss =
// (SpannableStringBuilder)Html.fromHtml(text);
SpannableString ss = new SpannableString(text);
// ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(),
// Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
messageView.setText(ss);
// Align the text with the image by removing the rule that the text is
// to the right of the image
// LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
// messageView
// .getLayoutParams();
// int[] rules = params.getRules();
// rules[RelativeLayout.RIGHT_OF] = 0;
}
}
I'm trying to display a bunch of text on the screen by placing TextViews inside rows of single-line LinearLayouts. Each word is stored in its own separate TextView, and I want to be able to place as many TextViews as will fit on a single LinearLayout line and detect when I've run out of horizontal space so that I can move to the next line.
The problem I'm facing is that I can't seem to find a way to measure the changing layout sizes as I create the display, because I can't get a reference width using getWidth() on the parent layout, and even after I add the TextViews, I can't seem to control the width.
We had a working version before, but it did everything using on hard-coded numbers based on the number of characters in a TextView at a fixed size. I'm trying to extend the app to work with all text and screen sizes. If this needs to be completely overhauled, I understand - I just want to be able to fill up the screen with an indefinite number of lines of text.
An obvious solution would be to just place all the text inside one TextView, but we need to be able to access each Word/Ponctuation object and its attributes through the displayed TextViews.
// layout_row is the current LinearLayout row I'm adding my TextViews to
// layout is the LinearLayout parent of all layout_rows
// text.text_content is a linked list of Word and Ponctuation objects
// each Word and Ponctuation object has a TextView attribute called view
private void display_views() {
if (text != null)
{
boolean prochainLigneSuivante; // if new line is to follow
int widthSoFar = 0;
int layoutWidth = layout_row.getWidth();
for (Object o : text.text_content) {
if (o instanceof Word ) {
Word w = (Word) o;
Object next = text.next(o);
if (noNeedForSpace(w)) {
// by default all TextViews have
// right padding to simulate spaces
w.view.setPadding(0, 0, 0, 0);
}
layout_row.addView(w.view);
widthSoFar += w.view.getWidth();
// Am I out of space?
prochainLigneSuivante = widthSoFar >= layoutWidth;
if(prochainLigneSuivante) {
layout_row.removeView(w.view);
widthSoFar = 0;
layout_row = new LinearLayout(context);
layout_row.setOrientation(LinearLayout.HORIZONTAL);
layout_row.addView(w.view);
layout_row.setBackgroundColor(Color.BLACK);
layout_row.setLayoutParams(new
LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
layout.addView(layout_row);
}
}
else if (o instanceof Ponctuation) {
Ponctuation p = (Ponctuation) o;
if (p.text.contains("CR")) {
layout_row = new LinearLayout(context);
layout_row.setOrientation(LinearLayout.HORIZONTAL);
layout_row.setLayoutParams(new
LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT));
widthSoFar = 0;
layout.addView(layout_row);
}
else {
if (p.view.getText().equals(" "))
p.view.setPadding(0, 0, 0, 0);
layout_row.addView(p.view);
if(!p.view.getText().equals(""))
widthSoFar += p.view.getWidth();
}
}
}
}
else {
Log.e("Text", "text est nul");
}
scroll.refreshDrawableState();
transition.startTransition(0);
}
If my row is colored red the text won't set to green and bold italic. When debugging I can see it telling the TextView to set each textViews setting.
TableRow row = new TableRow(getContext());
row.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
String[] items = list.get(l).split(":");
for(int i=0; i < items.length; i++){
//see if i need to colour row
if(items[i].startsWith("colorme_") == true) {
if (items[i].substring(8).equals("red") == true) {
row.setBackgroundColor(Color.RED);
}
} else {
//create a temp textview then add to row
TextView tempTV = new TextView(getContext());
tempTV.setText(items[i].toString());
//test against correct answers and colour text view green if correct
if (correctAnswers != null && correctAnswers.size() > i) {
if (correctAnswers.get(i).equals(items[i].toString()) == true) {
tempTV.setTextColor(Color.GREEN);
tempTV.setTypeface(null, Typeface.BOLD_ITALIC);
}
}
row.addView(tempTV,lpTextView);
}
}
//add the row
tempTable.addView(row);
To me it looks like you have separated the two different color setting codes on different sides on an if else so they won't both get called at the same time, because if the if statement returns true, then the else statement will not be triggered and you will pass the setTextColor code without running it and vice versa, if the if statement returns false, then you will skip changing the background color and only change the text color.
Hope that makes sense
Edit here is an example
if(items[i].startsWith("colorme_") == true) {
//this is where your are preforming your change row color to red
} else{
//this is where you are setting your text color to green
}