Dynamic leading text for android's EditText

北战南征 提交于 2020-04-17 22:06:08

问题


I've looked into using a TextWatcher and an InputFilter, but I'm not too sure how to approach this problem.

The idea is to have an EditText that inserts text from right to left. As the use input changes, I would like the following to occur.

- User enters "1" -> Text formats as 00:01
- User enters "2" -> Text formats as 00:12
- User enters "8" -> Text formats as 01:28

How could I approach this? Inputfilter seems to be for excluding text and using setText inside the TextWatcher appears to run in an endless loop.


回答1:


Try this:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <EditText
        android:id="@+id/edittext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25dp"
        android:padding="20dp"
        android:inputType="number"
        android:maxLength="4"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textview"
        android:textSize="25dp"
        android:padding="20dp"/>

</LinearLayout>

MainActivity.kt

import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        edittext.addTextChangedListener(object : 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) {
                if (s.length<5){
                    var a=""
                    var i=4-s.length
                    var j=0
                    while (j<i){
                        a+="0"
                        j++
                    }
                    var b=a+s
                    b = b.substring(0, 2) + ":" + b.substring(2, b.length)
                textview.setText(b)}
            }
        })
    }
}



回答2:


So I came up with a pretty decent solution yesterday.

I used two edit texts and a text view. I Aligned them within a constraint layout

        <TextView
            android:id="@+id/colon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text=":"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />


        <EditText
            android:id="@+id/hoursInput"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="end"
            android:inputType="number"
            android:maxLength="3"
            android:text="00"
            app:layout_constraintBottom_toBottomOf="@id/colon"
            app:layout_constraintEnd_toStartOf="@id/colon"
            app:layout_constraintTop_toTopOf="@id/colon"
            app:layout_constraintVertical_bias="0.0" />

        <EditText
            android:id="@+id/minutesInput"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="end"
            android:inputType="number"
            android:maxLength="3"
            android:text="00"
            app:layout_constraintBottom_toBottomOf="@id/colon"
            app:layout_constraintStart_toEndOf="@id/colon"
            app:layout_constraintTop_toTopOf="@id/colon" />

Since I only wanted to use numbers so I had to use a text view to represent the colon. modified the Text watcher for each input. I leveraged the string size in determining when to update the text entirely, and used onTextChanged to force the string to always be at minimum size 2 - Both the hours and minutes look exactly the same, with the exception of the enum/cursor position. The fields themselves would allow for a third digit to be enterd at the cursor position. So a fiel with "12" would turn into "123" and then trigger update time, which figures out if we need to set text else where, or reject the number, or whatever custom logic is required.

addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {  
                if (s != null && s.toString().length > 2)
                    //I used an ENUM to tell my update time function which view I was in. More to come.  Cusor position is set in the keylistener
                    updateTime(s.toString, hoursInput.text, cursorPosition, Field.MINUTES) 
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                if (s != null && s.length < 2)
                    StringBuilder(s).apply { this.insert(0, "0") }.toString().also {
                        minutesInput.setText(it)
                    }
            }
        })

I also added an onKeyListener to watch the delete key specifically to implement custom logic. But before it would return the result of the key pressed, i'd track the cursor position on keydown for every event. Both minutes and Hours inputs look the same/similar.

 setOnKeyListener { _, keyCode, keyEvent ->
            if (keyEvent.action == KeyEvent.ACTION_DOWN) cursorPosition = selectionStart
            (keyCode == KeyEvent.KEYCODE_DEL && keyEvent.action == KeyEvent.ACTION_DOWN && selectionStart == selectionEnd && selectionStart != 0).also { performDelete ->
                if (performDelete) Logic.deleteTime(minutesInputString, minutesInput.text, cursorPosition, Field.Minutes)
            }
        }

Finally, I have my Logic class which I actually performed the string manipulation.

// Inside update/delete I convert the strings into a string that is like 00129 and use the cursorPosition/Field enum to determine where to add text.
Logic.updateTime(minutesInputString, hoursInputString, cursorPosition, Field) : String[]
Logic.deleteTime(minutesInputString, hoursInputString, cursorPosition, Field): String[]

I then set the time immediately with the inputs.

updateTime(mins, hours, position, Field.MINUTES).also{ 
    // ("00", "129", 2, Minutes) returns as ["01", "29"] 
    minutesInput.setText(it[0])
    minutesInput.setText(it[1])
}

Complicated problem that is still being worked on, but this is how I've solved it. Implementing this solution took me 4 hours where I had spun my wheels for nearly three days!



来源:https://stackoverflow.com/questions/60839902/dynamic-leading-text-for-androids-edittext

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!