In my Android App I've created 8 TextViews stacked on top of each other. Now I want to load in some plain text into those TextView-Lines. At the moment my Strings have a ";" as delimiter to indicate a line break, however it would be much more convenient if I would detect a linebreak automatically instead of using the hardcoded semicolon approach.
This is my String at the moment:
myString = "" +
"This seems to be some sort of spaceship,;" +
"the designs on the walls appear to be of;" +
"earth origin. It looks very clean here.;"
And in my other class I load in this string into the 8 TextViews, which I've loaded into an ArrayList, using the ";" as a delimiter.
public fun fillLines(myString: String) {
// How To Make Line Breaks Automatic??
for(i: Int in str until myString.split(";").size) {
if(i > textViewArray.size - 1) {
break
}
textViewArray[i].text = myString.split(";")[i]
textViewArray[i].alpha = 1.0f
}
}
Is there any way I can get the same result as shown above but without hardcoding the delimiter as ";" but instead somehow automatically detect the line break which would occur inside the TextView and then use this as a delimiter to advance through all 8 TextView "Lines".
The reason I need 8 TextViews Stacked On top of each other as individual "text lines" is because of an animation technique I want to use.
Line-breaking gets fairly complicated, so my recommendation would be that you allow a TextView to perform the measuring and layout to determine the line breaks. You could have an invisible TextView with the same style as your other views, and attach it to the layout so that it has the same width as your individual TextView instances. From there, add a layout change listener, and you can then retrieve the individual lines from the TextView Layout:
myTextView.text = // your text string here
myTextView.addOnLayoutChangeListener { view, _, _, _, _, _, _, _, _ ->
(view as? TextView)?.layout?.let { layout ->
// Here you'll have the individual broken lines:
val lines = (0 until layout.lineCount).map {
layout.text.subSequence(layout.getLineStart(it), layout.getLineVisibleEnd(it)
}
}
}
That said, this comes with the caveat that you'll lose out on hyphenation provided by the TextView, so you may wish to disable hyphenation entirely in your case.
You could fill text view with html. Below example.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
tvDocument.setText(Html.fromHtml(bodyData,Html.FROM_HTML_MODE_LEGACY));
} else {
tvDocument.setText(Html.fromHtml(bodyData));
}
If your delimiter ; it is possible call method replaceAll(";", "<br>");
Ok I got it working now:
First you must add these properties for the textviews:
android:singleLine="true"
android:ellipsize="none"
Then you can do this:
public fun fillStorylines() {
val linecap = 46
var finalLine: String
var restChars = ""
val index = 9999
val text1: String = "" +
"This seems to be some sort of spaceship, " +
"the designs on the walls appear to be of " +
"earth origin. It looks very clean here. "
for(j: Int in 0..index) {
try {
finalLine = ""
val lines: List<String> = (restChars + text1.chunked(linecap)[j]).split(" ")
for (i: Int in 0 until lines.size - 1) {
finalLine += lines[i] + " "
}
textViewArray[j].text = finalLine
textViewArray[j].alpha = 1.0f
restChars = lines[lines.size - 1]
} catch (ex: Exception) {
break
}
}
}
If anyone knows a more elegant way to solve this please go ahead, your feedback is appreciated :)
Related
Sorry for the big amount of code guys. Im at a loss lol. I needed a way in an EditText to overwrite chars, but skip the two spaces when the curser comes to them. So that spaces will be "Permanent" in a sense. This is for a basic hexadecimal editor style text box. While this somewhat does work, an when it does its slick. But it seems to have some flaw I am missing, Sometimes when you re typing it will start inserting characters, specifically when the curser is in between two chars ex this is before : "01 02 0|3 04 05" if you type 5 it should replace the 3, then skip over the spaces and end up at the next 0 But it ends up either one of two things, either inserting "01 02 05|3 04 05" or overwriting the 3, and removing one of the two space while jumping "01 02 05 |04 05". lastly it used to sometimes replace a space when the curser was next to a pace but didn't jump over the two spaces, I believe I have worked this out but I've been working on the other problems so I may have been blinded a bit and not noticed it. I'm guessing my text watcher is not being invoked by either formating var not returning to false, or some other thing I've overlooked. Because once curser is moved IE you touch somewhere else in the text, it begins to work briefly till it ends up inserting in between digits again. Anyone see anything I may have missed?
Here is the code so far:
class CustomEditText : AppCompatEditText {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private var isFormatting = false
private var mStart = -1 // Start position of the Text being modified
private var mEnd = -1 // End position of the Text being modified
private val watcher = object : TextWatcher {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
mStart = -1
// Keep track of the start and end position of the text change
if (before == 0) {
// The start and end variables have not been set yet, and there are no characters being deleted.
// Set the start position to the current cursor position.
mStart = start + count
// Set the end position to either the end of the string or the current cursor position + count.
mEnd = min(mStart + count, s!!.length)
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// No action needed before text change
}
override fun afterTextChanged(s: Editable?) {
// Avoid infinite recursion if the TextWatcher is modifying the text
if (!isFormatting) {
if (mStart >= 0) {
// Set `isFormatting` to true to indicate that we're currently formatting the text.
isFormatting = true
// Check if the current position is a digit
if (Character.isDigit(s!![mStart - 1])) {
// Check if the next position is a space or the end of the string
if (mStart < s.length && s[mStart] == ' ' || mStart == s.length) {
// If the next position is a space or the end of the string, insert the digit at the next position
s.insert(mStart, s[mStart - 1].toString())
mStart++
} else {
// Overwrite the text at the current position
s.replace(mStart, mEnd, "")
}
} else if (s[mStart - 1] == ' ') {
// Check if the next position is a digit
if (mStart + 1 < s.length && Character.isDigit(s[mStart + 1])) {
// Jump over the spaces and overwrite the first character in the next set
mStart = s.indexOf(" ", mStart) + 2
s.replace(mStart, mStart + 1, "")
} else {
// Overwrite the text at the current position
s.replace(mStart, mEnd, "")
}
} else {
// Overwrite the text at the current position
s.replace(mStart, mEnd, "")
}
isFormatting = false
}
}
}
}
init {
// Initiate and add the text change listener "watcher"
addTextChangedListener(watcher)
}
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
// Get the current text in the EditText
val text = text
if (text != null) {
val len = text.length
// If the selection start and end are equal, meaning the cursor is at a certain position
if (selStart == selEnd) {
// Check if the cursor is positioned at a space between two hexadecimal digits
// And if the character after the space is also a space
if (selStart != 0 && selStart < len && text[selStart - 1] == ' ' && text[selStart + 1] == ' ') {
// Move the cursor one position to the right to position it at the start of the next hexadecimal digit
setSelection(selStart + 1)
return
}
// Check if the cursor is positioned at a space and the character after the space is not a space
if (selStart < len && text[selStart] == ' ' && (selStart + 1 >= len || text[selStart + 1] != ' ')) {
// Move the cursor one position to the right to position it at the start of the next hexadecimal digit
setSelection(selStart + 1)
}
}
}
// Call the superclass implementation of onSelectionChanged
super.onSelectionChanged(selStart, selEnd)
}
}
I've also toyed with using drawables for the spaces, I even thought that maybe if I make a custom drawable similar to a tictactoe board if you will, and have the digits in between the drawable to achieve the same result. I know either way I still have to handle backspaces and the arrow key movement in the even the user is using a keyboard, but that's a 3 day debug session for another time. If anyone has any ideas or see anything I missed that would be awesome, Or if you think this approach is not the best. I tired many different ways to approach this but this got the closest result to working. I do feel as though a drawable may be much more resource intensive than a text watcher, albeit this would be as well with larger files, but that can all be solved down the road. This is allot, and I don't expect a magical fix. But more eyes on a project might be able to spot what I've missed, thank you for your time =)
EDIT-----------
So it seems this is a buggy way to go about this type of use case, and very unreliable. Ive started working on instead overriding the onDraw method in EditText to simply draw the text in the positions. Hoping this isn't too resource intensive as I haven't ran the code on hardware yet to see but at any rate I'm assuming it will stay in place when edited and be pretty resource efficient as compared to other methods I've tried(Some even an S22 ultra had a hard time with). Then it's simply implementing overwrite mode. Which i have already done. Hopefully this pans out. If anyone has a better idea or if the above code can be made more reliable I would still love to see it! For now my efforts are going towards onDraw.
I have this code that keeps giving me a "Val cannot be reassigned" error but I can't seem to change the variable to a var instead of val. I simply want to be able to set a string value to my cell reference so I can access the values later like this myStringsArrayList.add(deviceData.cellOne).
Here is my code:
val cells = listOf(
deviceData.cellOne,
deviceData.cellTwo,
deviceData.cellThree,
deviceData.cellFour,
deviceData.cellFive,
deviceData.cellSix,
deviceData.cellSeven,
deviceData.cellEight,
deviceData.cellNine,
deviceData.cellTen,
deviceData.cellEleven,
deviceData.cellTwelve,
deviceData.cellThirteen,
deviceData.cellFourteen
)
for ((i, cell) in cells.withIndex()) {
val value = data[2 + i].toDouble() / 100 + 3.52
val cellNumberString = (i + 1).toString()
val formattedString = "Cell $cellNumberString: %.2fV".format(value)
cell = formattedString // THIS IS WHERE THE PROBLEM IS (cell is a val)
}
Does anyone know how I can get around this and achieve the functionality that I want?
I tried using a listIterator() but it hasn't seemed to work the way that I want it to.
Here is my attempt with the listIterator():
val cells = mutableListOf(
deviceData.cellOne,
deviceData.cellTwo,
deviceData.cellThree,
deviceData.cellFour,
deviceData.cellFive,
deviceData.cellSix,
deviceData.cellSeven,
deviceData.cellEight,
deviceData.cellNine,
deviceData.cellTen,
deviceData.cellEleven,
deviceData.cellTwelve,
deviceData.cellThirteen,
deviceData.cellFourteen)
val iterate = cells.listIterator()
while (iterate.hasNext()) {
var cell = iterate.next()
val value = data[2 + iterate.nextIndex()].toDouble() / 100 + 3.52
val cellNumberString = (iterate.nextIndex() + 1).toString()
val formattedString = "Cell $cellNumberString: %.2fV".format(value)
cell = formattedString
}
You can't make that a var, and there's no reason to anyway!
for ((i, cell) in cells.withIndex()) {
...
cell = formattedString // THIS IS WHERE THE PROBLEM IS (cell is a val)
}
You're creating a for loop there on a collection, with an index, and giving it a block of code to run for each loop. So when the loop runs, you're provided with two parameters - the current item from the collection, and its index in that collection.
These are just internal variables for use in the loop - you can't reassign them, because they're the values being passed in. Even if you could, you'd just be changing the values of those local variables.
What you're probably trying to do is update the cells list, taking the current item in the loop, finding it in cells, and replacing it. You'd have to actually update cells to do that! Change the list itself. You could do that with cells[i] = formattedString - but because you're currently iterating over that cells collection, you shouldn't modify it!
You could copy the source list, but the typical Kotlin way is to create a new list, using map (which transforms values):
cells.mapIndexed { i, cell ->
val value = data[2 + i].toDouble() / 100 + 3.52
val cellNumberString = (i + 1).toString()
// last expression is the return value, i.e. the formatted string
"Cell $cellNumberString: %.2fV".format(value)
}
That will spit out a new (immutable) list where each cell has been mapped to that formatted string version.
You could make cells a var and just reassign it:
cells = cells.mapIndexed { ... }
or just chain the map call when you initialise the val, so that end result is what gets assigned:
val cells = listOf(
...
).mapIndexed { ... }
But you're not actually using cell in that loop anyway, just using the index to generate values. You can create a list like this:
val data = List(14) { i ->
val value = data[2 + i].toDouble() / 100 + 3.52
// you can calculate the index inside the string by using braces
"Cell ${i + 1}: %.2fV".format(value)
}
It all depends whether you need to keep that list of devicedata values around for anything (if so use it to create another list)
You may wanna use Interator this way:
val list = mutableListOf("One", "Two", "Three", "Four")
println(list.joinToString(" "))
val iterator = list.listIterator()
while(iterator.hasNext()) {
val value = iterator.next()
if (value == "Two") {
iterator.set("xxxxxx")
}
}
println(list.joinToString(" "))
Use the 'set' method.
I need your knowledge
I have to create book reader applications like the amazon kindle but, using simple text formate not any file formate like pdf, epub or any. Load simple text in textview.
I have one idea, assume we have 1000 lines in one book. Load this text in textview with android:layout_height="match_parent". when text crosses the limit of texview hight, it auto load remaining text in the next textview but, how to know the text crosses the limit (eclipsing) of textview.
To implement this I have written this code but l.getEllipsisCount(lines - 1) return 0.
lLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
// your code here. `this` should work
val l = tvHtml.getLayout();
if (l != null) {
var lines = l.getLineCount()
if (lines > 0) {
if (l.getEllipsisCount(lines - 1) > 0) {//2147483647
Log.d("log ---------", "Text is ellipsized")
}
}
}
}
})
This is my logic but, if you have any new ideas to perform this task, pls inform me.
Thanks
I have a "for-loop" in Kotlin which is going to run my code 6 times.
I also have a textView on the app and want to see these 6 results shown there.
I can easily println() the results.
However, If I set the text of textView to these results, it only gets the last result.
What I like to do printing out all 5 results in textView (suggestedNums ) as each result is a separate line.
Is it even possible?
Any help appreciated.
Thanks.
for (i in 1..6) {
val s: MutableSet<Int> = mutableSetOf()
//create 5 numbers from numbers
while (s.size < 5) {
val rnd = (numbers).random()
s.add(rnd)
}
// remove all 5 random numbers from numbers list.
numbers.removeAll(s)
// sort 5 random numbers and println
println(s.sorted())
// set suggestedNums text to "s"
suggestedNums.text = s.sorted().toString()
}
You can do it in 2 ways
replace
suggestedNums.text = s.sorted().toString()
with
suggestedNums.text = suggestedNums.text.toString() + "\n" + s.sorted().toString()
Create a string and append the results with "\n" and set the text outside the for loop
So I'm trying to figure out how I would get the text from a dynamically created EditText field.
This is the code for the dynamic Text fields
private fun AddToDoItem() {
val EditText = EditText(this)
EditText.gravity = Gravity.TOP
EditText.gravity = Gravity.CENTER
EditText.tag = "ExtraField" + i
LinearLayout?.addView(EditText)
i++
}
And this is the code where I want to get the Textfields text
Finish.setOnClickListener {
var x = 0
val userId = mAuth.currentUser!!.uid
val mcDatabase = FirebaseDatabase.getInstance().getReference()
mcDatabase.child("Users").child(userId).child(ToDoName.text.toString()).push()
while (x < i) {
val currentUserDb = mDatabaseReference!!.child(userId).child(ToDoName.text.toString())
currentUserDb.child(i.toString()).setValue("ExtraField" + x.text) //HERE IS WHERE I WANT TO SET THE TEXT
x++
Toast.makeText(this, "Finished.", Toast.LENGTH_LONG).show()
}
}
Where its commented like "//HERE IS WHERE I WANT TO SET THE TEXT" is where I want the .text string.
(It's in the while loop)
There are two ways to handle this:
You are adding the EditText's to a LinearLayout therefore you can iterate over its children - as described here and therefore replacing the while loop.
The second way would be to add the EditText's to a List<EditText> and iterate over it, replacing the while loop.
The first solution would probably be cleaner, but both of them work in a very similar fashion.
Hope this helps you!