How to fill edittext with placeholders until it is not filled - android

I faced the following problem: I need to implement the solution for the case when the phone should be entered to the EditText. This phone should have non-removable part and last four numbers should be filled with underscore at the beginning and then when user typing them underscores should be changed to numbers, like:
+12345____ -> typing 6 -> +123456___
I implemented the non-removable part. Here's the way how I did it:
binding.etPhoneNumber.filters = arrayOf(InputFilter.LengthFilter(args.phoneNumber?.length ?: 0))
binding.etPhoneNumber.doAfterTextChanged {
val symbolsLeft = it?.toString()?.length ?: 0
if (symbolsLeft < phoneNumberUi.length) {
binding.etPhoneNumber.setText(phoneNumberUi)
binding.etPhoneNumber.setSelection(symbolsLeft + 1)
}
}
But now I do not understand, how to handle the logic with underscores. I tried to append the underscores in doAfterTextChanged, like if ((args.phoneNumber?.length ?: 0) > (it?.toString()?.length ?: 0)) append n underscores, where n is the difference between length, but in this case I cannot add new symbols as EditText is filled and underscores are not removed. So, how can I solve the problem? Thanks in advance for any help!

You can remove the LengthFilter and check the length in doAfterTextChanged :
val phoneNumberUi = "+12345"
val length = 10
binding.etPhoneNumber.doAfterTextChanged {
when {
it == null -> {
}
// missing / incomplete prefix
it.length < phoneNumberUi.length -> {
it.replace(0, it.length, phoneNumberUi)
}
// prefix was edited
!it.startsWith(phoneNumberUi) -> {
it.replace(0, phoneNumberUi.length, phoneNumberUi)
}
// too short
it.length < length -> {
it.append("_".repeat(length - it.length))
}
// too long
it.length > length -> {
it.replace(length, it.length, "")
}
// set the cursor at the first _
it.indexOf("_") >= 0 -> {
binding.etPhoneNumber.setSelection(it.indexOf("_"))
}
}
}
Note : This uses a when because every change triggers immediately a recursive call to doAfterTextChanged

This approach has the following conditional branches
The place where the user adds their input (non-removable part or the changeable part)
The entered char (number or backspace)
And works by getting the entered char (number/backspace) within the onTextChanged() and its index (second parameter), and set the new EditText value upon the values of both of them.
Also the value of the EditText is tracked by currentText variable. So that we can only replace one character at a time which is the input done by the user to avoid the burden of manipulating the entire text.
You can find the rest of the explanation by the below comments through the code:
attachTextWatcher(findViewById(R.id.edittext))
fun attachTextWatcher(editText: EditText) {
// set the cursor to the first underscore
editText.setSelection(editText.text.indexOf("_"))
var currentText = editText.text.toString() // which is "+12345____"
val watcher: TextWatcher = object : TextWatcher {
override fun onTextChanged(
s: CharSequence,
newCharIndex: Int, // "newCharIndex" is the index of the new entered char
before: Int,
count: Int
) {
// New entered char by the user that triggers the TextWatcher callbacks
val newChar = s.subSequence(newCharIndex, newCharIndex + count).toString().trim()
/* Stop the listener in order to programmatically
change the EditText Without triggering the TextWatcher*/
editText.removeTextChangedListener(this)
// Setting the new text of the EditText upon examining the user input
currentText =
if (newChar.isEmpty()) { // User entered backspace to delete a char
if (newCharIndex in 0..5) { // The backspace is pressed in the non-removable part
"+12345" + currentText.substring(6)
} else { // The backspace is pressed in the changeable part
val sb = StringBuilder(currentText)
// replace the the number at which backspace pressed with underscore
sb.setCharAt(newCharIndex, '_')
sb.toString()
}
} else { // User entered a number
if (newCharIndex in 0..5) { // The number is entered in the non-removable part
// replace the first underscore with the entered number
val sb = StringBuilder(currentText)
sb.setCharAt(sb.indexOf("_"), newChar[0])
sb.toString()
} else { // The number is entered in the changeable part
if (newCharIndex < 10) { // Avoid ArrayOutOfBoundsException as the number length should not exceed 10
val sb = StringBuilder(currentText)
// replace the the number at which the number is entered with the new number
sb.setCharAt(newCharIndex, newChar[0])
sb.toString()
} else currentText
}
}
// Set the adjusted text to the EditText
editText.setText(currentText)
// Set the current cursor place
if (editText.text.contains("_"))
editText.setSelection(editText.text.indexOf("_"))
else
editText.setSelection(editText.text.length)
// Re-add the listener, so that the EditText can intercept the number by the user
editText.addTextChangedListener(this)
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun afterTextChanged(s: Editable) {
}
}
editText.addTextChangedListener(watcher)
}
This is the layout I'm testing with:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="#+id/edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="11"
android:text="+12345____"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Note: make sure to survive the value of the currentText on configuration change.
preview

I think that PhúcNguyễn had a good idea to marry a TextView with an EditText to produce what you are looking for. You could place these as separate fields in a layout or place them in a composite view. Either way the effect will be the same and you could achieve what you want.
You have already figured out how to handle the static text at the beginning of the field. What I am presenting below is how to handle the underscores so characters that are entered appear to overwrite the underscores.
For the demo, I have place a TextView with the static text beside a custom EditText. It is the custom EditText that is really of any interest. With the custom view, the onDraw() function is overridden to write the underscores as part of the background. Although these underscores will appear like any other character in the field, they cannot be selected, deleted, skipped over or manipulated in any way except, as the user types, the underscores are overwritten one-by-one. The end padding of the custom view is manipulated to provide room for the underscores and text.
Here is the custom view:
EditTextFillInBlanks.kt
class EditTextFillInBlanks #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatEditText(context, attrs, defStyleAttr) {
// Right padding before we manipulate it
private var mBaseRightPadding = 0
// Width of text that has been entered
private var mTextWidth = 0f
// Mad length of data that can be entered in characters
private var mMaxLength = 0
// The blanks (underscores) that we will show
private lateinit var mBlanks: String
// MeasureSpec for measuring width of entered characters.
private val mUnspecifiedWidthHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
init {
mBaseRightPadding = paddingRight
doOnTextChanged { text, _, _, _ ->
measure(mUnspecifiedWidthHeight, mUnspecifiedWidthHeight)
mTextWidth = measuredWidth.toFloat() - paddingStart - paddingEnd
updatePaddingForBlanks(text)
}
setText("", BufferType.EDITABLE)
}
/*
Make sure that the end padding is sufficient to hold the blanks that we are showing.
The blanks (underscores) are written into the expanded padding.
*/
private fun updatePaddingForBlanks(text: CharSequence?) {
if (mMaxLength <= 0) {
mMaxLength = determineMaxLen()
check(mMaxLength > 0) { "Maximum length must be > 0" }
}
text?.apply {
val blanksCount = max(0, mMaxLength - length)
mBlanks = "_".repeat(blanksCount).apply {
updatePadding(right = mBaseRightPadding + paint.measureText(this).toInt())
}
}
}
/*
Draw the underscores on the canvas. They will appear as characters in the field but
cannot be manipulated by the user.
*/
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (mBlanks.isNotEmpty()) {
canvas?.withSave {
drawText(mBlanks, paddingStart + mTextWidth, baseline.toFloat(), paint)
}
}
}
fun setMaxLen(maxLen: Int) {
mMaxLength = maxLen
}
private fun determineMaxLen(): Int {
// Before Lollipop, we can't get max for InputFilter.LengthFilter
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return 0
return filters.firstOrNull { it is InputFilter.LengthFilter }
?.let {
it as InputFilter.LengthFilter
it.max
} ?: 0
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_blue_light"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/white"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:text="+12345"
android:textColor="#android:color/black"
android:textSize="36sp"
app:layout_constraintBaseline_toBaselineOf="#id/editableSuffix"
app:layout_constraintEnd_toStartOf="#+id/editableSuffix"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="#+id/guideline2" />
<com.example.edittextwithblanks.EditTextFillInBlanks
android:id="#+id/editableSuffix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/edittext_background"
android:inputType="number"
android:maxLength="#integer/blankFillLen"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:textColor="#android:color/black"
android:textSize="36sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/textView"
app:layout_constraintTop_toTopOf="parent"
tools:text="____">
<requestFocus />
</com.example.edittextwithblanks.EditTextFillInBlanks>
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="92dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val mStaticStart = "+12345"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (BuildConfig.VERSION_CODE < Build.VERSION_CODES.P) {
val maxLen = resources.getInteger(R.integer.blankFillLen)
findViewById<EditTextFillInBlanks>(R.id.editableSuffix).setMaxLen(maxLen)
}
}
}
It is likely that you can incorporate your static text handling into the custom view for a complete solution.

Related

How to fit the view to the size of the object? (Kotlin)

I'm building my first game in Android Studio. Right now, dots fall from the top of the screen down to the bottom. For some reason, in Layout Inspector the view of each dot is the entire screen even though the dots are comparatively small. This negatively affects the game since when a user presses anywhere on the screen, it deletes the most recently created dot rather than the one pressed. I want to get the dot's view to match the size of the actual dots without effecting other functionality.
Dot.kt
class Dot(context: Context, attrs: AttributeSet?, private var dotColor: Int, private var xPos: Int, private var yPos: Int) : View(context, attrs) {
private var isMatching: Boolean = false
private var dotIsPressed: Boolean = false
private var isDestroyed: Boolean = false
private lateinit var mHandler: Handler
private lateinit var runnable: Runnable
init {
this.isPressed = false
this.isDestroyed = false
mHandler = Handler()
runnable = object : Runnable {
override fun run() {
moveDown()
invalidate()
mHandler.postDelayed(this, 20)
}
}
val random = Random()
xPos = random.nextInt(context.resources.displayMetrics.widthPixels)
startFalling()
startDrawing()
}
// other methods
fun getDotColor() = dotColor
fun getXPos() = xPos
fun getYPos() = yPos
fun isMatching() = isMatching
fun setMatching(matching: Boolean) {
this.isMatching = matching
}
fun dotIsPressed() = dotIsPressed
override fun setPressed(pressed: Boolean) {
this.dotIsPressed = pressed
}
fun isDestroyed() = isDestroyed
fun setDestroyed(destroyed: Boolean) {
this.isDestroyed = destroyed
}
fun moveDown() {
// code to move the dot down the screen
yPos += 10
}
fun checkCollision(line: Line) {
// check if dot is colliding with line
// if yes, check if dot is matching or not
// update the dot state accordingly
}
fun startFalling() {
mHandler.post(runnable)
}
fun startDrawing() {
mHandler.postDelayed(object : Runnable {
override fun run() {
invalidate()
mHandler.postDelayed(this, 500)
}
}, 500)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (!isDestroyed) {
val paint = Paint().apply {
color = dotColor
}
canvas?.drawCircle(xPos.toFloat(), yPos.toFloat(), 30f, paint)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var score = 0
private lateinit var scoreCounter: TextView
private val dots = mutableListOf<Dot>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
createLine(Color.RED, 5000)
scoreCounter = TextView(this)
scoreCounter.text = score.toString()
scoreCounter.setTextColor(Color.WHITE)
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.setBackgroundColor(Color.BLACK)
val params = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
scoreCounter.layoutParams = params
layout.addView(scoreCounter)
val dotColors = intArrayOf(Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW)
val random = Random()
val handler = Handler()
val runnable = object : Runnable {
override fun run() {
val dotColor = dotColors[random.nextInt(dotColors.size)]
createAndAddDot(0, 0, dotColor)
handler.postDelayed(this, 500)
}
}
handler.post(runnable)
}
fun updateScore(increment: Int) {
score += increment
scoreCounter.text = score.toString()
}
fun createAndAddDot(x: Int, y: Int, color: Int) {
Log.d("Dot", "createAndAddDot called")
val dot = Dot(this, null, color, x, y)
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.addView(dot)
dots.add(dot)
dot.setOnTouchListener { view, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
val dotToRemove = dots.find { it == view }
dotToRemove?.let {
layout.removeView(it)
dots.remove(it)
updateScore(1)
view.performClick()
}
}
true
}
}
fun createLine(color: Int, interval: Int) {
Log.d("Line", "createLine called")
val line = Line(color, interval)
val lineView = Line.LineView(this, null, line)
val layout = findViewById<ConstraintLayout>(R.id.layout)
if (layout == null) {
throw IllegalStateException("Layout not found")
}
layout.addView(lineView)
val params = ConstraintLayout.LayoutParams(2000, 350)
lineView.layoutParams = params
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID
params.bottomMargin = (0.1 * layout.height).toInt()
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Your view here -->
<View
android:id="#+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- Guideline set to 10% from the bottom -->
<androidx.constraintlayout.widget.Guideline
android:id="#+id/bottom_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.1" />
</androidx.constraintlayout.widget.ConstraintLayout>
I tried changing the view size with
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val diameter = 40 // or any other desired diameter for the dots setMeasuredDimension(diameter, diameter) }
That made the view size a square stuck in the top left corner. As I played around with it, I could only get dots to show in that small window in the top corner rather than moving down the screen from different starting x-positions
Your custom view isn't a dot, it's a large display area that draws a dot somewhere inside it and animates its position. In onDraw you're drawing a circle at xPos (a random point on the screen width via displayMetrics.widthPixels) and yPos (an increasing value which moves the dot down the view).
There are two typical approaches to things like this:
use simple views like ImageViews. Let the containing Activity or Fragment add them to a container and control their position, maybe using the View Animation system. Handle player interaction by giving them click listeners and let the view system work out what's been clicked.
create a custom view that acts as the game area. Let that custom view control the game state (what dots exist, where they currently are) and draw that state in onDraw. Handle touch events on the view, and work out if those touches coincide with a dot (by comparing to the current game state).
What you're doing is sort of a combination of the two with none of the advantages that either approach gives on its own. You have multiple equally-sized "game field" views stacked on top of each other, so any clicks will be consumed by the top one, because you're clicking the entire view itself. And because your custom view fills the whole area, you can't move it around with basic view properties to control where the dot is - you have to write the logic to draw the view and animate its contents.
You could implement some code that handles the clicks and decides whether the view consumes it (because it intersects a dot) or passes it on to the next view in the stack, but that's a lot of work and you still have all your logic split between the Activity/Fragment and the custom view itself.
I think it would be way easier to just pick one approach - either use ImageViews sized to the dot you want and let the view system handle the interaction, or make a view that runs the game internally. Personally I'd go with the latter (you'll find it a lot easier to handle dots going out of bounds, get better performance, more control over the look and interaction etc, no need to cancel Runnables) but it's up to you!

how to handle new line in word count?

i'm referring this answer https://stackoverflow.com/a/42802060/12428090
basically my goal is to handle word count of 100 words
the abve solution works fine for typed 100 words and even handles copy paste very well
but it doesnt handles the new line case
suppose in entered word or copy pasted word contains new line then the word count is returning incorrect
following is the code please help out
override fun onTextChanged(s: CharSequence, start: Int, before: Int,
count: Int) {
val wordsLength: Int = countWords(s.toString()) // words.length;
limitmoderator.text = "$wordsLength/$MAX_WORDS"
val yourText: String =
moderator_intro.getText().toString().replace(160.toChar().toString(), " ")
if (yourText.split("\\s+".toRegex()).size > MAX_WORDS) {
var space = 0
var length = 0
for (i in 0 until yourText.length) {
if (yourText[i] == ' ') {
space++
if (space >= MAX_WORDS) {
length = i
break
}
}
}
if (length > 1) {
moderator_intro.getText()
.delete(length, yourText.length) // deleting last exceeded words
setCharLimit(moderator_intro, length - 1) //limit edit text
}
} else {
removeFilter(moderator_intro)
}}
private fun countWords(s: String): Int {
val trim = s.trim()
return if (trim.isEmpty()) 0 else trim.split("\\s+".toRegex()).size
// separate string around spaces
}
private var filter: InputFilter? = null
private fun setCharLimit(et: EditText, max: Int) {
filter = LengthFilter(max)
et.filters = arrayOf<InputFilter>(filter as LengthFilter)
}
private fun removeFilter(et: EditText) {
if (filter != null) {
et.filters = arrayOfNulls(0)
filter = null
}
}
so i have tried rplacing the "\n" in the text but it doesnt seems to be handling the case properly
any help will be appreciated
thanks in advance
Here's a different strategy than the one from the question you linked. Notice I'm using afterTextChanged and not onTextChanged!
I'm manually counting words to get the character index of the first whitespace after the last allowable word. That way I don't have to trim and then use Regex, and then try to figure out the index offset of that Regex. Then instead of applying a temporary filter, I directly cut the end of the Editable off.
editText.setSelection is to keep the cursor from jumping to the beginning.
override fun afterTextChanged(s: Editable) {
var previousWasWhitespace = true
var i = 0
var wordCount = 0
for (c in s) {
val whitespace = c.isWhitespace()
if (whitespace && !previousWasWhitespace && ++wordCount == MAX_WORDS) {
break
}
previousWasWhitespace = whitespace
i++
}
if (i < s.length) {
s.delete(i, s.length)
editText.setSelection(i)
}
}
You could write a regular expression to match the text that you want to keep and remove the rest. In this case you want match (non-whitespace+)(whitespace*) maximum 100 times.
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
val input = moderator_intro.getText().toString()
val maxWords = 100
val whiteSpace = "\\p{javaWhitespace}\u00A0\u2007\u202F" //All white space characters
val keepRegex = Regex("[$whiteSpace]*([^$whiteSpace]+[$whiteSpace]*){1,${maxWords.toString()}}")
val allowedText = keepRegex.find(input)?.value ?: ""
val wordAmount = allowedText.split(Regex("[$whiteSpace]+")).filter { it.isNotBlank() }.size
val trailingWhiteSpace = Regex("[$whiteSpace]+$")
if(wordAmount == maxWords && allowedText.contains(trailingWhiteSpace)) {
val newText = allowedText.replace(trailingWhiteSpace, "")
moderator_intro.getText().delete(allowedText.length, input.length)
setCharLimit(moderator_intro, newText.length)
} else {
removeFilter(moderator_intro)
}
}

Cursor position in EditText with TextWatcher and LiveData?

I'm creating a form on an android application using kotlin. Each field of the form is associated with a livedata, to validate or update the data instantly. To obtain this result, I used a textwatcher, which updates the associated livedata at each change of the text. The problem is that with each update, it places the cursor at the start of the field making it impossible to write continuously.
I paste here the intresting part of the code:
Activity
viewModel.name.observe(lifecycleOwner, Observer { nameValue->
binding.nameEditText.setText(
nameValue?.toString()
)
binding.nameTextInputLayout.error =
viewModel.nameError.value
})
viewModel.price.observe(lifecycleOwner, Observer { priceValue->
binding.priceEditText.setText(
price?.toString()
)
binding.priceTextInputLayout.error =
viewModel.priceError.value
})
binding.nameEditText.addTextChangedListener(
TextFieldValidation(
binding.nameEditText
)
)
binding.priceEditText.addTextChangedListener(
TextFieldValidation(
binding.priceEditText
)
)
inner class TextFieldValidation(private val view: View) : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
when (view.id) {
R.id.nameEditText-> {
viewModel.onNameChanged(s.toString())
viewModel.isNameValid()
}
R.id.priceEditText-> {
viewModel.onPriceChanged(s.toString())
viewModel.isPriceValid()
}
}
}
ViewModel
var name = MutableLiveData<String>()
var price = MutableLiveData<Double>()
var nameError = MutableLiveData<String>()
var priceError = MutableLiveData<String>()
fun onNameChanged(newValue: String?) {
if ((name.value != newValue)) {
name.value = newValue
}
}
fun onPriceChanged(newValue: Double?) {
if ((price.value != newValue)) {
price.value = newValue
}
}
fun isNameValid() : Boolean {
return if ((name.value == null) || (name.value == "")) {
nameError.value = "Error"
false
} else {
nameError.value = ""
true
}
}
fun isPriceValid() : Boolean {
return if ((price.value == null) || (price.value == "")) {
priceError.value = "Error"
false
} else {
priceError.value = ""
true
}
}
XML
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/nameTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/nameEditText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:backgroundTint="#color/white"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/priceTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/priceEditText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:backgroundTint="#color/white"
android:inputType="numberDecimal" />
</com.google.android.material.textfield.TextInputLayout>
I tried using 'mEdittext.setSelection (mEdittext.length ());' but it doesn't work well, because if I make changes in the middle of the string, it brings the cursor to the end. And even in double fields, it doesn't behave correctly.
I need to have the cursor always at the exact position? Either in case of adding, deleting or writing in the middle of the string. And both in the case of a string and in the case of a double.
Could someone help me?
Thank you for your patience and help!
This is happening, because in your ViewModel observer you are setting nameValue and priceValue back to the EditText where they just came from. You could add a check and not set unchanged values back to the EditText:
if ((nameValue?.toString() == binding.nameEditText.getText().toString()) == false) {
binding.nameEditText.setText(nameValue?.toString())
}
I also had the same problem that the cursor moved to the beginning, searching I found a first solution that served me to a certain extent allowing me to write a message in a row, which would be the following
viewModel.name.observe(lifecycleOwner, Observer { nameValue->
binding.nameEditText.setText(
nameValue?.toString()
)
binding.nameEditText.setSelection(
nameValue?.toString().length
)
})
I thought I had already solved it, but another problem arose and that is that if I wanted to edit any part of the center after typing a character the cursor would move to the end, until I found the correct solution https://stackoverflow.com/a/65794745/15798571
viewModel.name.observe(lifecycleOwner, Observer { nameValue->
val selection = binding.nameEditText.selectionEnd
val length = nameValue.length
binding.nameEditText.setText(
nameValue?.toString()
)
binding.nameEditText.setSelection(
Math.min(selection, length)
)
})
I hope it serves you as it served me

Implement autoSizeTextType in android Edittext

Android Oreo buildToolsVersion provides a simplified way to autoresize textsize in AppCompatTextView as follows
<android.support.v7.widget.AppCompatTextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:maxWidth="300dp"
android:background="#android:color/holo_green_light"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="5sp"
app:autoSizeMaxTextSize="50sp"
app:autoSizeStepGranularity="4sp"
/>
Can a similar implementation be applied to AppCompatEditText since it is basically an extension of TextView? Simply applying autoSizeTextType to AppCompatEditText doesn't seem to work. Is there a way to make this work out?
No you cannot. Please see here; it is disabled for all AppCompatEditText since it is not supported.
I had an specific case which was a single line EditText, so I'm posting my solution just in case to be helpful to someone...
private val initialTextSize: Float by lazy {
resources.getDimensionPixelSize(R.dimen.default_text_size).toFloat()
}
private fun updateTextSize(s: CharSequence) {
// Auto resizing text
val editWidth = myEditText.width
if (editWidth > 0) { // when the screen is opened, the width is zero
var textWidth = myEditText.paint.measureText(s, 0, s.length)
if (textWidth > editWidth) {
var fontSize = initialTextSize
while (textWidth > editWidth && fontSize > 12) { // minFontSize=12
fontSize -= 1
myEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
textWidth = myEditText.paint.measureText(s, 0, s.length)
}
// As long the text grows, the font size decreases,
// so here I'm increasing it to set the correct text s
} else {
var fontSize = myEditText.textSize
while (textWidth <= editWidth && fontSize < initialTextSize) {
fontSize = min(fontSize + 1, initialTextSize)
myEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
textWidth = myEditText.paint.measureText(s, 0, s.length)
}
}
}
}
Then you must call this function from a TextWatcher attached to the EditText.
private fun createTextWatcher() = object : SimpleTextWatcher() {
override fun beforeTextChanged(s: CharSequence?,
start: Int, count: Int, after: Int
) {
super.beforeTextChanged(s, start, count, after)
s?.let { updateTextSize(it) }
}
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int
) {
// Do nothing
}
override fun afterTextChanged(s: Editable?) {
super.afterTextChanged(s)
s?.let { updateTextSize(it) }
}
}
My suggestion would be use: --> https://github.com/AndroidDeveloperLB/AutoFitTextView correction https://github.com/ViksaaSkool/AutoFitEditText
See this thread for more info: --> Auto-fit TextView for Android
General Article on mention library by author: --> https://viksaaskool.wordpress.com/2014/11/16/using-auto-resize-to-fit-edittext-in-android/

Allow only selected charcters based on regex in an EditText

I want to allow users only to type certain characters based on the a regex in my android applications. How do I achieve it?
Used a TextWatcher as #Matt Ball suggested.
#Override
public void afterTextChanged(Editable s) {
String text = s.toString();
int length = text.length();
if(length > 0 && !Pattern.matches(PATTERN, text)) {
s.delete(length - 1, length);
}
}
Edit
Although the TextWatcher works, it would be cleaner to use an InputFilter. Check this example.
You could use android:digits on the xml EditText instead of using a regex.
For your allowed chars of the regex (numbers, comma and plus symbol):
android:digits="0123456789,+"
And you could use a string resource as the digits value in case you want to reuse it.
Try this: If the character to type matches /[a-zA-Z0-9{insert valid characters here}]/ then allow it, otherwise don't.
You can use an InputFilter for advanced filtering:
class CustomInputFilter : InputFilter {
private val regex = Pattern.compile("^[A-Z0-9]*$")
override fun filter(
source: CharSequence,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence? {
val matcher = regex.matcher(source)
return if (matcher.find()) {
null
} else {
""
}
}
}
And then add it to an EditText or TextInputEditText like this:
textInputLayout.editText!!.filters += CustomInputFilter()
//or
editText.filters += CustomInputFilter()
Remember that if you have a TextWatcher this will not prevent the TextWatcher to fire, you can filter out those events checking if the previous and next text values are the same.
Something like:
//addTextChangedListener is an extension function available in core-ktx library
textInputLayout.editText!!.addTextChangedListener(afterTextChanged = { editable ->
val editTextValue = viewModel.editTextLiveData.value ?: ""
if (!editTextValue.equals(editable)) {
viewModel.updateEditTextValue(editable?.toString() ?: "")
}
})

Categories

Resources