this is my first question here.
I'm trying to develop a "book" with text and images and I'm having problems to show a text (loaded from a raw file) flowing around and image in a textview.
I have tried this and it works fine while the text is defined in a string resource. However, when the text comes from an external file (eg a .txt file which includes break lines) the TextView looks something like this:
--------- text text text text text
| | text text text text text
--------- text text text text text
text text text text
text text text text
text text text text
text text text text
That is to say, just after the image, each line leaves an empty space to the right which has the same size than the image.
I don't know why this happens, Am I missing something? this is the code:
ImageView page_im_iv = (ImageView) findViewById(R.id.page_image);
TextView page_text = (TextView) findViewById(R.id.page_text);
Drawable page_image getResources().getDrawable(R.drawable.anyname);
page_im_iv.setBackground(page_image);
float left_margin = page_image.getIntrinsicWidth() + 10;
float top_margin = page_image.getIntrinsicHeight() + 10;
float flines = top_margin/page_text.getTextSize();
int ilines = (int) flines;
StringBuilder raw_text = readRaw(this,res_id);//res_id changes dynamically, it is just the name of the .txt file
SpannableString ss = new SpannableString(raw_text.toString());
ss.setSpan(new MyLeadingMarginSpan2(ilines, left_margin), 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
page_text.setText(ss);
And this is the layout:
<ScrollView xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="example.ActivityBookPage"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/page_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/page_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"/>
</RelativeLayout>
<!-- Other stuff
...
-->
</LinearLayout>
</ScrollView >
Thats all.
In case the problem comes from the readRaw method, this is the code:
public static StringBuilder readRaw(Context ctx,int res_id) {
StringBuilder text = new StringBuilder();
InputStream is = ctx.getResources().openRawResource(res_id);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr, 8192);
try {
String line;
while ((line = br.readLine()) != null) {
text.append(line);
text.append("\n");
}
isr.close();
is.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return text;
}
And this is the code for MyLeadingMarginSpan2 class, copy-pasted from the previous link
public class MyLeadingMarginSpan2 implements LeadingMarginSpan2 {
private int margin;
private int lines;
public MyLeadingMarginSpan2(int lines, int margin) {
this.margin = margin;
this.lines = lines;
}
#Override
public int getLeadingMargin(boolean first) {
return first ? margin : 0;
}
#Override
public int getLeadingMarginLineCount() {
return lines;
}
#Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom, CharSequence text,
int start, int end, boolean first, Layout layout) {}
}
Finally I found a solution (thanks to this old reply). The problem was that the text coming from the raw file may include break lines (\n), which are not taken into account when calculating "ilines". Hence, we need to count the number of characters that fit just at the right of the image, then include a new break line and then the rest of the text. This is the code
int charCount = page_text_layout.getLineEnd(Math.min(ilines - 1, page_text_layout.getLineCount() - 1));//see below what page_text_layout is
//in case the image is big enough to have all
//the text at its right, just use ss.length as
//the third parameter for setSpan
if (charCount >= ss.length() || charCount <= 0 ) {
ss.setSpan(new MyLeadingMarginSpan2(ilines, left_margin), 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
page_text.setText(ss);
}
else { //in case the text is longer, make three blocks
Spannable s1 = new SpannableStringBuilder(ss, 0, charCount);
s1.setSpan(new MyLeadingMarginSpan2(ilines, left_margin), 0, charCount, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Spannable s2 = new SpannableStringBuilder(System.getProperty("line.separator"));
Spannable s3 = new SpannableStringBuilder(ss, charCount, ss.length());
page_text.setText(TextUtils.concat(s1, s2, s3));
}
where page_text_layout is defined previously inside an onGlobalLayout callback (in my case inside the onCreate() method):
ViewTreeObserver vto = page_text.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
page_text_layout = page_text.getLayout();
}
});
Of course this code can be refined (eg. checking whether s1 breaks the text in the middle of a word) but this would be the main structure.
Hope this helps someone!
Related
In android we have constraints so we can put Views to start-of another Views and to end-of another Views
Specifically TextView when you set it's end to start of another view it will be look like:-
Tex | Another View
tVi |
ew |
Is there any layout to help me getting dynamic TextView for example the above one would be look like:-
Tex | Another View
tView | Parent device border
Here is a visual example:-
Maybe FlowTextView can solve your problem. FlowTextView is based on a RelativeLayout with added TextView features that wraps its text around its children.
Alternatively, if it's ok for you if the TextView is at the right and the image is at the left, like this:
---------
| Image | Text that is so
--------- long that it's
wrapping around the image
you could try the LeadingMarginSpan2:
Implement it like this:
public class MyLeadingMarginSpan2 implements LeadingMarginSpan.LeadingMarginSpan2 {
int lines;
int offset;
public MyLeadingMarginSpan2(int lines, int offset) {
this.lines = lines;
this.offset = offset;
}
#Override
public int getLeadingMarginLineCount() {
return lines;
}
#Override
public int getLeadingMargin(boolean first) {
return first ? offset : 0;
}
#Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
}
}
and use it like this in your code:
float textLineHeight = textView.getPaint().getTextSize();
int lines = (int) (imageView.getMeasuredHeight() / textLineHeight) + 1;
int offset = imageView.getMeasuredWidth();
SpannableString text = new SpannableString("Text that is so long that it's wrapping around the image");
text.setSpan(new MyLeadingMarginSpan2(lines, offset), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(text);
In case you want to provide an additonal fallback for very old versions of Android (< 2.2), you can have a look at this answer to a similar question: https://stackoverflow.com/a/8463221/13792619
There is a great answer provided here : stackoverflow.com/a/27064368/3696500
I hope this helps ,
Also have a look at https://github.com/deano2390/FlowTextView
A github library.
<uk.co.deanwild.flowtextview.FlowTextView
android:id="#+id/ftv"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:padding="10dip"
android:src="#drawable/android"/>
</uk.co.deanwild.flowtextview.FlowTextView>
In the picture bellow i want the Footnote string to always appear floating to the right edge of the Textview:
I tried the following but it doesn't work:
SpannableStringBuilder builder = new SpannableStringBuilder();
SpannableString item = new SpannableString ("Main text");
builder.append (item);
builder.append (" ");
String footnote = "Footnote";
int len = footnote.length();
SpannableString subitem = new SpannableString (footnote);
subitem.setSpan (new ForegroundColorSpan (color), 0, len, 0);
subitem.setSpan (new AbsoluteSizeSpan (12, true), 0, len, 0);
subitem.setSpan (new AlignmentSpan.Standard (Layout.Alignment.ALIGN_OPPOSITE), 0, len, 0);
builder.append (subitem);
myTextView.setText (builder, TextView.BufferType.SPANNABLE);
It can be achieved by using 2 textviews in frame layout and set the value of layout_gravity as right and bottom of 2nd second textview.
android:layout_gravity="bottom|right"
Example:
<FrameLayout
android:id="#+id/flString"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<TextView
android:id="#+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text "
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="#android:color/black" />
<TextView
android:id="#+id/tvHighlighted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:text="Footnote"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="#0000ff" />
</FrameLayout>
In case of single line, second highlighted word overlaps the text. It can be handled programatically:
/**
* Used to create 2 text view text in single line like :
*
* Text Text Text Text Text Text Text Text Text Text |Footnote|
*
* #param frameLayout FrameLayout
* #param tvTitle TextView
* #param tvHighlighted TextView
* Directly "View" can be used as param type, but for this case only I
* have used dedicated Views and ViewGroup.
*/
public static void manageTextViews(final FrameLayout frameLayout, final TextView tvTitle, final TextView tvHighlighted) {
tvTitle.post(new Runnable() {
#Override
public void run() {
try {
int lineCount = tvTitle.getLineCount();
Rect bounds = new Rect();
tvTitle.getLineBounds(lineCount > 0 ? lineCount - 1 : 0, bounds);
FrameLayout.LayoutParams fllp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
fllp.gravity = (Gravity.BOTTOM | Gravity.RIGHT);
if (frameLayout.getWidth() <= (bounds.width() + tvBuyable.getWidth() + 10)) {
fllp.setMargins(0, tvTitle.getLineHeight() + 5, 0, 0);
} else {
fllp.setMargins(0, 0, 0, 0);
}
tvHighlighted.setLayoutParams(fllp);
} catch (Exception e) {
Log.e(ClassName + ".manageTextViews()", "mTxtTitle.post", e);
}
}
});
}
By following this question, I was able to have text around an image. However, I have the following problem.
As you can see, the space for the image on top is displayed in every paragraph at the right. In the question someone had this problem and suggested to change 'ss.length()' for 'lines'. This seemed to work except if the first paragraph was too short, the next paragraph would overlap the image.
I modified the FlowTextHelper class slightly to use text from Html. This is the code I'm using:
public class FlowTextHelper {
private static boolean mNewClassAvailable;
/* class initialization fails when this throws an exception */
static {
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, int addPadding){
// 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() + addPadding;
messageView.measure(width, height); //to allow getTotalPaddingTop
int padding = messageView.getTotalPaddingTop();
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 - padding) / textLineHeight);
//SpannableString ss = new SpannableString(text);
//For an html text you can use this line:
if(!text.equals("")) {
SpannableStringBuilder ss = (SpannableStringBuilder) Html.fromHtml(text);
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), 0);
messageView.setText(ss);
messageView.setMovementMethod(LinkMovementMethod.getInstance()); // links
// 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 class MyLeadingMarginSpan2 implements LeadingMarginSpan.LeadingMarginSpan2 {
private int margin;
private int lines;
public MyLeadingMarginSpan2(int lines, int margin) {
this.margin = margin;
this.lines = lines;
}
#Override
public int getLeadingMargin(boolean first) {
return first ? margin : 0;
}
#Override
public int getLeadingMarginLineCount() {
return lines;
}
#Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom, CharSequence text,
int start, int end, boolean first, Layout layout) {}
}
What is causing the space being repeated every paragraph and how can I get rid of it? Any help is appreciated.
I've spend hours to solve this issue, but solved it with thanks to the answer found here:
text wrapping around image in android
Basically as follows:
First add a margin to your textview and set the text
final RelativeLayout.LayoutParams params = RelativeLayout.LayoutParams)messageView.getLayoutParams();
params.setMargins(marginWidth, 0, 0, 0);
messageView.setText(Html.fromHtml(text));
Then add an OnGlobalLayoutListener and in the onGlobalLayout() call you calculate how many lines actually need the margin. You split the lines in 2 separate spannables and add the Margin only to the first one:
messageView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() {
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
int linesCount = messageView.getLayout().getLineCount();
// restore the margin
params.setMargins(0, 0, 0, 0);
SpannableString spanS = new SpannableString ( Html.fromHtml(text) );
if (linesCount <= lines) {
spanS.setSpan(new MyLeadingMarginSpan2(lines, width), 0, spanS.length(), 0);
messageView.setText(spanS);
} else {
// find the breakpoint where to break the String.
int breakpoint = messageView.getLayout().getLineEnd(lines-1);
Spannable s1 = new SpannableStringBuilder(spanS, 0, breakpoint);
s1.setSpan(new MyLeadingMarginSpan2(lines, width), 0, s1.length(), 0);
Spannable s2 = new SpannableStringBuilder(System.getProperty("line.separator"));
Spannable s3 = new SpannableStringBuilder(spanS, breakpoint, spanS.length());
// It is needed to set a zero-margin span on for the text under the image to prevent the space on the right!
s3.setSpan(new MyLeadingMarginSpan2(0, 0), 0, s3.length(), 0);
messageView.setText(TextUtils.concat(s1, s2, s3));
}
// remove the GlobalLayoutListener
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
messageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
messageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
If you need to wrap text around an image, use this library FlowTextView.
The library performs well, and it can be used with a couple lines. However, it does not support screen pixel size for fonts. I found a workaround with this answer, so that you can convert pixel size to sp.
I hope this helps anyone and you don't waste as much time as me using the question from my original post.
I am using list view to show image and text i want to show like above image, can anyone suggest me how to wrap text around image with out webview. I am using following code:
Drawable dIcon = getResources().getDrawable(R.drawable.video_icon);
int leftMargin = dIcon.getIntrinsicWidth() + 10;
ImageView icon = (ImageView) findViewById(R.id.icon);
icon.setBackgroundDrawable(dIcon);
SpannableString ss = new SpannableString(text);
ss.setSpan(new MyLeadingMarginSpan2(3, leftMargin), 0, ss.length(), 0);
TextView messageView = (TextView) findViewById(R.id.message_view);
messageView.setText(ss);
class
class MyLeadingMarginSpan2 implements LeadingMarginSpan2 {
private int margin;
private int lines;
MyLeadingMarginSpan2(int lines, int margin) {
this.margin = margin;
this.lines = lines;
}
#Override
public int getLeadingMargin(boolean first) {
if (first) {
return margin;
} else {
return 0;
}
}
#Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom, CharSequence text,
int start, int end, boolean first, Layout layout) {}
#Override
public int getLeadingMarginLineCount() {
return lines;
}
};
by using this code iam getting below image pls suggest to how to get first means correct wrapping text around image without more empty spaces
Older post, but since there is no accepted answer and I have just found solution for same problem in my app, I will post a solution.
I have discovered that text without any line break works well.
Text with a line break that splits the text into 2 parts in a way that the part before line break ends to the right of the image, and the part after line break starts already on next line bellow the image, this also works well.
So what I do is I set left margin of the wrapping TextView's LayoutParams to the desired indent, and I set the text into TextView. Then I add OnGlobalLayoutListener, and inside onGlobalLayout callback, I count the position of the last character on the last line to the right of the image
//lines - number of lines to be affected by the leadingMargin
int charCount = textView.getLayout().getLineEnd(Math.min(lines - 1, textView.getLayout().getLineCount() - 1));
If the text does not have more lines than the number of lines that should have the left margin (or if the last character is already line break), I just set the LeadingMarginSpan2 on the whole length of the text.
// s - original Spannable containing the whole text
if (charCount >= s.length() || charCount <= 0 || s.charAt(charCount - 1) == '\n') {
s.setSpan(new MyLeadingMarginSpan(lines, w), 0, charCount, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(s);
}
If the text is longer, I split it into 2 parts (first one ending at the charCount position), insert line break between them, merge them and set the LeadingMarginSpan2 only on the first part.
else {
Spannable s1 = new SpannableStringBuilder(s, 0, charCount);
s1.setSpan(new MyLeadingMarginSpan(lines, w), 0, charCount, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Spannable s2 = new SpannableStringBuilder(System.getProperty("line.separator"));
Spannable s3 = new SpannableStringBuilder(s, charCount, s.length());
textView.setText(TextUtils.concat(s1, s2, s3));
}
At the end, do not forget to remove the left margin of the TextView's LayoutParams.
How can I align a bitmap to the text in a SpannableString?
SpannableStringBuilder ssb = new SpannableStringBuilder(arr_messages.get(position));
String msg1 = ssb.toString();
for (int i=0; i<smileys.length; i++)
{
if (msg1.indexOf(smileys[i]) > 0)
{
int t = msg1.indexOf(smileys[i]);
ssb.setSpan(new ImageSpan(bitmapArray.get(i)), t, t+2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
holder.txtName.setText(ssb, BufferType.SPANNABLE);
I would like to demonstrate the problem with these images:
What I want is to center both the text and the image vertically (the text is ok, but the image screws it up).
The result is:
Try ...new ImageSpan(bitmapArray.get(i), ImageSpan.ALIGN_BASELINE)...!