Animating a specific line of a textview using android Animations - java

I am wondering if it is possible to perform a fade out/fade in animation on a specific line of a textview. I have a two line textview which I would like the "title" to stay visible while the "data" fades out and in when it changes. I am attempting to limit the number of views on my fragment so separating the two lines into separate textviews is not preferable. I am fairly new to animations and was unsure if this is possible.
Update
After Cheticamps answer I developed my own java version of his solution and wanted to post it here if anyone else was looking for this.
ValueAnimator alphaAnim = ValueAnimator.ofInt(255,0).setDuration(1000);
alphaAnim.addUpdateListener(valueAnimator -> {
int alpha = (int) valueAnimator.getAnimatedValue();
int newColor = binding.ForegroundSpanText.getCurrentTextColor() & 0x00ffff | (alpha <<24);
System.out.println("Color: "+ Integer.toHexString(newColor));
SpannableString tempStringHolder = binding.getAnimString();
if(fadingSpan !=null){
tempStringHolder.removeSpan(fadingSpan);
}
fadingSpan = new ForegroundColorSpan(newColor);
tempStringHolder.setSpan(fadingSpan, 38, animStringHolder.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
binding.setAnimString(tempStringHolder);
});
alphaAnim.addListener(new AnimatorListenerAdapter(){
#Override
public void onAnimationEnd(Animator animation){
System.out.println("Finished");
}
});

You can set a series of ForegroundColorSpans on the section of the text that you want to fade. Each successive ForegroundColorSpan will decrease the alpha of the text color until the alpha value is zero. (Alpha == 255 is fully visible; alpha == 0 is invisible.)
Animation of the alpha value of the text color associated with the ForegroundColorSpan can be accomplished with a ValueAnimator. The following shows this technique.
The TextViw is simply
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:bufferType="spannable"
android:gravity="center"
android:text="Hello\nWorld!"
android:textColor="#android:color/black"
android:textSize="48sp"/>
The code is wrapped in a button's click listener for demo purposes:
var fadingSpan: ForegroundColorSpan? = null
val spannable = binding.textView.text as Spannable
binding.button.setOnClickListener {
// android:bufferType="spannable" must be set on the TextView for the following to work.
// Alpha value varies from the 255 to zero. (We are assuming the starting alpha is 255.)
ValueAnimator.ofInt(255, 0).apply {
duration = 3000 // 3 seconds to fade
// Update listener is called for every tick of the animation.
addUpdateListener { updatedAnimation ->
// Get the new alpha value and incorporate it into the color int (AARRGGBB)
val newAlpha = updatedAnimation.animatedValue as Int
val newColor =
binding.textView.currentTextColor and 0x00ffff or (newAlpha shl 24)
if (fadingSpan != null) {
spannable.removeSpan(fadingSpan)
}
fadingSpan = ForegroundColorSpan(newColor)
spannable.setSpan(
fadingSpan,
6,
12,
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
start()
}
}

Related

Click on progressbar to seek MediaPlayer

I have a music player that has a ProgressBar that has a max value of mediaPlayer.getTotalDuration().toSeconds()
Recently I have been trying to make a MouseListener to seek the mediaPlayer to the returned X value when the ProgressBar is clicked on a certain position.
The problem: I click on the ProgressBar and it appears to be receiving milliseconds so I multiply it by 1000 so it seeks to the corresponding second-count.
This works accurately for some music/mp3s but for shorter ones, or some longer ones, the ProgressBar only jumps to the nearest possible position, or jumps to some other position, completely inaccurate due to the * 1000 calculation of the X value. (Below I've tried someone's suggestion for another answer to calculate the X value to seconds and I have also tried setting the ProgressBar value and setting that value to where the mediaPlayer is.)
"int point" is where I receive the X value.
Code:
progressBar.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent mouseClick) {
try{
progressBar.setMaximum((int)mediaPlayer.getTotalDuration().toSeconds());
int point = (int)Math.round(((double)mouseClick.getX() / (double)progressBar.getWidth()) * progressBar.getMaximum()); // previously tried "* 1000".
Duration pointDuration = new Duration(point);
mediaPlayer.seek(pointDuration);
} catch (Exception e7){
//
}
}
});
(Unfortunately that calculation is even worse.)
What sort of calculation should I use to correctly seek to the clicked position?
You have to choose your units wisely, and to make them appear in your code. What I usually do is to limit the progressbar to a [0.0,1.0] range, i.e. a percentage with double arithmetic.
When you receive a mouse click event you convert the event position in that [0,1] range.
Two things are needed:
the width (in pixels) of the progressbar
the x-coordinate of the mouseclick relative to progressbar component
And you have an accurate point (note toMilliseconds):
double dx = evt.getX();
double dwidth = progressBar.getWidth();
double progression = (dx / dwidth);
int milliseconds = (progression * mediaPlayer.getTotalDuration().toMilliseconds())
Duration duration = new Duration(milliseconds);
mediaPlayer.seek(duration);
Well 1st mistake you make is setting progressBar maximum value when you click the progress bar. While media is playing and you did not pressed progressBar yet the max value of progres bar is wrong. This value should be set when the mediaPlayer duration is changed. You should add listener to totalDurationProperty change in same method that mediaPlayer got media assigned.
Media media = new Media(filePath);
mediaPlayer = new MediaPlayer(media);
mainMediaView.setMediaPlayer(mediaPlayer);
mediaPlayer.totalDurationProperty().addListener(new ChangeListener<Duration>() {
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue,
Duration newValue) {
progressBar.setMax(newValue.toSeconds());
}
});
2nd problem is that Slider component and its progress bar have different sizes.
progressBar mouse listener:
progressBar.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent mouseClick) {
try{
double value;
value = (mouseClick.getX()-9) * (progressBar.getMax() / (progressBar.getWidth()-19));
progressBar.setValue(value);
} catch (Exception e7){
}
}
});
You might ask how did -9 and -19 showed up in there. Got those values by trial and error. As Slider component is larger than its progress bar getWidth() returns larger value. That includes gap on both sides of progress bar. -9 is for adjusting click position to gap between slider component and beginning of progress bar. -19 thats slider component width smaller by gaps on both sides of progress bar.

JavaFX: Position Axis' tick label in the middle of tick marks

I need to move the label (Text) of all tick marks in an Axis such that the text is right in the middle of its own tick mark and the next tick mark.
I am using Roland's GanttChart control (with some modifications) and Christian Schudt's DateAxis for my X-axis. My objective is to plot a gantt chart based on Date values (ignoring time; all time values are truncated).
My gantt chart has a "start date" and an "end date" for every single task (i.e. visually on the chart it is represented by a single bar).
Consider this:
I have a task starting on 1st Feb, and it ends on the same day (1st Feb). I have two ways to render this on the chart:
Render it starting from 1st Feb, and ends at 1st Feb. This bar is effectively hidden, because its width is 0.
Render it starting from 1st Feb, with the right-edge of the bar touching 2nd Feb. This can potentially confuse the users because it would look like it starts from 1st Feb and ends on 2nd Feb.
To solve the problem caused by method 2, I need to shift the text labels by half a tick mark width to the right. Doing this would make it very clear to the user.
I have tried doing this:
final DateAxis xAxis = (DateAxis) this.ganttchart.getXAxis();
xAxis.getChildrenUnmodifiable().addListener(new ListChangeListener<Node>()
{
#Override
public void onChanged(javafx.collections.ListChangeListener.Change<? extends Node> c)
{
final List<Node> labels = xAxis.getChildrenUnmodifiable().filtered(node -> node instanceof Text);
for (Node label : labels)
{
label.setTranslateX(xAxis.getWidth() / (labels.size() - 1) / 2);
}
}
});
However, doing so does not seem to shift the Text labels to the right at all.
The method I had tried actually works, but I was having problem with race condition. If I used Platform.runLater(), it would shift correctly. However, the position flickers whenever I resize the chart (it would jump between original position and shifted position).
This is the complete working version, which I need to override layoutChildren() in DateAxis.
#Override
protected void layoutChildren()
{
if (!isAutoRanging())
{
currentLowerBound.set(getLowerBound().getTime());
currentUpperBound.set(getUpperBound().getTime());
}
super.layoutChildren();
/*
* Newly added codes.
*/
final List<Node> labels = this.getChildrenUnmodifiable().filtered(node -> node instanceof Text);
for (Node label : labels)
{
if (this.getSide() == Side.LEFT || this.getSide() == Side.RIGHT)
label.setTranslateY(this.getHeight() / (labels.size() - 1) / 2);
else if (this.getSide() == Side.TOP || this.getSide() == Side.BOTTOM)
label.setTranslateX(this.getWidth() / (labels.size() - 1) / 2);
}
/*
* End of new codes.
*/
}
Update
There was still some layout bugs. It has to do with the fact that Axis.positionTextNode() positions the texts using getBoundsInParent(), which takes into consider the translation.
This is the new working version, hopefully someday it would be helpful to someone.
#Override
protected void layoutChildren()
{
if (!isAutoRanging())
{
currentLowerBound.set(getLowerBound().getTime());
currentUpperBound.set(getUpperBound().getTime());
}
super.layoutChildren();
/*
* Newly added codes.
*/
final List<Node> labels = this.getChildrenUnmodifiable().filtered(node -> node instanceof Text);
for (Node label : labels)
{
if (this.getSide().isHorizontal())
{
if (label.getTranslateX() == 0)
label.setTranslateX(this.getWidth() / (labels.size() - 1) / 2);
else
{
label.setLayoutX(label.getLayoutX() + label.getTranslateX());
}
}
else if (this.getSide().isVertical())
{
if (label.getTranslateY() == 0)
label.setTranslateY(this.getHeight() / (labels.size() - 1) / 2);
else
{
label.setLayoutY(label.getLayoutY() + label.getTranslateY());
}
}
}
/*
* End of new codes.
*/
}

Paginating text in Android

I am writing a simple text/eBook viewer for Android, so I have used a TextView to show the HTML formatted text to the users, so they can browse the text in pages by going back and forth. But my problem is that I can not paginate the text in Android.
I can not (or I don't know how to) get appropriate feedback from the line-breaking and page-breaking algorithms in which TextView uses to break text into lines and pages. Thus, I can not understand where the content ends in the actual display, so that I continue from the remaining in the next page. I want to find way to overcome this problem.
If I know what is the last character painted on the screen, I can easily put enough characters to fill a screen, and knowing where tha actual painting was finished, I can continue at the next page. Is this possible? How?
Similar questions have been asked several times on StackOverflow, but no satisfactory answer was provided. These are just a few of them:
How to paginate long text into pages in Android?
Ebook reader pagination issue in android
Paginate text based on rendered text size
There was a single answer, which seems to work, but it is slow. It adds characters and lines until the page is filled. I don't think this is a good way to do page breaking:
How to break styled text into pages in Android?
Rather than this question, it happens that PageTurner eBook reader does it mostly right, although it is somehow slow.
https://github.com/nightwhistler/pageturner
PS: I am not confined to TextView, and I know line breaking and page breaking algorithms can be quite complex (as in TeX), so I am not looking for an optimal answer, but rather a reasonably fast solution that can be usable by the users.
Update: This seems to be a good start for getting the right answer:
Is there a way of retrieving a TextView's visible line count or range?
Answer: After completing text layout, it is possible to find out the visible text:
ViewTreeObserver vto = txtViewEx.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
ViewTreeObserver obs = txtViewEx.getViewTreeObserver();
obs.removeOnGlobalLayoutListener(this);
height = txtViewEx.getHeight();
scrollY = txtViewEx.getScrollY();
Layout layout = txtViewEx.getLayout();
firstVisibleLineNumber = layout.getLineForVertical(scrollY);
lastVisibleLineNumber = layout.getLineForVertical(height+scrollY);
}
});
NEW ANSWER
PagedTextView library (in Kotlin) summarises the below lying algorithm by extending Android TextView. The sample app demonstrates the usage of the library.
Setup
dependencies {
implementation 'com.github.onikx:pagedtextview:0.1.3'
}
Usage
<com.onik.pagedtextview.PagedTextView
android:layout_width="match_parent"
android:layout_height="match_parent" />
OLD ANSWER
The algorithm below implements text pagination in separation of TextView itself lacking simultaneous dynamic change of both the TextView attributes and algorithm configuration parameters.
Background
What we know about text processing within TextView is that it properly breaks a text by lines according to the width of a view. Looking at the TextView's sources we can see that the text processing is done by the Layout class. So we can make use of the work the Layout class does for us and utilizing its methods do pagination.
Problem
The problem with TextView is that the visible part of text might be cut vertically somewhere at the middle of the last visible line. Regarding said, we should break a new page when the last line that fully fits into a view's height is met.
Algorithm
We iterate through the lines of text and check if the line's bottom exceeds the view's height;
If so, we break a new page and calculate a new value for the cumulative height to compare the following lines' bottom with (see the implementation). The new value is defined as top value (red line in the picture below) of the line that hasn't fit into the previous page + TextView's height.
Implementation
public class Pagination {
private final boolean mIncludePad;
private final int mWidth;
private final int mHeight;
private final float mSpacingMult;
private final float mSpacingAdd;
private final CharSequence mText;
private final TextPaint mPaint;
private final List<CharSequence> mPages;
public Pagination(CharSequence text, int pageW, int pageH, TextPaint paint, float spacingMult, float spacingAdd, boolean inclidePad) {
this.mText = text;
this.mWidth = pageW;
this.mHeight = pageH;
this.mPaint = paint;
this.mSpacingMult = spacingMult;
this.mSpacingAdd = spacingAdd;
this.mIncludePad = inclidePad;
this.mPages = new ArrayList<>();
layout();
}
private void layout() {
final StaticLayout layout = new StaticLayout(mText, mPaint, mWidth, Layout.Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, mIncludePad);
final int lines = layout.getLineCount();
final CharSequence text = layout.getText();
int startOffset = 0;
int height = mHeight;
for (int i = 0; i < lines; i++) {
if (height < layout.getLineBottom(i)) {
// When the layout height has been exceeded
addPage(text.subSequence(startOffset, layout.getLineStart(i)));
startOffset = layout.getLineStart(i);
height = layout.getLineTop(i) + mHeight;
}
if (i == lines - 1) {
// Put the rest of the text into the last page
addPage(text.subSequence(startOffset, layout.getLineEnd(i)));
return;
}
}
}
private void addPage(CharSequence text) {
mPages.add(text);
}
public int size() {
return mPages.size();
}
public CharSequence get(int index) {
return (index >= 0 && index < mPages.size()) ? mPages.get(index) : null;
}
}
Note 1
The algorithm works not just for TextView (Pagination class uses TextView's parameters in the implementation above). You may pass any set of parameters StaticLayout accepts and later use the paginated layouts to draw text on Canvas/Bitmap/PdfDocument.
You can also use Spannable as yourText parameter for different fonts as well as Html-formatted strings (like in the sample below).
Note 2
When all text has the same font size, all lines have equal height. In that case you might want to consider further optimization of the algorithm by calculating an amount of lines that fits into a single page and jumping to the proper line at each loop iteration.
Sample
The sample below paginates a string containing both html and Spanned text.
public class PaginationActivity extends Activity {
private TextView mTextView;
private Pagination mPagination;
private CharSequence mText;
private int mCurrentIndex = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pagination);
mTextView = (TextView) findViewById(R.id.tv);
Spanned htmlString = Html.fromHtml(getString(R.string.html_string));
Spannable spanString = new SpannableString(getString(R.string.long_string));
spanString.setSpan(new ForegroundColorSpan(Color.BLUE), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new RelativeSizeSpan(2f), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new StyleSpan(Typeface.MONOSPACE.getStyle()), 0, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new ForegroundColorSpan(Color.BLUE), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new RelativeSizeSpan(2f), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanString.setSpan(new StyleSpan(Typeface.MONOSPACE.getStyle()), 700, spanString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mText = TextUtils.concat(htmlString, spanString);
mTextView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Removing layout listener to avoid multiple calls
mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mPagination = new Pagination(mText,
mTextView.getWidth(),
mTextView.getHeight(),
mTextView.getPaint(),
mTextView.getLineSpacingMultiplier(),
mTextView.getLineSpacingExtra(),
mTextView.getIncludeFontPadding());
update();
}
});
findViewById(R.id.btn_back).setOnClickListener(v -> {
mCurrentIndex = (mCurrentIndex > 0) ? mCurrentIndex - 1 : 0;
update();
});
findViewById(R.id.btn_forward).setOnClickListener(v -> {
mCurrentIndex = (mCurrentIndex < mPagination.size() - 1) ? mCurrentIndex + 1 : mPagination.size() - 1;
update();
});
}
private void update() {
final CharSequence text = mPagination.get(mCurrentIndex);
if(text != null) mTextView.setText(text);
}
}
Activity's layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/btn_back"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#android:color/transparent"/>
<Button
android:id="#+id/btn_forward"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#android:color/transparent"/>
</LinearLayout>
<TextView
android:id="#+id/tv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
Screenshot:
Take a look at my demo project.
The "magic" is in this code:
mTextView.setText(mText);
int height = mTextView.getHeight();
int scrollY = mTextView.getScrollY();
Layout layout = mTextView.getLayout();
int firstVisibleLineNumber = layout.getLineForVertical(scrollY);
int lastVisibleLineNumber = layout.getLineForVertical(height + scrollY);
//check is latest line fully visible
if (mTextView.getHeight() < layout.getLineBottom(lastVisibleLineNumber)) {
lastVisibleLineNumber--;
}
int start = pageStartSymbol + mTextView.getLayout().getLineStart(firstVisibleLineNumber);
int end = pageStartSymbol + mTextView.getLayout().getLineEnd(lastVisibleLineNumber);
String displayedText = mText.substring(start, end);
//correct visible text
mTextView.setText(displayedText);
Surprisingly finding libraries for Pagination is difficult. I think it's better to use another Android UI element besides TextView. How about WebView?
An example # android-webview-example.
Code snippet:
webView = (WebView) findViewById(R.id.webView1);
String customHtml = "<html><body><h1>Hello, WebView</h1></body></html>";
webView.loadData(customHtml, "text/html", "UTF-8");
Note: This simply loads data onto a WebView, similar to a web browser. But let's not stop with just this idea. Add this UI to using pagination by WebViewClient onPageFinished . Please read on SO link # html-book-like-pagination.
Code snippet from one of the best answer by Dan:
mWebView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
...
mWebView.loadUrl("...");
}
});
Notes:
The code loads more data upon page scroll.
On the same webpage, there is a posted answer by Engin Kurutepe to set measurements for the WebView. This is necessary for specifing a page in pagination.
I have not implemented pagination but I think this is a good start and shows promise, should be fast. As you can see, there are developers that have implemented this feature.

LibGdx: Table not displayed correctly

I have a table in libgdx which is supposed to have 6 buttons, organised into 2 rows. Here's my code:
elementsTable.clear();
String[] colors = new String [6];
colors[0] = "red";
colors[1] = "orange";
colors[2] = "yellow";
colors[3] = "purple";
colors[4] = "blue";
colors[5] = "green";
String category = currentCategory.name();
category = category.substring(0, category.length()-1).toLowerCase(); //removes the last character because otherwise there would be an extra 's'
int count = 0;
for(String color : colors) {
ButtonStyle buttonStyle = new ButtonStyle();
buttonStyle.up = cellsSkin.getDrawable(category + "_" + color);
Button button = new Button(buttonStyle);
button.addListener(toolBoxListener);
button.setName(category + "_" + color);
button.setSize(toolBoxButtonSize, toolBoxButtonSize);
elementsTable.add(button).pad(toolBoxButtonPadding);
count ++;
if (count == Math.ceil((colors.length/2d))) elementsTable.row();
}
That's the filling. My buttons are of 6 different colors so I loop over the array with the color names to access the skin to get the drawable for the buttons. This part seems to work because when I debug and look at the table, it has the 6 buttons in there with the right size.
Then I set the position and size of my table and add it to the stage.:
elementsTable.setSize(toolBoxWidth, 500/(float)(3*toolBoxButtonSize+6*toolBoxButtonPadding)*elementsTable.getHeight());
elementsTable.setPosition(195, 30);
stage.addActor(elementsTable);
The stage has an orthographic camera set that should scale things down just fine.
However, what I get is this:
Another strange thin is that when I look at the table's height and width in debug mode, it says 0 even thoug there are elements in it that all have the corrct size.
Can anyone help me?
If you have any further questions on the problem, please ask! I tried to give a detailed description, but I am happy to answer your questions.
It seemsits just the position. 30 is too low for the Y:
elementsTable.setPosition(195, 30);
Try 150 for example:
elementsTable.setPosition(195, 150);
Remember the coordinate system that Libgdx (and OpenGL) use is with Y going up.

Display text progressively with libgdx

I am looking for a way to display text progressively with libgdx, but I can't find a way to do it exactly the way I want. Here is what I did:
I have a text label that is being updated periodically to display a different text. The label is set to setWrap(true); and setAlignment(Align.center);
Every time I change the text of the label I use a custom Action which I built like this
public class DisplayTextAction extends TemporalAction{
private CharSequence completeText;
#Override
protected void update(float percent) {
((Label)actor).setText(
completeText.subSequence(
0,
(int)Math.round(completeText.length()*percent));
}
public void setText(String newText){
completeText = newText;
}
}
Every text update, I call the action from a pool, change the text and add the action to the label.
Here is my problem: This doesn't work the way I want with a centered and wrapped text.
This happens when text isn't centered (dots represent space):
|h........|
|hel......|
|hello....|
(Works as intended)
This is what happens when the text is centered:
|....h....|
|...hel...|
|..hello..|
And this is how I want it to behave:
|..h......|
|..hel....|
|..hello..|
My original idea to fix this was to use 2 sets of strings, one that is the visible text, and one invisible that acts as "padding". I came up with something like this:
CharSequence visibleText = completeText.subSequence(
0,
(int)Math.round(completeText.length()*percent));
CharSequence invisibleText = completeText.subSequence(
(int)Math.round(completeText.length()*percent),
completeText.length());
So I have my two sets of strings, but I can't find a way to display two different fonts (one visible, and another one which is the same but with an alpha of 0) or styles in the same label with Libgdx.
I'm stuck, I don't know if my approach is the right one or if I should look into something completely different, and if my approach is correct, I don't know how to follow it up using libgdx tools.
EDIT:
I followed Jyro117's instructions and I could make great progress, but I couldn't make it work with centred text on multiple lines.
imagine this text:
|all those lines are|
|..for a very long..|
|........text.......|
And it has to be displayed like this
|all th.............|
|...................|
|...................|
|all those line.....|
|...................|
|...................|
|all those lines are|
|..for a ve.........|
|...................|
|all those lines are|
|..for a very long..|
|........text.......|
Jyro117's solution give either
|all those lines are|
|for a very long....|
|text...............|
displayed correctly.
or
|...................|
|......all tho......|
|...................|
|...................|
|...all those lin...|
|...................|
|all those lines are|
|......for a v......|
|...................|
You are over-complicating the solution. All you really need is to determine the size of the label when all the text is added. Once you have determined that, lock the label size to those dimensions, put it inside of a table that expands to fill up the area around it, and then update your label with the action. (You can use a pool and such as needed, but for simplicity I left that out of the code below).
You will have to obviously adapt the code to yours, but this gives you a code reference to what I mean.
Here is a code snippet on one way to do it:
stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
Gdx.input.setInputProcessor(stage);
uiSkin = new Skin(Gdx.files.internal("skin/uiskin.json"));
Table fullScreenTable = new Table();
fullScreenTable.setFillParent(true);
final String message = "hello";
final Label progressLabel = new Label(message, this.uiSkin);
final TextBounds bounds = progressLabel.getTextBounds(); // Get libgdx to calc the bounds
final float width = bounds.width;
final float height = bounds.height;
progressLabel.setText(""); // clear the text since we want to fill it later
progressLabel.setAlignment(Align.CENTER | Align.TOP); // Center the text
Table progressTable = new Table();
progressTable.add(progressLabel).expand().size(width, height).pad(10);
final float duration = 3.0f;
final TextButton button = new TextButton("Go!", this.uiSkin);
button.addListener(new ClickListener() {
#Override public void clicked(InputEvent event, float x, float y) {
progressLabel.addAction(new TemporalAction(duration){
LabelFormatter formatter = new LabelFormatter(message);
#Override protected void update(float percent) {
progressLabel.setText(formatter.getText(percent));
}
});
}
});
stage.addActor(button);
fullScreenTable.add(progressTable);
fullScreenTable.row();
fullScreenTable.add(button);
stage.addActor(fullScreenTable);
Edit:
Added code to center and top align text in label. Also added code to fill spaces on the end to allow for proper alignment. Note: Only useful for mono-spaced fonts.
class LabelFormatter {
private final int textLength;
private final String[] data;
private final StringBuilder textBuilder;
LabelFormatter(String text) {
this.textBuilder = new StringBuilder();
this.data = text.split("\n");
int temp = 0;
for (int i = 0 ; i < data.length; i++) {
temp += data[i].length();
}
textLength = temp;
}
String getText(float percent) {
textBuilder.delete(0, textBuilder.length());
int current = Math.round(percent * textLength);
for (final String row : data) {
current -= row.length();
if (current >= 0) {
textBuilder.append(row);
if (current != 0) {
textBuilder.append('\n');
}
} else {
textBuilder.append(row.substring(0, row.length() + current));
// Back fill spaces for partial line
for (int i = 0; i < -current; i++) {
textBuilder.append(' ');
}
}
if (current <= 0) {
break;
}
}
return textBuilder.toString();
}
}

Categories

Resources