I have a TextView match parented for whole screen, and a text appended on it.
For example, the Raven poem:
Once upon a midnight dreary, while I pondered, weak and weary,
Over many a quaint and curious volume of forgotten lore,
While I nodded, nearly napping, suddenly there came a tapping,
As of some one gently rapping, rapping at my chamber door.
"'Tis some visiter," I muttered, "tapping at my chamber door—
Only this, and nothing more." etc etc etc...
I can't see it all on my phone screen.
So the question:
How can I get the index of the last visible character of the text?
How can I see it
If you want index then use,
val poem = "hello"
val lastCharacterIndex = poem.toCharArray().size - 1
If you want the Character at that index then use,
val character = poem.toCharArray()[index]
But I think you want to make your text view scrollable so that you can see the whole poem. Then you can use ScrollView as parent tag in XML then wrap the text view inside it.
Many many thanks for Mike M here the answer:
fun TextView.getLastVisibleCharacter(): Int {
val layoutBottom = height + scrollY - totalPaddingTop - totalPaddingBottom
val bottomLine = layout.getLineForVertical(layoutBottom)
val lastWhollyVisibleLineNumber =
if (layout.getLineBottom(bottomLine) > layoutBottom) bottomLine - 1
else bottomLine
return layout.getLineVisibleEnd(lastWhollyVisibleLineNumber) - 1
}
Related
I am trying to create a small game with the following components:
9 cells - in a GridView
some words (that have to be introduced in the table, each on a cell)
TextToSpeech - on each cell
Each single cell contains one single word and the user has to choose one of the words displayed below in order to complete the cell (2 words are supposed to be in a cell).
Everything works perfectly, the only problem is that the TextToSpeech is being activated once the user chooses the right word, but I would want it to be one step forward. That means, once the user chooses the right word for ONE cell, the TextToSpeech should be automatically activated for the NEXT cell, so once the user has chosen the right word, he should hear the next word from the next cell.
These cells don't have ids, they are in the MainActivity, like this:
private fun setData() {
dataBeanArrayList = ArrayList<CellDataBean>()
val cellDataBean = CellDataBean()
//we store in the var cellDataBean both texts
//according to the class CellDataBean
cellDataBean.text_one = "Hello"
cellDataBean.text_two = "World"
dataBeanArrayList!!.add(cellDataBean)
val cellDataBean1 = CellDataBean()
cellDataBean1.text_one = "Okay"
cellDataBean1.text_two = "Not bad"
If I implement a new text view (which is not preferable), the TextToSpeech only works like I want for the first cell, once you press on this:
starttv.setOnClickListener {
if (count == 0) {
isstart = true
val text: String = dataBeanArrayList[count].text_1
textToSpeech!!.speak(text, TextToSpeech.QUEUE_FLUSH, null)
dataBeanArrayList[count].isSelected = true
cellAdapter!!.notifyDataSetChanged()
}
}
I am trying to implement something like: (where 'mainbacklay' is the ID from the cell)
when(vh.mainbacklay)[position]==0{
// here the text to speech should be enabled for the text from the second cell with the position 1
}
So when the position 2 from the cell is activated, the text to speech should be enabled for the text with the third position and so on.
Could someone help me figure this out, please?
Thank you.
I don't have a clear picture of exactly how your app operates, but I think you want something like this:
fun setAnswer(cellNumber: Int, text: String) {
// Update the data with the selected text etc
// Announce the text for cellNumber+1, or a fallback when there is no next cell
val message = dataBeans.getOrNull(cellNumber + 1)?.text_1 ?: "You're done!"
textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null)
}
and then whenever your event (a click or whatever) happens for cell n, you call that function. It takes care of handling that event, managing the overall state, and doing anything that needs to happen as a result (like announcing the next cell's contents, or moving to an endgame screen, etc.
Basically you don't want your individual cells / TextViews / ViewHolders managing that overall state and interacting with each other and each other's data. Just hand it off to a central function or component that manages what's going on, and tells the UI what to display. Makes it a lot easier!
Hope that helps, I can only be general without seeing exactly what you're doing
I'm using an EditText control that I allow text formatting of (bold, italics etc.).
To apply formatting, within my TextWatcher's AfterTextChanged event handler I detect whether a formatting style, such as bold, has been toggled on via the UI. If it is, I've tried two different approaches, neither of which are satisfactory for different reasons:
Approach 1
textView.EditableText.SetSpan(new StyleSpan(TypefaceStyle.Bold), start, end, SpanTypes.ExclusiveExclusive);
For the start value, I've tried using _textView.SelectionStart - 1 or the starting position when the StyleSpan was first applied. And for the end value _textView.SelectionStart.
Although the text appears formatted fine using this method, it creates unnecessary StyleSpans when only the single would suffice. This is clear when I try to save the text to my local db through a Html conversion:
string html = Html.ToHtml(new SpannableString(Fragment_Textarea.Instance().Textarea().EditableText));
For example, instead of <b>this is bold text</b>, I'm getting <b><b><b><b><b><b><b><b><b><b><b><b><b><b><b><b><b>this is bold text</b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b></b>. So, clearly, I'm doing something wrong/being inefficient in this approach. What obviously this leads to is eventual slowdowns when both inputting text as well as retrieving at launch.
Something I've considered is to check whether there's a Span on the preceding character (_textView.SelectionStart - 1), and, if yes, to remove the span, and then add a span that starts at that point up until _textView.SelectionStart i.e. ensures there's only a single Span by constantly checking/removing/adding the necessary Span. But this seems like another inefficient method to handle this.
Approach 2
textView.EditableText.SetSpan(new StyleSpan(TypefaceStyle.Bold), start, end, SpanTypes.ExclusiveInclusive);
So, this doesn't lead to the same inefficiencies as above, but because of the SpanTypes.ExclusiveInclusive flag, I'm unable to stop the style formatting to end when I toggle it off via the UI. In other words, when I toggle the Bold style on, all text that follows will be formatted in bold styling, even when I've turned its toggle off.
Of the two, this seems to me like the one that's the correct general approach, and so I'm wondering whether I can do anything to stop the style being applied as soon as I turn its toggle off. Or is there another way that I've missed altogether as best practice for handling this sort of requirement.
So, I ended up taking quite a different approach by moving the responsibility of setting the span to when the button on the Toolbar to active a style is toggled (as opposed to in any of the text changed listeners).
For example, when the bold style is toggled on, its event handler runs hitting the following code:
int start = _textarea.SelectionStart - 1;
var spanType = SpanTypes.ExclusiveInclusive;
_textarea.EditableText.SetSpan(new StyleSpan(TypefaceStyle.Bold), start, _textarea.SelectionStart, spanType);
The span type needs to be ExclusiveInclusive as suggested above. The trick is to change this as soon as the style has been toggled off. This is relatively straightforward if you're typing in bold and then turn the style off (just a matter of finding the span, removing it and then adding a new span with same start/end points but that's ExcExc). But I needed the code to be more flexible and account for a situation whereby you may later decide to type within the span text of another style. For example, let's say I start with:
This is bold text
But then I edit and want to change it to:
This is bold yes text
In such a scenario, I need to make sure I create an ExclusiveExclusive bold span on either side of the "yes". :
int start = -1;
int end = -1;
List<Tuple<int, int>> respans = new List<Tuple<int, int>>();
// go through all relevant spans that start from -1 indices ago
var spans = _textarea.EditableText.GetSpans(_textarea.SelectionStart - 1, _textarea.SelectionStart, Class.FromType(typeof(StyleSpan)));
if (spans.Length > 0)
{
for (int u = 0; u < spans.Length; u++)
{
// found a matching span!
if (((StyleSpan)spans[u]).Style == TypefaceStyle.Bold)
{
// get the starting and ending indices for the iterated span
start = _textarea.EditableText.GetSpanStart(spans[u]);
end = _textarea.EditableText.GetSpanEnd(spans[u]);
// remove the span
_textarea.EditableText.RemoveSpan(spans[u]);
// if the current index is less than when this iterated span ended
// and greater than when it started
// then that means non-bold text is being inserted in the middle of a bold span
// that needs to be split into 2 (before current index + after current index)
if (_textarea.SelectionStart > start && _textarea.SelectionStart < end)
{
respans.Add(new Tuple<int, int>(start, _textarea.SelectionStart - 1));
for(int c = _textarea.SelectionStart + 1; c < _textarea.Length(); c++ )
{
if(_textarea.Text[c] != ' ' )
{
respans.Add(new Tuple<int, int>(c, end));
break;
}
}
}
// otherwise, the recreated span needs to start and end when the iterated did
// with one important change in relation to its span type
else
{
respans.Add(new Tuple<int, int>(start, end));
}
}
}
// if there are 1 or more spans that need to be restored,
// go through them and add them back according to start/end points set on their creation
// as an ExclusiveExclusive span type
if( respans.Count > 0 )
{
foreach( Tuple<int,int> tp in respans )
{
_textarea.EditableText.SetSpan(new StyleSpan(TypefaceStyle.Bold), tp.Item1, tp.Item2, SpanTypes.ExclusiveExclusive);
}
}
}
This seems to be doing the job: spans are created/managed when the UI is interacted with (and not text changed) 👍
I am trying to interact with dynamically generated buttons. I want to update text and background color for those player clicked and for those who is near by horizontal or vertical axis at the moment of a click.
What I've tried. I've found an id property of a button in XML which led me to idea that I can make a text key to refer to a programmatically generated button. But when I've tried to assign an id - IDE was expecting a number (Int), not a string. Since buttons form a square array - I decided to encode each button via 4 digit number where first 2 digits stand for a row number and other two stand for a column number. Though when I tried to use findViewById IDE told me that it was expecting a special id data type, not a number.
That's how it looks for now:
for (i in 1..size) {
for (j in 1..size){
val button = Button(this)
button.id = i*100 + j
constraintLayout.addView(button)
}
}
What idea or method could I look at?
If you created it dynamically you can save it in a variable (or an array) for later use.
val myButtons = ArrayList<Button>()
for (i in 1..size) {
for (j in 1..size){
val button = Button(this)
myButtons.add(button)
constraintLayout.addView(button)
}
}
if you have a layout with dynamically created views and you know their order you can get them with getChildAt(index).
Alternatively you can assign ids saved in xml like this.
as getting into android i decided to replace the default calculator with mine. A simple calculator with the 4 operational signs. I've been giving to all buttons the right behaviour, storing every number in a 'num' ArrayList(String) and signs in a 'sign' ArrayList(String).
What i wanted to do, was to then combine numbers and signs into a string, parse it into a float and getting a result. I thought this was one of the easy/simple ways to deal with it, since when you set a float like this:
float f = 6*4-5/2+3
it gives you the right result. but it clearly does not when starting from a String, like this:
String s = "6*4-5/2+3"
Float f = Float.valueOf(s)
Is there a way to getting a result from my 2 ArrayList(String)? In the negative case, what would be a doable approach (in the sense im not an experienced programmer)I?
I Think this approach is incorrect.
I would do the following:
You would have a Textview or Edittext as the calculator "screen" on top.
then you would have all your number and operation signs buttons.
Now, every number you press, it will append to the last one on the screen, using .append()
once you tap on an operator sign - two things will happen:
1) the number in the textView will be stored as a Float (using Float.valueOf(yourTextView); in a varibale, say firstNum.
2) you will save the operator you clicked in a second variable, say String calcOper.
Now, you enter your second number, and then you would press the Equals sign.
What will happen then is simply use a Switch of If expression.
If calcOper is "-" - then do firstNum- Current number shown on screen.
If calcOper is "+" - then do firstNum+ Current number shown on screen.
At last don't forget to set the text on the TextView the result.
Good luck!
i know highlighting in text-view is possible
Highlight Textview Using EditText
and scrolling text-view is also possible
(got the scroll code from here and is successfully scrolling too)
textView scroll at first line
now the question is, i am searching and i want to highlight that text and navigate to it, when someone presses the search button, the highlighting part is perfect, now i can get the index of the word in the string, but not line number of the string in the text-view,
point is if i want to find a position of certain text in text-view, i.e. which line number is that on, how to do it ?
i found an answer for this, but later i realized its for iOS
Search occurrences of a string in a TextView
Correct if I am wrong, you want to know the position where a particular text is in a String? If so then you can do it by using the following
String text = "0123hello9012hello8901hello7890";
String word = "hello";
Log.d("startOfWordPosition",text.indexOf(word)); // prints "4"
Log.d("endOfWordPosition",text.lastIndexOf(word)); // prints "22"
As you see that it will tell you the position as to where the word is located but you have to think about case where a word may come more than once. If you are sure that word will occur only once then above code is perfect for you. If not, then you will have to somehow tackle the problem.
This is working for loading a file into a textview so that the user's last selection or cursor position is at the top of the screen.
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
page.setSelection(pos, pos2);
Layout layout = page.getLayout();
scroll.scrollTo(0, layout.getLineTop(layout.getLineForOffset(pos2))); }
}, 500);
page is a TextView, pos and pos2 are ints and the two ends of the last selection by the user (they are the same int if it's just the cursor), scroll is a scrollview containing the textview. It is all in a Handler because of delay issues internal to Android's loading all these objects. Th filename, pos and pos2 are saved as settings on exit.