PDFJet-Open-source: How to set multiline text inside Cell object - android

I use PDFJet-Open-Source library to construct a pdf. So, I have couple of questions:
1) How can I place the multiline text inside Cell?
Problem description: Currently I faced with problem that I can't place the multiline text inside Cell object. I tried set text like "text1 \n text2..." but it does not have any effect. Unfortunatelly, open source version does not have TextColumn and Paragraph classes.
2) What is the CompositeTextLine and how to use it?
Problem description: Perhaps I have wrong imagination, but I tried to do the following:
...
CompositeTextLine ctl = new CompositTextLine(0,0);
ctl.addComponent(new TextLine(f1,"MyText1"));
ctl.addComponent(new TextLine(f1,"MyText2"));
ctl.addComponent(new TextLine(f1,"MyText3"));
Cell cell = new Cell(f1);
cell.setCompositeTextLine(ctl);
...
I expected to see several multiple lines in the Cell but I observed nothing. Moreover, if I add the line table.wrapAroundCellText(), I've got NullPointerException. If I call ctl.drawOn(page), I just observe: "MyText1 MyText2 MyText3" without line breaking.
UPDATE: I discovered the TextBox class, so that, if I write:
TextBox textbox = new TextBox(f1);
textbox.setText("First Line \n Second Line");
textbox.drawOn(page);
it will construct what I want:
First Line
Second Line
But still I am interested with the possibility of (1) and description of (2) and some of them variations, like to "How to set TextBox (or image etc.) inside Cell, not only single line?"
And last one, could anyone, please, refer me to the realization of "text justification" algorithm in Java or C++.

I can across the same problem, and I ended up extending Cell, and using WordUtils from apache commons lang3:
public class MultilineCell extends Cell {
private final int characterCount;
public MultilineCell(Font font, String content, int characterCount) {
super(font, content);
this.characterCount = characterCount;
}
#Override
public String getText() {
return WrapUtil.wrap(super.getText(), this.characterCount);
}
#Override
public float getHeight() {
float height = this.font.getBodyHeight();
String text = getText();
if (text != null) {
String[] wrappedTexts = text.split(System.getProperty("line.separator"));
if (wrappedTexts.length > 1) {
return (height * wrappedTexts.length) + this.top_padding + this.bottom_padding;
}
}
return height + this.top_padding + this.bottom_padding;
}
#Override
protected void paint(Page page, float x, float y, float w, float h) throws Exception {
page.setPenColor(this.getPenColor());
page.setPenWidth(this.lineWidth);
drawBorders(page, x, y, w, h);
drawText(page, x, y, w);
}
private void drawBorders(
Page page,
float x,
float y,
float cell_w,
float cell_h) throws Exception {
if (getBorder(Border.TOP) &&
getBorder(Border.BOTTOM) &&
getBorder(Border.LEFT) &&
getBorder(Border.RIGHT)) {
page.drawRect(x, y, cell_w, cell_h);
}
else {
if (getBorder(Border.TOP)) {
page.moveTo(x, y);
page.lineTo(x + cell_w, y);
page.strokePath();
}
if (getBorder(Border.BOTTOM)) {
page.moveTo(x, y + cell_h);
page.lineTo(x + cell_w, y + cell_h);
page.strokePath();
}
if (getBorder(Border.LEFT)) {
page.moveTo(x, y);
page.lineTo(x, y + cell_h);
page.strokePath();
}
if (getBorder(Border.RIGHT)) {
page.moveTo(x + cell_w, y);
page.lineTo(x + cell_w, y + cell_h);
page.strokePath();
}
}
}
private void drawText(
Page page,
float x,
float y,
float cell_w) throws IOException {
String wrappedText = WrapUtil.wrap(super.getText(), this.characterCount);
String[] lines = wrappedText.split(System.getProperty("line.separator"));
float x_text = x + this.left_padding;
float y_text = y + this.font.getAscent() + this.top_padding;
for (String line : lines) {
page.drawString(this.font, line, x_text, y_text);
y_text += this.font.getBodyHeight();
}
}
}
You can instantiate and add the MultilineCell as you would with a Cell:
List<Cell> rowCells = new ArrayList<Cell>();
rowCells.add(new MultilineCell(font, c.getString(reasonIdx), 42));
I know that extension is not a nice solution, and copying drawBorders() is even worse, but in this case, it is the only solution if you don't want to fork PDFJet.
This however breaks autoAdjustColumnWidths: the width is calculated on the whole text, instead of the longest line. So if you intend to use this method, either subclass Table, fork PDFJet, or extract just this method (the only downside of the latter is that I couldn't work around the cell padding though):
/**
* Auto adjusts the widths of all columns so that they are just wide enough to hold the text without truncation.
*/
private static void autoAdjustColumnWidths(List<List<Cell>> tableData) {
// Find the maximum text width for each column
float[] max_col_widths = new float[tableData.get(0).size()];
for (int i = 0; i < tableData.size(); i++) {
List<Cell> row = tableData.get(i);
for (int j = 0; j < row.size(); j++) {
Cell cell = row.get(j);
if (cell.getColSpan() == 1) {
float cellWidth = 0f;
if (cell.getImage() != null) {
cellWidth = cell.getImage().getWidth();
}
if (cell.getText() != null) {
// Is this a multiline cell? If so, measure the widest line
if (cell.getText().contains(MultilineCell.NEW_LINE)) {
String[] lines = cell.getText().split(MultilineCell.NEW_LINE);
for (String line : lines) {
if (cell.getFont().stringWidth(cell.getFallbackFont(), line) > cellWidth) {
cellWidth = cell.getFont().stringWidth(cell.getFallbackFont(), line);
}
}
}
// Standard (single-line) cell, measure whole text
else {
if (cell.getFont().stringWidth(cell.getFallbackFont(), cell.getText()) > cellWidth) {
cellWidth = cell.getFont().stringWidth(cell.getFallbackFont(), cell.getText());
}
}
}
cell.setWidth(cellWidth + 2f /*cell.left_padding*/ + 2f/*cell.right_padding*/);
if (max_col_widths[j] == 0f ||
cell.getWidth() > max_col_widths[j]) {
max_col_widths[j] = cell.getWidth();
}
}
}
}
for (int i = 0; i < tableData.size(); i++) {
List<Cell> row = tableData.get(i);
for (int j = 0; j < row.size(); j++) {
Cell cell = row.get(j);
cell.setWidth(max_col_widths[j]);
}
}
}
PDFJet is a funny library, btw: protected fields, every other method throws Exception, and the classes are definitely not designed for extension, even if there're not final.

There is a tricky solution for that:
As suggested before split your input into several lines (using for example using WordUtils from apache commons lang3)
String wrappedText = WrapUtil.wrap(super.getText(), this.characterCount);
String[] lines = wrappedText.split(System.getProperty("line.separator"));
After you have your lines, add as many rows as lines into your table with those separate lines.
To simulate "multiline cell" call function removeLineBetweenRows providing index of those added rows. It will look like a one big cell.

Related

MPAndroidChart - How to display text to the left of LimitLine?

I'm trying to display text on the left of the LimitLine like this:
However these are the only options I'm getting for setting the position of the Label for limit line.
I'm using LimitLine.LimitLabelPosition.LEFT_TOP and it only displays the Label above the Limit line.
YAxis leftAxis = mChart.getAxisLeft();
LimitLine minimumLimit = new LimitLine(50f, "Minimum Limit");
minimumLimit.setLineWidth(0.5f);
minimumLimit.setTextColor(ContextCompat.getColor(this, R.color.white_60_opacity));
minimumLimit.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
leftAxis.addLimitLine(minimumLimit);
How do I display the LimitLine's Label to the left of the LimitLine?
Edit:
I have also tried used the methods .setXOffset(50f) and .setYOffset(50f) but this only shifts the position of the label and not the line minimumLimit.
You can achieve this by using a custom YAxisRenderer with a little modification of the override method public void renderLimitLines(Canvas c).
The modifications needed for this purpose are:
1.To calculate the label width of each limit line to be able to move the limit line to the correct x position like below:
limitLinePath.moveTo(mViewPortHandler.contentLeft()+getLabelTextWidth(l), pts[1]);
2.To draw the label to the new x,y position something like this:
c.drawText(label, mViewPortHandler.contentLeft() + xOffset, pts[1]+l.getYOffset(), mLimitLinePaint);
Below is a custom MyYAxisRenderer containing the above modifications:
public class MyYAxisRenderer extends YAxisRenderer {
private final Paint textPaint;
public MyYAxisRenderer(ViewPortHandler viewPortHandler, YAxis yAxis, Transformer trans) {
super(viewPortHandler, yAxis, trans);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
public void renderLimitLines(Canvas c) {
List<LimitLine> limitLines = mYAxis.getLimitLines();
if (limitLines == null || limitLines.size() <= 0)
return;
float[] pts = mRenderLimitLinesBuffer;
pts[0] = 0;
pts[1] = 0;
Path limitLinePath = mRenderLimitLines;
limitLinePath.reset();
for (int i = 0; i < limitLines.size(); i++) {
LimitLine l = limitLines.get(i);
if (!l.isEnabled())
continue;
int clipRestoreCount = c.save();
mLimitLineClippingRect.set(mViewPortHandler.getContentRect());
mLimitLineClippingRect.inset(0.f, -l.getLineWidth());
c.clipRect(mLimitLineClippingRect);
mLimitLinePaint.setStyle(Paint.Style.STROKE);
mLimitLinePaint.setColor(l.getLineColor());
mLimitLinePaint.setStrokeWidth(l.getLineWidth());
mLimitLinePaint.setPathEffect(l.getDashPathEffect());
pts[1] = l.getLimit();
mTrans.pointValuesToPixel(pts);
limitLinePath.moveTo(mViewPortHandler.contentLeft()+getLabelTextWidth(l), pts[1]);
limitLinePath.lineTo(mViewPortHandler.contentRight(), pts[1]);
c.drawPath(limitLinePath, mLimitLinePaint);
limitLinePath.reset();
String label = l.getLabel();
// if drawing the limit-value label is enabled
if (label != null && !label.equals("")) {
mLimitLinePaint.setStyle(l.getTextStyle());
mLimitLinePaint.setPathEffect(null);
mLimitLinePaint.setColor(l.getTextColor());
mLimitLinePaint.setTypeface(l.getTypeface());
mLimitLinePaint.setStrokeWidth(0.5f);
mLimitLinePaint.setTextSize(l.getTextSize());
final float labelLineHeight = Utils.calcTextHeight(mLimitLinePaint, label);
float xOffset = getLimitLineXOffset(l);
float yOffset = l.getLineWidth() + labelLineHeight + l.getYOffset();
final LimitLine.LimitLabelPosition position = l.getLabelPosition();
//draw the label on the left in the same y position of the limit line
mLimitLinePaint.setTextAlign(Paint.Align.LEFT);
c.drawText(label,
mViewPortHandler.contentLeft() + xOffset,
pts[1]+l.getYOffset(), mLimitLinePaint);
}
c.restoreToCount(clipRestoreCount);
}
}
private float getLimitLineXOffset(LimitLine l){
return Utils.convertDpToPixel(4f) + l.getXOffset();
}
private float getLabelTextWidth(LimitLine l) {
String label = l.getLabel();
if (label != null && !label.equals("")) {
textPaint.setStyle(l.getTextStyle());
textPaint.setPathEffect(null);
textPaint.setColor(l.getTextColor());
textPaint.setTypeface(l.getTypeface());
textPaint.setStrokeWidth(0.5f);
textPaint.setTextSize(l.getTextSize());
int textWidth = Utils.calcTextWidth(textPaint, label);
float xOffset = getLimitLineXOffset(l);
return textWidth + (xOffset*2);
}
return 0;
}
}
In the above renderer i have added two helper functions one for the calculation of the label text width private float getLabelTextWidth(LimitLine l) for a specific limit line and one to get the x offset of each limit line private float getLimitLineXOffset(LimitLine l) which you can modify based on your needs.
And you can use the above Renderer like the below:
lineChart.setRendererLeftYAxis(new MyYAxisRenderer(lineChart.getViewPortHandler(), lineChart.getAxisLeft(), lineChart.getTransformer(YAxis.AxisDependency.LEFT)));
Result:
Note: This was tested with v3.1.0 ('com.github.PhilJay:MPAndroidChart:v3.1.0')

How to place the text values inside in MPAndroidChart circle?

I am developing a chart based application, I am using MPAndroidChart library, I need to place the text value inside of circle, i tried to display,Thanks for if any suggestions related this,
i attached a screenshot related to that issue. I need to be do like this
but i get like this image:
Thanks Again for helping this issue,
ArrayList<Entry> e1 = new ArrayList<Entry>();
float[] values = new float[]{48, 59, 79, 29, 39, 50, 60};
for (int i = 0; i < values.length; i++) {
e1.add(new Entry(values[i], i, "line3"));
}
int[] color = {Color.parseColor("#D13385"), Color.parseColor("#37D04E"), Color.parseColor("#33D1D1"), Color.parseColor("#D1C933")};
LineDataSet d1 = new LineDataSet(e1, "" + cnt);
d1.setColors(color);
d1.setLineWidth(3.0f);
d1.setCircleSize(7.0f);
d1.setDrawValues(true);
d1.setCircleColor(Color.parseColor("#891e9a"));
d1.setCircleColorHole(Color.parseColor("#891e9a"));
d1.setDrawHighlightIndicators(false);
d1.setDrawFilled(false);
d1.setFillAlpha(20);
d1.setHighlightLineWidth(50f);
d1.setValueTextSize(10f);
Currently it is not possible to change the position where the values are drawn by default. You will have to modify the library to get that behaviour.
It is a bit "hacky", but I've managed to achieve such layout You've provided by creating two sets of data and attaching them to same chart. One set (lets call it "dots") contains your data needed to be displayed as dots. The second one ("lines") is a bit offset downwards (y value minus some experimentally picked value). Now you can set no line displaying for "lines" and a lines for "dots", no values labels for "dots" and white labels for "lines" and by experimentally moving your y values back and forth you can achieve overlaying values labels from one chart on top of another ("dots" will be covered by "lines" values).
UPDATE:
Actually, I've an answer more elegant, that I've provided! Use Highlight[] and create array of highlights.
highlihts = new Highlight[values_dots.size()];
for (int i = 0; i < values_dots.size(); i++) {
Highlight h = new Highlight(values_dots.get(i).getX(),values_dots.get(i).getY(), 0);
highlihts[i] = h;
}
chart.highlightValues(highlihts);
In CustomMarkerView class position marker like so:
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -(getHeight() / 2));
}
Boom
P.S. Philipp Jahoda, awesome library!
there was an easy way to do this
custom maker view
Marker view
public class MyMarkerView extends MarkerView {
private final TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
tvContent = findViewById(R.id.tvContent);
}
// runs every time the MarkerView is redrawn, can be used to update the
// content (user-interface)
#SuppressLint("SetTextI18n")
#Override
public void refreshContent(Entry e, Highlight highlight) {
if (e instanceof CandleEntry) {
CandleEntry ce = (CandleEntry) e;
tvContent.setText(Utils.formatNumber(ce.getHigh(), 0, true)+(ce.getData()));
} else {
tvContent.setText(Utils.formatNumber(e.getY(), 0, true)+"\n "+(e.getData()));
}
super.refreshContent(e, highlight);
}
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
}
and lastly in your activity
values.add(new Entry(i, val,"Custom message per value"));
Preview be like
example screen shot
there was an easy way to do this
custom maker view
<TextView
android:id="#+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="7dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text=""
android:textSize="12sp"
android:textColor="#android:color/white"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
Marker view class
public class MyMarkerView extends MarkerView {
private final TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
tvContent = findViewById(R.id.tvContent);
}
// runs every time the MarkerView is redrawn, can be used to update the
// content (user-interface)
#SuppressLint("SetTextI18n")
#Override
public void refreshContent(Entry e, Highlight highlight) {
if (e instanceof CandleEntry) {
CandleEntry ce = (CandleEntry) e;
tvContent.setText(Utils.formatNumber(ce.getHigh(), 0, true)+(ce.getData()));
} else {
tvContent.setText(Utils.formatNumber(e.getY(), 0, true)+"\n "+(e.getData()));
}
super.refreshContent(e, highlight);
}
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
}
and lastly in your activity
values.add(new Entry(i, val,"Custom message per value"));
Preview be like
example screen shot
There are two possibilities:
(1) Not so good: Shift the y-value of the label position
Two Data sets one for text and one for the line (including circles)
Modifiy the y-position for the text value with a constant offset
Pro: Easy
Con: The offset is not always constant (see offset is not always similar)
(2) Better: Override the drawValues method from LineChartRenderer
In LineChartRenderer.java -> drawValues the text is vertically shifted by this line:
drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
So to get rid of the "- valOffset":
1.Override the drawValues method
Create a new java file "CenteredTextLineChartRenderer.java" and override method drawValues from LineChartRenderer
2.Modify the y-valOffset to y+textHeight*0.35f
Add float textHeight = dataSet.getValueTextSize();
public class CenteredTextLineChartRenderer extends LineChartRenderer {
public CenteredTextLineChartRenderer(LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
//Modified drawValues Method
// Center label on coordinate instead of applying a valOffset
#Override
public void drawValues(Canvas c) {
if (isDrawingValuesAllowed(mChart)) {
List<ILineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
float textHeight = dataSet.getValueTextSize();
if (!shouldDrawValues(dataSet) || dataSet.getEntryCount() < 1)
continue;
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
// make sure the values do not interfear with the circles
int valOffset = (int) (dataSet.getCircleRadius() * 1.75f);
if (!dataSet.isDrawCirclesEnabled())
valOffset = valOffset / 2;
mXBounds.set(mChart, dataSet);
float[] positions = trans.generateTransformedValuesLine(dataSet, mAnimator.getPhaseX(), mAnimator
.getPhaseY(), mXBounds.min, mXBounds.max);
ValueFormatter formatter = dataSet.getValueFormatter();
MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);
for (int j = 0; j < positions.length; j += 2) {
float x = positions[j];
float y = positions[j + 1];
if (!mViewPortHandler.isInBoundsRight(x))
break;
if (!mViewPortHandler.isInBoundsLeft(x) || !mViewPortHandler.isInBoundsY(y))
continue;
Entry entry = dataSet.getEntryForIndex(j / 2 + mXBounds.min);
if (dataSet.isDrawValuesEnabled()) {
//drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
drawValue(c, formatter.getPointLabel(entry), x, y+textHeight*0.35f, dataSet.getValueTextColor(j / 2));
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
Utils.drawImage(
c,
icon,
(int)(x + iconsOffset.x),
(int)(y + iconsOffset.y),
icon.getIntrinsicWidth(),
icon.getIntrinsicHeight());
}
}
MPPointF.recycleInstance(iconsOffset);
}
}
}
}
3.Set your own LineChart renderer to your modified drawValues class
LineChart mChart = (LineChart) mainActivity.findViewById(R.id.LineChart);
mChart.setRenderer(new CenteredTextLineChartRenderer(mChart,mChart.getAnimator(),mChart.getViewPortHandler()));
Run your code and manually adapt the 0.35f offset in your CenteredTextLineChartRenderer class
Now your text is always vertically centered!
IMPORTANT: With deleting the valOffset your label is not vertically centered as the text anchor is not in the center of your text label. So you have to insert a manual offset "textHeight*0.35f" (just try it out). But the big advantage of method (2) is that the text is always centered with the same offset also for example in landscape mode and on other screen sizes...

How to programmatically adjust z order of Views

The below code is my attempt to send mMyView to the front or the back of the set of children of mPivotParent so it will be rendered on top or behind the others. Hiding the view will not suffice in my case.
mPivotParent is a FrameLayout.
Looking at mPivotParent.mChildren shows that my code below "works" in that the ordering is being set correctly. Yet it has no impact on the z order. Not only this, but the framerate gets cumulatively slower and slower the more times the repositioning code gets called. There are 4 children total and mPivotParent.mChildrenCount remains at 4 throughout as expected.
I'm targeting API Level 7.
#Override
public boolean onTouchEvent(MotionEvent event) {
Display display = getWindowManager().getDefaultDisplay();
float x = event.getRawX();
float sWidth = (int)display.getWidth();
float xLerpFromCenter = ((x / sWidth) - .5f) * 2.f; // [-1, 1]
mRotateAnimation.mfLerp = xLerpFromCenter;
if(xLerpFromCenter < -0.2f && mPivotParent.getChildAt(0) != mMyView)
{
mPivotParent.removeView(mMyView);
mPivotParent.addView(mMyView, 0);
refreshEverything();
}
else if(xLerpFromCenter > 0.2f && mPivotParent.getChildAt(0) == mMyView)
{
mPivotParent.removeView(mMyView);
mPivotParent.addView(mMyView, mPivotParent.getChildCount() - 1);
refreshEverything();
}
return super.onTouchEvent(event);
}
private void refreshEverything()
{
for(int i = 0; i < mPivotParent.getChildCount(); ++i)
{
mPivotParent.getChildAt(i).invalidate();
mPivotParent.getChildAt(i).requestLayout();
}
mPivotParent.invalidate();
mPivotParent.requestLayout();
}
Partial Solution
Here's a somewhat inefficient hack but it works for my purpose, which is to take the top item and send it to the back, keeping all other items in their same order.
private void putFrontAtBack()
{
for(int i = 0; i < mPivotParent.getChildCount() - 1; ++i)
{
mPivotParent.getChildAt(0).bringToFront();
}
refreshEverything();
}
Note: This doesn't work in the general case of arbitrary re-ordering.
Try this.
private void reorder(int[] order)
{
if(order == null || order.length != mPivotParent.getChildCount()) return;
for(int i = order.length - 1; i >= 0; i--)
{
mPivotParent.getChildAt(order[i]).bringToFront();
}
refreshEverything();
}
This code provides arbitrary reordering. The integer array "order" is used to indicate the new order of each view, where order[n]=x means the new order of childAt(x) is n.

How to find android TextView number of characters per line?

So I have a TextView in android that has the width of the whole length of the screen and a padding of dip 5. How can I calculate the number of characters that will fit a single line on the screen? I guess in other words, I'm trying to get the number of columns of a textview?
I considered manual calculation depending on textsize and width, but 1) don't know the correlation and 2) due to the padding in the units of dip, different screens will use different number of actual pixels to pad.
Overall Question: I am trying to use this to solve: if given a string how can I manually edit to string such that when the textview prints the string character by character, I will know when to start a word that won't fit on one line on the next. Note: I know that textview automatically puts words that won't fit onto the next line, however, since I'm printing character by character, like typing animation, textview doesn't know the word won't fit until it prints out the overflowing characters of that word.
Been searching everywhere for this...
Thanks!
Added solutions:
one possible solution:
public String measure2 (TextView t, String s) {
String u = "";
int start = 0;
int end = 1;
int space = 0;
boolean ellipsized = false;
float fwidth = t.getMeasuredWidth();
for(;;) {
//t.setText(s.substring(start, end));
float twidth = t.getPaint().measureText(s.substring(start, end));
if (twidth < fwidth){
if (end < s.length())
end++;
else {
if (!ellipsized)
return s;
return u + s.subSequence(start, end);
}
}
else {
ellipsized = true;
space = (u + s.substring(start, end)).lastIndexOf(" ");
if (space == -1)
space = end - 1;
u += s.subSequence(start, space) + "\n";
start = space + 1;
end = start + 1;
}
}
}
solution 2, but still uses solution1 sometimes:
public String measure3 (TextView t, String s) {
List<String> wlist = Arrays.asList(s.split(" "));
if (wlist.size() == 1)
return measure2(t, s);
String u = "";
int end = 1;
float fwidth = t.getMeasuredWidth();
for(;;) {
//t.setText(s.substring(start, end));
if (wlist.isEmpty())
return u;
String temp = listStr(wlist, end);
float twidth = t.getPaint().measureText(temp);
if (twidth < fwidth){
if (end < wlist.size())
end++;
else {
return u + temp;
}
}
else {
temp = listStr(wlist, end-1);
if (end == 1)
temp = measure2(t, temp);
if (wlist.isEmpty())
return u + temp;
else
u = u + temp + "\n";
wlist = wlist.subList(end - 1, wlist.size());
end = 1;
}
}
}
public String listStr (List<String> arr, int end) {
String s = "";
for (String e : arr.subList(0, end) ){
s = s + e + " ";
}
return s.trim();
}
I used the above code to generate off a original string s, a string u that would be printed. However, I think this approach is very inefficient. Is there another approach or a better algorithm? Note: there are some errors in measure3 that I fixed, but was too lazy to edit
Try this:
private boolean isTooLarge (TextView text, String newText) {
float textWidth = text.getPaint().measureText(newText);
return (textWidth >= text.getMeasuredWidth ());
}
Detecting how many characters fit will be impossible due to the variable width of the characters. The above function will test if a particular string will fit or not in the TextView. The content of newText should be all the characters in a particular line. If true, then start a new line (and using a new string to pass as parameter).
Answer to the comment:
because the app can be run in many systems is exactly why you need to measure it.
This is a way to solve your "overall question". What is the difference between using str.size()>numCol vs is too large? You will need to implement your animation (hint #1: insert a newline character)
as I said before when you start a new line, you start a new string (hint #2: if you extend TextView, you can implement all this in overriding setText). (hint #3: Keep track of the lines created with a static int lines; and use newString.split("\\r?\\n")[lines-1] to check for length).
You can get total line of Textview and get string for each characters by below code.Then you can set style to each line whichever you want.
I set first line bold.
private void setLayoutListner( final TextView textView ) {
textView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
final Layout layout = textView.getLayout();
// Loop over all the lines and do whatever you need with
// the width of the line
for (int i = 0; i < layout.getLineCount(); i++) {
int end = layout.getLineEnd(0);
SpannableString content = new SpannableString( textView.getText().toString() );
content.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, end, 0);
content.setSpan(new StyleSpan(android.graphics.Typeface.NORMAL), end, content.length(), 0);
textView.setText( content );
}
}
});
}
Try this way.You can apply multiple style this way.
I had the same issue and I calculated the number characters per line by 2 steps:
Step 1: Calculate the number of lines
val widthOfTvComment = widthOfScreen - marginLeft - marginRight
var bounds = Rect()
var paint = Paint()
paint.textSize = textSize
paint.getTextBounds(comment,0,comment.length,bounds)
val lines = ( bounds.width()/widthOfTvComment)
Step 2: Calculated the number characters per line
val charactersPerLine = comment.length / lines

animate number change

i want to build a component that will be able to show a integer number max 5 digits in the style of the old analog car counters and animate the digit change.
that looks something like this maybe...
i have tried searching for this kinda of examples but i couldn't find anything so far.
in your opinion what is the best approach to achieve this?
i looked at the iphone alarm time picker and as far as i can tell there is only a fixed background and they push the numbers in or out the view. but how do i place the digits in this case and reference them to a particular value?
tnx.
You can try to create your own view, extending view and overriding onDraw().
Here you can use rows of numbers in bitmaps and editing their position based on the number you wish to show.
Dont forget to call invalidate() after setting new numbers to redraw the view.
I will paste an example containing a start for your project.
The bitmap number is a vertical image with numbers from 1-9 (and 0&.)
Ex.
class TickerView extends View { ..
public void setDouble(double d) {
value = d;
invalidate();
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int startx = 0;
int starty = 0;
DecimalFormat df = new DecimalFormat("#0.00");
String str = df.format(value);
String original = Double.toString(value);
Bitmap nums = BitmapFactory.decodeResource(context.getResources(),
R.drawable.numbers);
for (int i = 0; i < str.length(); i++) {
int num = 0;
try {
num = Integer.parseInt(str.charAt(i) + "");
} catch (Exception e) {
num = 10;
}
int numbefore = 0;
try {
numbefore = Integer.parseInt(original.charAt(i -1) + "");
} catch (Exception e) {
numbefore = 0;
}
canvas.drawBitmap(nums, startx + (i * 40), (starty + 40)
- (num * 50) + (numbefore), paintY);
}
paintY.setStrokeWidth(10);
canvas.drawLine(startx, starty+36, startx + (str.length() * 40), starty+36,
paintY);
canvas.drawLine(startx, starty + 90, startx + (str.length() * 40),
starty + 90, paintY);
invalidate();
}
}

Categories

Resources