Make delete key on custom in-app keyboard delete text from other edit text - android

I've made a custom in-app keyboard (by following this tutorial). But I'd like it to delete text from a different edit text when the delete key is pressed. Is this possible to achieve?
This is my onClick code in my keyboard activity file:
override fun onClick(v: View) {
if (v.id == R.id.button_delete) {
// Delete key is pressed.
} else {
// Regular key is pressed.
val value = keyValues[v.id]
inputConnection?.commitText(value, 1)
}
}
Let me know if there's anything else you'd like to know :)

I eventually solved this by putting a character e.g: 'd' into the edittext like so:
override fun onClick(v: View) {
if (v.id == R.id.button_delete) {
inputConnection?.commitText("d", 1)
} else {
val value = keyValues[v.id]
inputConnection?.commitText(value, 1)
}
}
Then in the activity that controlls the edittext's layout, e.g: for me it was PasscodeActivity, put a TextWatcher that detects when the text inside of the edittext has changed.
private fun setEditTextListener() {
val inputEl = findViewById<EditText>(resources.getIdentifier("inputEl", "id", packageName))
inputEl.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(textBeforeChangedStr: CharSequence?, startNum: Int, countNum: Int, afterNum: Int) {
...
}
override fun onTextChanged(textAfterChangedStr: CharSequence?, startNum: Int, beforeNum: Int, countNum: Int) {
...
}
override fun afterTextChanged(editableEl: Editable?) {
if (inputEl.text.toString() == "d") { // The 'd' character is put into the edittext when the delete key is pressed.
clearOtherEditText()
}
...
}
})
}
Then in the function clearOtherEditText() I put in some code to get rid of the text in a different edittext like so:
val otherInputEl = findViewById<EditText>(resources.getIdentifier("otherInput", "id", packageName))
otherInputEl.text.clear()
Hope this helps someone else :)

try below solution
override fun onClick(v: View) {
if (v.id == R.id.button_delete) {
// Delete key is pressed.
**inputConnection.deleteSurroundingText(1, 0)**
} else {
// Regular key is pressed.
val value = keyValues[v.id]
inputConnection?.commitText(value, 1)
}
}

Related

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

What to return in this recursive method?

I'm creating a method to recursively search for a View inside an ArrayList. It will loop through this ArrayList and, if it contains an ArrayList, it will be searched for Views too, and so on, until finding a View to return. This is so I can make whatever View is inside there invisible.
fun searchForView(arrayList: ArrayList<*>): View {
arrayList.forEach { item ->
if (item is View) {
return item
} else if (item is ArrayList<*>) {
item.forEach {
searchForView(it as ArrayList<*>)
}
}
}
} // Error here, needs to return a View
So I will use it like this:
someArrayList.forEach {
searchForView(someArrayList).visibility = View.INVISIBLE
}
However it is giving me an error because there needs to be a return someView statement near the end of the method. Whenever I call it, the ArrayList being searched will always have a View. So what should I be returning here at the end, knowing that whatever View found will already be returned?
You can set inside function and don't return anything
fun searchForView(arrayList: ArrayList<*>){
arrayList.forEach { item ->
if (item is View) {
item.visibility = View.INVISIBLE // set here
} else if (item is ArrayList<*>) {
item.forEach {
searchForView(it as ArrayList<*>)
}
}
}
}
You should use searchForView(item) instead of item.forEach { searchForView(it as ArrayList<*>) } as #IR42 suggested since you don't know each item in arraylist is an arraylist or not.
Your function is not compileable because it's supposed to return a View, but you aren't returning a View in the else branch or if you reach the end of the input list without finding a View.
However, if all this function does is return a View, then it is not usable for your requirement to set all views' visibility. It would only return a single View.
Instead, you can pass a function argument for what to do to each view it finds. There's no need to return anything.
fun ArrayList<*>.forEachViewDeep(block: (View) -> Unit) {
for (item in this) when (item) {
is View -> block(item)
is ArrayList<*> -> item.forEachViewDeep(block)
}
}
And use it like:
someArrayList.forEachViewDeep {
it.visibility = View.INVISIBLE
}
If it's very deeply nested, you might want to rearrange this function to be tail-recursive like this:
tailrec fun List<*>.forEachViewDeep(block: (View) -> Unit) {
for (item in this) {
if (item is View)
block(item)
}
filterIsInstance<ArrayList<*>>().flatten().forEachViewDeep(block)
}
I was trying to do the same thing like you before
and this is what I've made
class VisibilitySwitcher(private val mutableViewSet: MutableSet<View?>, private val onCondition: Boolean = true){
fun betweenVisibleOrGone(){
if(onCondition)
mutableViewSet.forEach {
when (it?.visibility) {
View.VISIBLE -> {it.visibility = View.GONE}
View.GONE -> {it.visibility = View.VISIBLE}
}
}
}
fun betweenVisibleOrInvisible(){
if(onCondition)
mutableViewSet.forEach {
when (it?.visibility) {
View.VISIBLE -> {it.visibility = View.INVISIBLE}
View.INVISIBLE -> {it.visibility = View.VISIBLE}
}
}
}
fun betweenInVisibleOrGone(){
if(onCondition)
mutableViewSet.forEach {
when (it?.visibility) {
View.INVISIBLE -> {it.visibility = View.GONE}
View.GONE -> {it.visibility = View.INVISIBLE}
}
}
}
}
Usage Example
class LoginActivity : BaseActivity() {
#Inject
#ViewModelInjection
lateinit var viewModel: LoginVM
private lateinit var mutableViewSet: MutableSet<View?>
override fun layoutRes() = R.layout.activity_login
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
facebookBtn.setOnClickListener { handleClickEvent(it) }
googleBtn.setOnClickListener { handleClickEvent(it) }
}
private fun handleClickEvent(view: View) {
when (view) {
facebookBtn -> { viewModel.smartLoginManager.onFacebookLoginClick() }
googleBtn -> { viewModel.smartLoginManager.onGoogleLoginClick() }
}
mutableViewSet = mutableSetOf(facebookBtn, googleBtn, progressBar)
VisibilitySwitcher(mutableViewSet).betweenVisibleOrGone() // <----- Use without Condition
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
VisibilitySwitcher(mutableViewSet, resultCode != -1).betweenVisibleOrGone() //<-- Use with Conditions
viewModel.smartLoginManager.onActivityResultCallBack(requestCode, resultCode, data)
super.onActivityResult(requestCode, resultCode, data)
}
}
The point is whenever you click login from facebook or google button
It will set visibility for facebook and google to be gone and set progressbar(the default of progressbar is View.GONE) to be visible
At override fun onActivityResult()
if the resultcode is not -1 it means that it got some error or cancel
so it will switch back the progressbar to be gone and change facebook and google button to be visible again
If you want to fix your own code I would do this
fun searchForView(mutableViewSet: MutableSet<View?>){
mutableViewSet.forEach {
when (it?.visibility) {
View.VISIBLE -> {it.visibility = View.INVISIBLE}
View.INVISIBLE -> {it.visibility = View.VISIBLE} //<-- you can delete this if you don't want
}
}
}
Or very short form
fun searchForView(mutableViewSet: MutableSet<View?>) = mutableViewSet.forEach { when (it?.visibility) {View.VISIBLE -> it.visibility = View.INVISIBLE } }
Usage
val mutableViewSet = mutableSetOf(your view1,2,3....)
searchForView(mutableViewSet)
if it has to use arrayList: ArrayList<*> Then
fun searchForView(arrayList: ArrayList<*>) = arrayList.forEach{ if (it is View) it.visibility = View.INVISIBLE

EditText replacing character after appending

I'm trying to make a number look like this
932-874838/9
I did this with my EditText to append the - and / after some spaces
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(text: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
onValueChange(s.toString())
}
})
}
private fun onValueChange(value: String) {
mNumberTxtView.text = value
if (value.length == 3) {
mNumberTxtView.append("-")
}
if (value.length == 10) {
mNumberTxtView.append("/")
}
}
When I'm typing like
932
it automatically appends the - , and that works, but after it appends the - and if I type another number it replaces the - with that number instead of continuing, so it becomes 932- at first but when trying to put another number,
9328
it gets replaced like that removing the appended -
I think the problem is inside the onValueChange() method
onValueChange should be like this:
var test: StringBuilder = StringBuilder()
var lastValue: String = ""
fun onValueChange(value: String) {
if(lastValue.length > value.length) {
test.deleteCharAt(test.lastIndex)
if(test.length == 3 || test.length == 10) {
test.deleteCharAt(test.lastIndex)
}
} else {
test.append(value.last())
if (test.length == 3) {
test.append("-")
} else if (test.length == 10) {
test.append("/")
}
}
lastValue = value
textView.text = test
}
Try this, instead.
private fun onValueChange(value: String) {
if (value.length == 3) {
mNumberTxtView.text = "${value}_"
} else if (value.length == 10) {
mNumberTxtView.text = "${value}/"
}
}
Let me know if this works.
(The curly brackets around "value" in the strings may not be necessary. I'm still getting used to Kotlin's way of handling string concatenation.)
Edited to remove redundant and potentially loop-causing part.
You should not change text in beforeTextChanged and afterTextChanged to prevent re-call of those methods by TextWatcher. Make changes in afterTextChanged.
But be careful not to get yourself into an infinite loop, because any changes you make will cause this method to be called again recursively.
So set invoke of onValueChanged into afterTextChanged method
with removal of mNumberTxtView.text = value

How to iterate for variable names in Kotlin?

I'm doing a project which has a lot of buttons. I would like to iterate over those buttons by its name. They are all named 'levelXbutton', where X can be a big number.
I'm now doing it with a lot of lines of code. But I'm sure there's a way to do it in a loop, specially in Kotlin.
For example, this is one of the operations I'd like to do:
if(FacadeData.getLastUnlockedLevel()<2){ binding.lvl2Button.setTextColor(Color.WHITE)}
if(FacadeData.getLastUnlockedLevel()<3){ binding.lvl3Button.setTextColor(Color.WHITE)}
if(FacadeData.getLastUnlockedLevel()<4){ binding.lvl4Button.setTextColor(Color.WHITE)}
if(FacadeData.getLastUnlockedLevel()<5){ binding.lvl5Button.setTextColor(Color.WHITE)}
You can make a loop to go through the buttons numbers and then load their ids with a string like so:
for (i in 0..20) {
val layoutID = context.resources.getIdentifier("lvl${i}Button", "id", context.packageName)
val button = findViewById(layoutID) as Button
...
}
Maybe you should try something like this:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
applyChangeRecursively(findViewById<ViewGroup>(android.R.id.content)) {
it.setBackgroundColor(Color.WHITE)
}
}
fun getResourceName(view:View):String? {
if (view.id > 0 && view is Button) {
return view.resources.getResourceName(view.id)
}
return null
}
fun matchesLabel(resourceName:String?):Boolean = resourceName?.matches(Regex(".*lvl\\d+Button")) ?: false
fun applyChange(v:View, u:(View)->Unit) = v.run(u)
fun applyChangeRecursively(parent:View, fun1:(View)->Unit){
when (parent) {
is ViewGroup -> parent.children.forEach{ applyChangeRecursively(it, fun1) }
else -> if(matchesLabel(getResourceName(parent))) { applyChange(parent, fun1) }
}
}
}

How to disable cursor positioning and text selection in an EditText? (Android)

I'm searching for a way to prevent the user from moving the cursor position anywhere. The cursor should always stay at the end of the current EditText value. In addition to that the user should not be able to select anything in the EditText. Do you have any idea how to realize that in Android using an EditText?
To clarify: the user should be able to insert text, but only at the end.
I had the same problem. This ended up working for me:
public class CustomEditText extends EditText {
#Override
public void onSelectionChanged(int start, int end) {
CharSequence text = getText();
if (text != null) {
if (start != text.length() || end != text.length()) {
setSelection(text.length(), text.length());
return;
}
}
super.onSelectionChanged(start, end);
}
}
Try this:
mEditText.setMovementMethod(null);
This will reset cursor focus to the last position of the text
editText.setSelection(editText.getText().length());
This method will disable cursor move on touch
public class MyEditText extends EditText{
#Override
public boolean onTouchEvent(MotionEvent event)
{
final int eventX = event.getX();
final int eventY = event.getY();
if( (eventX,eventY) is in the middle of your editText)
{
return false;
}
return true;
}
}
And You can use either the xml attribute
android:cursorVisible
or the java function
setCursorVisible(boolean)
to disable blinking cursor of edittext
It sounds like the best way to do this is to make your own CustomEditText class and override/modify any relevant methods. You can see the source code for EditText here.
public class CustomEditText extends EditText {
#Override
public void selectAll() {
// Do nothing
}
/* override other methods, etc. */
}
Yes, I know how to do it =) just do it:
Copy the helper class:
class SelectionSpanWatcher(
private val listener: OnChangeSelectionListener
) : SpanWatcher {
override fun onSpanAdded(text: Spannable?, what: Any?, start: Int, end: Int) {
/* do nothing */
}
override fun onSpanRemoved(text: Spannable?, what: Any?, start: Int, end: Int) {
/* do nothing */
}
override fun onSpanChanged(text: Spannable?, what: Any?, oStart: Int, oEnd: Int, nStart: Int, nEnd: Int) {
when (what) {
SELECTION_START -> listener.onSelectionChanged(Selection.START, oStart, oEnd, nStart, nEnd)
SELECTION_END -> listener.onSelectionChanged(Selection.END, oStart, oEnd, nStart, nEnd)
}
}
enum class Selection {
START,
END
}
fun interface OnChangeSelectionListener {
fun onSelectionChanged(selection: Selection, oStart: Int, oEnd: Int, nStart: Int, nEnd: Int)
}
}
Then copy extension method:
fun Editable.setOnSelectionChangedListener(listener: OnChangeSelectionListener?) {
getSpans(0, length, SelectionSpanWatcher::class.java)
.forEach { span -> removeSpan(span) }
if (listener != null) {
setSpan(SelectionSpanWatcher(listener), 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
}
Then use the copied code as follows (used androidx.core.widget.addTextChangedListener from android-ktx):
editText.addTextChangedListener(afterTextChanged = { editable ->
editable?.setOnSelectionChangedListener { _, _, _, _, _ ->
editText.postOnAnimation {
editText.setSelection(editText.text.length)
}
}
})

Categories

Resources