I am trying to achieve a count badge-like effect in a text view with a replacement span. I am using a drawable (circle defined in xml) as background as for some reason the canvas.drawCircle method did not seem to work. I want to draw the text on top of this drawable, however, no matter what I try, it never appears. Does anybody have any idea what the problem could be?
My custom replacement span class:
public class CircleBackgroundSpan extends ReplacementSpan {
private Drawable circle;
public CircleBackgroundSpan(Drawable drawable)
{
circle = drawable;
}
#Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
int textWidth = Math.round(measureText(paint, text, start, end));
return Math.max(textWidth, circle.getIntrinsicWidth());
}
#Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
Rect bounds = new Rect();
circle.setBounds((int) x, top, (int) x + circle.getIntrinsicWidth(), top + circle.getIntrinsicHeight());
circle.draw(canvas);
paint.setColor(0x0000A8);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(16);
paint.getTextBounds(text.toString(), 0, text.subSequence(start, end).length(), bounds);
canvas.drawText(text, start, end, x + circle.getIntrinsicWidth()/2f , y, paint);
}
private float measureText(Paint paint, CharSequence text, int start, int end)
{
return paint.measureText(text, start, end);
}
}
Usage:
SpannableString spannableString = new SpannableString(selected+" items selected");
String numberString = String.valueOf(selected);
spannableString.setSpan(new CircleBackgroundSpan(getResources().getDrawable(R.drawable.multiselect_circle)), 0, numberString.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
multiselect.setText(spannableString, TextView.BufferType.SPANNABLE);
And the result:
I am trying to get something like this:
Any ideas?
Turns out that the problem was with setting the paint colour. After I changed paint.setColor(0x0000A8) to paint.setColor(Color.parseColor("#0000A8"), the text was shown.
Related
I have custom span which should be repeatedly redrawn after specific interval. I try to call invalidate method of TextView instance but nothing happens.
My custom span is redrawn only when I append new text to TextView instance.
So, my question is how to achive "force" redraw/invalidate of TextView without change it's text?
UPD:
My custom span TypingAnimationSpan inherited from ReplacementSpan.
On Draw method in TypingAnimationSpan only part of text is drawing to canvas. On next Draw call length of drawing text part is increases.
As result "typing" effect should be achived.
My custom AnimatedTextView in Draw method checks - are any of it's TypingAnimationSpans are in animation state (should be redrawn) and calls PostInvalidateDelayed
Part of TypingAnimationSpan:
public override void Draw(Canvas canvas, ICharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint)
{
count += 0.1f * Speed;
count = Math.Min(count, end - start);
canvas.DrawText(text, start, start + (int) count, x, y, paint);
IsAnimationRunning = count <= end - start;
}
public override int GetSize(Paint paint, ICharSequence text, int start, int end, Paint.FontMetricsInt fm)
{
return (int) paint.MeasureText(text, start, start + (int) count);
}
Part of AnimatedTextView:
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
var targetSpans = (this.TextFormatted as ISpanned)
?.GetSpans(0, this.TextFormatted.Length(), Java.Lang.Class.FromType(typeof(TypingAnimationSpan)))
?.ToList();
bool needInvalidate = targetSpans?.Any(x => ((TypingAnimationSpan ) x).IsAnimationRunning) == true;
if (needInvalidate)
{
this.PostInvalidateDelayed(1);
}
}
So, I see that this.PostInvalidateDelayed(1); is called, but span is not redrawn.
How can padding be added to the bottom of the textview programmatically? I noticed that there is some padding above the text but not below. What can be done in order to add some black padding at the bottom just like at the top? It would make the text view a lot more legible (especially for letters that extend below the baseline i.e. g and y).
TextView tv1 = v.findViewById(R.id.textView1);
String myString = " " + getString(R.string.magnify) + " ";
Spannable spanna = new SpannableString(myString);
spanna.setSpan(new BackgroundColorSpan(Color.BLACK), 0, myString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanna.setSpan(new ForegroundColorSpan(ContextCompat.getColor(getContext(), R.color.yellow)), 0, myString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv2.setText(spanna);
With this custom ReplacementSpan, text is drawn "vertically center"
final SpannableString spannableString = new SpannableString(myString);
stringBuilder.append(spannableString);
stringBuilder.setSpan(new TagSpan(Color.BLACK, Color.MAGENTA), 0, myString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
testTextView.setText(stringBuilder, TextView.BufferType.SPANNABLE);
Class TagSpan
public class TagSpan extends ReplacementSpan {
private RectF mRect;
private int mBackgroundColor;
private int mForegroundColor;
public TagSpan(int backgroundColor, int foregroundColor) {
this.mRect = new RectF();
this.mBackgroundColor = backgroundColor;
this.mForegroundColor = foregroundColor;
}
#Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
// Background
mRect.set(x, top, x + paint.measureText(text, start, end), bottom);
paint.setColor(mBackgroundColor);
canvas.drawRect(mRect, paint);
// Text
paint.setColor(mForegroundColor);
int xPos = Math.round(x);
int yPos = (int) (0 + ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2)));
canvas.drawText(text, start, end, xPos, yPos, paint);
}
#Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
return Math.round(paint.measureText(text, start, end));
}
}
Not exactly answer to "bottom padding" request but solve part of your problem I think.
(Original post)
You can use: yourTextView.setPadding(0, 10, 0, 0); Android - How to set padding for TextView in ListView or ExpandableListView
Or increase the TextviewHeight and centre the text.
Set RelativeLayout.LayoutParams for Textview
TextView tv1 = v.findViewById(R.id.textView1);
String myString = " " + getString(R.string.magnify) + " ";
Spannable spanna = new SpannableString(myString);
spanna.setSpan(new BackgroundColorSpan(Color.BLACK), 0, myString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanna.setSpan(new ForegroundColorSpan(ContextCompat.getColor(getContext(), R.color.yellow)), 0, myString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv1.setText(spanna);
RelativeLayout.LayoutParams params= new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, tv1.getId());
tv1.setLayoutParams(params);
I'm using LeadingSpanMargin2 as suggested to style my TextView so that it would flow around an Image, the problem is the text I set is dynamic and can be rtl or ltr.
I tried anything I could think of to fix the leadingMargin so that it would be either right or left in both cases, to no avail.
Also the I've tried FlowTextView widget posted in github and it doesn't work well in rtl cases, so please do not suggest that.
Many thanks.
Here's the code I use, which was suggested in another answer:
public void setFlowText(CharSequence text, int width , int height, TextView messageView)
{
float textLineHeight = messageView.getPaint().getTextSize();
// Set the span according to the number of lines and width of the image
int lines = (int)Math.ceil(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);
//ss.setSpan(new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE), 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 class MyLeadingMarginSpan2 implements LeadingMarginSpan2 {
private int margin;
private int lines;
private boolean wasDrawCalled = false;
private int drawLineCount = 0;
public MyLeadingMarginSpan2(int lines, int margin) {
this.margin = margin;
this.lines = lines;
}
#Override
public int getLeadingMargin(boolean first) {
boolean isFirstMargin = first;
// a different algorithm for api 21+
if (Build.VERSION.SDK_INT >= 21) {
this.drawLineCount = this.wasDrawCalled ? this.drawLineCount + 1 : 0;
this.wasDrawCalled = false;
isFirstMargin = this.drawLineCount <= this.lines;
}
return isFirstMargin ? this.margin : 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) {
this.wasDrawCalled = true;
}
#Override
public int getLeadingMarginLineCount() {
return this.lines;
}
}
For the some unknown reason you can get left margin by passing:
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
and to get right margin you need to pass:
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
I don't know what is going on there, but this works!
I am implementing my own Look&Feel for a JTabbedPane using BasicTabbedPaneUI.
Following this tutorial, I want to add close buttons at the end of my tabs.
So far, I have managed to paint my closing icons on the right of the tab but it overlays my tab title. Therefore, I would like to reduce the width of the Rectangle used for the textRect parameter in the overridden method paintTab().
I have tried this but it has no effect:
#Override
protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex,
Rectangle iconRect, Rectangle textRect) {
//reduce textrect width to leave space for close icon
textRect.setSize(textRect.width - (2 * WIDTHDELTA + icon.getIconWidth()), textRect.height);
super.paintTab(g, tabPlacement, rects, tabIndex, iconRect, textRect);
Rectangle tabRect = rects[tabIndex];
// Calculate the coordinates where the button should be.
int dx = tabRect.x + tabRect.width - icon.getIconWidth() - WIDTHDELTA;
int dy = tabRect.y + (tabRect.height - icon.getIconHeight()) / 2;
//Paint the Close button
icon.paintIcon(tabPane, g, dx, dy);
}
How and where can I shrink the rectangle used to paint the tab's text?
Try to play with the fields of BasicTabbedPaneUI
protected Insets tabInsets;
protected Insets selectedTabPadInsets;
protected Insets tabAreaInsets;
protected Insets contentBorderInsets;
The tabInsets are used in the method you can try to override
protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics)
Here is the solution I came up with:
private String clipText(FontMetrics metrics, String text) {
String clipped = "...";
if (metrics.stringWidth(clipped) < MAX_TEXT_WIDTH) {
StringBuilder sb = new StringBuilder(clipped);
int index = 0;
for (char c : text.toCharArray()) {
sb.insert(index, c);
if (metrics.stringWidth(sb.toString()) > MAX_TEXT_WIDTH) {
clipped = sb.deleteCharAt(index).toString();
break;
}
index++;
}
}
return clipped;
}
I use it in:
#Override
protected void paintText(Graphics g, int tabPlacement, Font font, FontMetrics metrics,
int tabIndex, String title, Rectangle textRect, boolean isSelected) {
g.setFont(font);
View v = getTextViewForTab(tabIndex);
if (v != null) {
// html
v.paint(g, textRect);
} else {
// plain text
int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
g.setColor(isSelected ? ANZ_BLUE : Color.WHITE);
String text;
int rectX;
if (metrics.stringWidth(title) <= MAX_TEXT_WIDTH) {
text = title;
rectX = textRect.x - (icon.getIconWidth() / 2); //center text
} else {
text = clipText(metrics, title); //clip text
rectX = textRect.x;
}
if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemIndex, rectX,
textRect.y + metrics.getAscent());
} else { // tab disabled
Color bg = tabPane.getBackgroundAt(tabIndex);
g.setColor(bg.brighter());
BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemIndex, rectX,
textRect.y + metrics.getAscent());
g.setColor(bg.darker());
BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemIndex, rectX - 1,
textRect.y + metrics.getAscent() - 1);
}
}
}
It works fine for me.
I'm doing an Android Game, and I'm using a function like this to show texts on the device screen:
public void drawString(String text, int x, int y, Paint paint) {
canvas.drawText(text, x, y, paint);
}
And I try to show the following message:
g.drawString("Player: " + playerString+ " :\n" + messageString,SCREENWIDTH / 2, SCREENHEIGHT / 2, paint);
However instead of a newline (\n) I get a strange character (a square).
Anyone can help me?
Thanks
Instead of drawString call drawText
and for break lines call drawText twice with Y offset.
look here for example
Draw multi-line text to Canvas
public void drawString(Canvas canvas, String text, int x, int y, TextPaint paint) {
if (text.contains("\n")) {
String[] texts = text.split("\n");
for (String txt : texts) {
canvas.drawText(txt, x, y, paint);
y += paint.getTextSize();
}
} else {
canvas.drawText(text, x, y, paint);
}
}