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!
Related
Given a StyledText, initializied with SWT.MULTI and SWT.WRAP, I need to calculate the appropriate height hint based on the appropriate lines count for the content.
In this image you can see how the editor is resized when the content changes
The code that calculates the lines to display is the following
private int calcRealLinesCount() {
final int componentWidth = styledText.getSize().x;
final int dumbLinesCount = styledText.getLineCount();
int realLinesCount = 0;
for (int i = 0; i < dumbLinesCount; i++) {
final String lineText = styledText.getLine(i);
final Point lineTextExtent = styledTextGc.textExtent(lineText);
final double lines = lineTextExtent.x / (double) componentWidth;
realLinesCount += (int) Math.max(1D, Math.ceil(lines));
}
return Math.max(dumbLinesCount, realLinesCount);
}
The lines count is then used to get the appropriate height
((GridData) layoutData).heightHint = realLinesCount * fontHeight;
However, this code does not consider word wraps, and I cannot think of a way to do it.
Any ideas? Could I do this in a different way?
Thanks to Greg for the JFaceTextUtil#computeLineHeight hint.
I could not use that directly, but I've at least learned how JFace does what I need.
The following is what I'm using now to get a StyledText's line height:
private int computeRealLineHeight(final int lineIndex) {
final int startOffset = styledText.getOffsetAtLine(lineIndex);
final String lineText = styledText.getLine(lineIndex);
if (lineText.isEmpty()) {
return styledText.getLineHeight(startOffset);
}
final int endOffset = startOffset + lineText.length() - 1;
final Rectangle textBounds = styledText.getTextBounds(startOffset, endOffset);
return textBounds.height;
}
I am using a tutorial to understand how sprites work using a draw() method as well as using a gameloop. I adjusted the code as far as I understand it for my own project.
The question I have is how can I access a different row of my sprite sheet besides the second row. My sprite sheet has 9 columns and 20 rows.
public class Sprite implements Drawable {
private static final int BMP_COLUMNS = 9;
private static final int BMP_ROWS = 20;
private int x = 0;
private int y = 0;
private int xSpeed = 5;
private Bitmap bmp;
float fracsect = 30;
private GameContent gameContent;
private int currentFrame = 0;
private int width;
private int height;
public Sprite(GameContent gameContent, Bitmap bmp) {
this.gameContent = gameContent;
this.bmp = bmp;
this.width = bmp.getWidth() / BMP_COLUMNS;
this.height = bmp.getHeight() / BMP_ROWS;
}
#Override
public void update(float fracsec) {
if (x > gameContent.getGameWidth() - width - xSpeed) {
xSpeed = -5;
}
if (x + xSpeed < 0) {
xSpeed = 5;
}
x = x + xSpeed;
currentFrame = ++currentFrame % BMP_COLUMNS;
}
#Override
public void draw(Canvas canvas) {
update(fracsect);
int srcX = currentFrame * width;
int srcY = 1*height - 41;
Rect src = new Rect(srcX +20 , srcY,srcX + width,srcY + height-38); // Generates
Rect dst = new Rect(x,y,x+width -30, y+height-30); // Scales
canvas.drawBitmap(bmp, src, dst, null);
}
}
How do I gain access to the second row and how can I change it for instance to the third or 4th row ?
What I understand so far is that using a sprite as an object as a bitmap instead via imageview the code implementation of the code works differently. Is there any advice on how to access a different row for the sprite sheet ? I used the android documentation as well as the tutorial to understand this process as far as I can.
Here is the tutorial also:
http://www.edu4java.com/en/androidgame/androidgame4.html
From what I can see you are iterating through the first row, drawing each sprite in turn, perhaps to create an animation.
If you want to draw the animation for the second row, you can simply add 'height' to 'srcY'. It looks like you had this idea but you substracted instead of adding. Remember that Y-coordinates on computers go from top to bottom (i.e. (0,0) is top-left, (0, 10) is 10 pixels lower).
Likewise for the third and fourth rows, just add 'height' to 'srcY' again.
I am trying to create a text sticker in android. I am creating a text sticker on one of my phones and then saving the text size in the DP.
Then on another phone, I am loading the same text with the same DP and converting the DP value to pixel. Then calling the method
TextPaint.setTextSize(pixelValue)
But the output is different on both phones. I also tried replacing DP with SP, but it seems that's not the solution.
Also one more thing, I am determining the width and height of my view based on a drawable(res) width height.
Here is code of that drawable
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#android:color/transparent" />
<size android:width="200dp" android:height="50dp" />
</shape>
But this doesn't seem to be problem as the values are in DP in the drawable file.
Here is the constructor of text sticker.
public DynamicTextSticker(#NonNull Context context, #Nullable Drawable drawable, String text) {
this.context = context;
this.drawable = drawable;
if (drawable == null) {
this.drawable = ContextCompat.getDrawable(context, R.drawable.sticker_transparent_background);
}
textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
minTextSizePixels = DensityUtils.px2dp(context, 6);
maxTextSizePixels = DensityUtils.px2dp(context, 32);
alignment = Layout.Alignment.ALIGN_CENTER;
textPaint.setTextSize(maxTextSizePixels);
realBounds = new Rect(0, 0, getWidth(), getHeight());
textRect = new Rect(0, 0, getWidth(), getHeight());
}
draw method
#Override public void draw(#NonNull Canvas canvas) {
Matrix matrix = getMatrix();
canvas.save();
canvas.concat(matrix);
if (drawable != null) {
drawable.setBounds(realBounds);
drawable.draw(canvas);
}
canvas.restore();
canvas.save();
canvas.concat(matrix);
if (textRect.width() == getWidth()) {
int dy = getHeight() / 2 - staticLayout.getHeight() / 2;
// center vertical
canvas.translate(0, dy);
} else {
int dx = textRect.left;
int dy = textRect.top + textRect.height() / 2 - staticLayout.getHeight() / 2;
canvas.translate(dx, dy);
}
staticLayout.draw(canvas);
canvas.restore();
}
here is the resize method where the static layout is initialized
final int availableHeightPixels = textRect.height();
final int availableWidthPixels = textRect.width();
final CharSequence text = getText();
// Safety check
// (Do not resize if the view does not have dimensions or if there is no text)
if (text == null
|| text.length() <= 0
|| availableHeightPixels <= 0
|| availableWidthPixels <= 0
|| maxTextSizePixels <= 0) {
return this;
}
float targetTextSizePixels = maxTextSizePixels;
int targetTextHeightPixels =
getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels);
// Until we either fit within our TextView
// or we have reached our minimum text size,
// incrementally try smaller sizes
while (targetTextHeightPixels > availableHeightPixels
&& targetTextSizePixels > minTextSizePixels) {
targetTextSizePixels = Math.max(targetTextSizePixels - 2, minTextSizePixels);
targetTextHeightPixels =
getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels);
}
// If we have reached our minimum text size and the text still doesn't fit,
// append an ellipsis
// (NOTE: Auto-ellipsize doesn't work hence why we have to do it here)
if (targetTextSizePixels == minTextSizePixels
&& targetTextHeightPixels > availableHeightPixels) {
// Make a copy of the original TextPaint object for measuring
TextPaint textPaintCopy = new TextPaint(textPaint);
textPaintCopy.setTextSize(targetTextSizePixels);
// Measure using a StaticLayout instance
StaticLayout staticLayout =
new StaticLayout(text, textPaintCopy, availableWidthPixels, Layout.Alignment.ALIGN_NORMAL,
lineSpacingMultiplier, lineSpacingExtra, false);
// Check that we have a least one line of rendered text
if (staticLayout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line and add an ellipsis
int lastLine = staticLayout.getLineForVertical(availableHeightPixels) - 1;
if (lastLine >= 0) {
int startOffset = staticLayout.getLineStart(lastLine);
int endOffset = staticLayout.getLineEnd(lastLine);
float lineWidthPixels = staticLayout.getLineWidth(lastLine);
float ellipseWidth = textPaintCopy.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while (availableWidthPixels < lineWidthPixels + ellipseWidth) {
endOffset--;
lineWidthPixels =
textPaintCopy.measureText(text.subSequence(startOffset, endOffset + 1).toString());
}
setText(text.subSequence(0, endOffset) + mEllipsis);
}
}
}
textPaint.setTextSize(targetTextSizePixels);
this.targetTextSizePixel = targetTextSizePixels;
staticLayout =
new StaticLayout(this.text, textPaint, textRect.width(), alignment, lineSpacingMultiplier,
lineSpacingExtra, true);
return this;
here is the getWidth and getHeight method
#Override public int getWidth() {
return drawable.getIntrinsicWidth();
}
#Override public int getHeight() {
return drawable.getIntrinsicHeight();
}
I tried several ways but none of them worked for me.
I think the problem is not with text size. but the problem might be with drawable. But I don't know what possibly could go wrong with that, as I have used DP unit everywhere.
**If i forget to add any code please tell me I will attach it as well**
I am new to android so I am not sure the reason behind this problem.
Thanks for your help.
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.
I am working on a project to create a Text Editor (notepad, basically), in Swing and i have encountered a problem with the line numbers.
I must say i am fairly new to java, but i am doing my best to change that!
You have below the class that handels the line numbers in the JEditorPane I am using. But everytime it ads another digit to the line numbers, I get a duplicate separator line for ~10 lines of text. How can i prevent this from happening.
Also, how can I add a "Long line marker", just another line drawn at about 100 'm' characters from the left.
This is the class:
/**
* Part of the source code got from here:
* developer.com/java/other/article.php/3318421/Add-Line-Numbering-in-the-JEditorPane.htm
*
* About the Author Stanislav Lapitsky is an offshore software developer and
* consultant with more than 7 years of programming experience. His area of
* knowledge includes java based technologies and RDBMS.
*
*/
package MainGUI;
/**
*
* #author mrbigheart
*/
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
public class LineNumbers {
// private default constructor
private LineNumbers() {
}
// private constructor
private LineNumbers(JEditorPane edit) {
edit.setEditorKit(new NumberedEditorKit());
}
// static factory
public static LineNumbers valueOf(JEditorPane edit) {
return new LineNumbers(edit);
}
}
class NumberedEditorKit extends StyledEditorKit {
#Override
public ViewFactory getViewFactory() {
return new NumberedViewFactory();
}
}
class NumberedViewFactory implements ViewFactory {
#Override
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
switch (kind) {
case AbstractDocument.ContentElementName:
return new LabelView(elem);
case AbstractDocument.ParagraphElementName:
// return new ParagraphView(elem);
return new NumberedParagraphView(elem);
case AbstractDocument.SectionElementName:
return new BoxView(elem, View.Y_AXIS);
case StyleConstants.ComponentElementName:
return new ComponentView(elem);
case StyleConstants.IconElementName:
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
final class NumberedParagraphView extends ParagraphView {
public static short NUMBERS_WIDTH = 30;
public NumberedParagraphView(Element e) {
super(e);
short top = 7;
short left = 0;
short bottom = 0;
short right = 0;
this.setInsets(top, left, bottom, right);
}
// indent for the JEditorPane
#Override
protected void setInsets(short top, short left, short bottom,
short right) {
super.setInsets(top, (short) (left + NUMBERS_WIDTH + 5),
bottom, right);
}
#Override
public void paintChild(Graphics g, Rectangle r, int n) {
super.paintChild(g, r, n);
View parent = this.getParent();
int previousLineCount = getPreviousLineCount();
int numberX = r.x - getLeftInset();
int numberY = r.y + r.height - 5;
// Update sizes when number of digits in the line number changes
int lines = getPreviousLineCount();
int digits = Math.max(String.valueOf(lines).length(), 2);
FontMetrics fontMetrics = g.getFontMetrics();
//get the width of a zero character times the number of digits
int width = (fontMetrics.charWidth('0') * digits) + numberX + 7;
// update NUMBERS_WIDTH with the new width
NUMBERS_WIDTH = (short)width;
// line numbers rectangle (x, y, width, height)
g.drawRect(0, 0, width, parent.getContainer().getHeight());
g.setColor(Color.YELLOW);
g.drawString(Integer.toString(previousLineCount + n + 1),
numberX, numberY);
}
public int getPreviousLineCount() {
int lineCount = 0;
View parent = this.getParent();
int count = parent.getViewCount();
for (int i = 0; i < count; i++) {
if (parent.getView(i) == this) {
break;
} else {
lineCount += parent.getView(i).getViewCount();
}
}
return lineCount;
}
}
Thank you in advance!
This seemed to be an improvement for me. Force the width and NUMBERS_WIDTH to be the max it has ever been with Math.max() in paintChild():
...
int width = (fontMetrics.charWidth('0') * digits) + numberX + 7;
// update NUMBERS_WIDTH with the new width
NUMBERS_WIDTH = (short) Math.max(NUMBERS_WIDTH, width);
// line numbers rectangle (x, y, width, height)
g.drawRect(0, 0, NUMBERS_WIDTH, parent.getContainer().getHeight());
...