I have three buttons with ids b00, b01, b02 that I want to all do the same thing when they are long clicked. Is there a better way to do this than
b00.setOnLongClickListener {
//code
true
}
b01.setOnLongClickListener {
//same code
true
}
b02.setOnLongClickListener {
//same code
true
}
You can do this:
/...
b00.setOnLongClickListener(this)
b01.setOnLongClickListener(this)
b02.setOnLongClickListener(this)
}
//...
override fun onLongClick(v: View?): Boolean {
var id = v?.id
if ((id == b00.id) or (id == b01.id) or (id == b02.id)) {
//your code
return true
}
return false
}
Following your example, I assume you're using Kotlin.
In programming, try to keep it simple, easy to understand and don't repeat yourself.
Since for OnLongClickListener you need to return Boolean that you consumed event, I suggest adding inline function
inline fun consumeEvent(function: () -> Unit): Boolean {
function()
return true
}
Then, move common code from listeners to new function, like fun myFunction() and call it.
fun myFunction() {
// some code
}
b00.setOnLongClickListener { consumeEvent { myFunction() } }
b01.setOnLongClickListener { consumeEvent { myFunction() } }
b02.setOnLongClickListener { consumeEvent { myFunction() } }
Related
I need to override the work of the keyboard (Android), I write some code.
At the onCreate method I write:
editNumber = findViewById(R.id.editNumber);
editNumber.setOnEditorActionListener(this);
editPassword = findViewById(R.id.password);
editPassword.setOnEditorActionListener(this);
editPassword.setOnTouchListener(
View.OnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
if (event.rawX >=
editPassword.right - resources.getDimension(R.dimen._48pxh)) {
editPassword.setTransformationMethod (
if (passwordVisible) null
else PasswordTransformationMethod());
passwordVisible = !passwordVisible;
editPassword.setSelection(password.size);
}
else {
editPassword.requestFocus();
super.onTouchEvent(event);
}
}
}
false;
});
I only init my view elements and add listeners.
And override the method:
override fun onEditorAction(view: TextView?, code: Int, event: KeyEvent?): Boolean {
Log.d("My", "Sddfsd") //This log never write
when (view.id) {
R.id.editNumber -> {
//I think its not matter what i do here for this question
return true;
}
R.id.password -> {
//I think its not matter what i do here for this question
return true;
}
}
return false;
}
Logs show that the method onEditorAction never works. I try to use onKeyListner, but it doesn't work too, but its works with a physical keyboard from a PC. What am I doing wrong?
You should use addOnUnhandledKeyEventListener()
I have a plus and min button that work when pressed. Now I want to make them when you hold/press it down it goes up/down more then 1 at a time.
This is one of my regular buttons:
plusBtn.setOnClickListener {
if(isEmpty(PenaltyTimeInputTxt.text))
{
PenaltyTimeInputTxt.setText("0")
}
penaltyInput = PenaltyTimeInputTxt.text.toString().toInt()
if(penaltyInput < 99){
penaltyInput++
PenaltyTimeInputTxt.setText(penaltyInput.toString())
}
else {
Toast.makeText(this, "Penalty time cannot go over 99", Toast.LENGTH_SHORT).show()
}
}
is there a simple way of doing this? I saw something about onTouchListener.
EDIT ---
End result. Thanks to Tenfour04: include the whole fun view.doWhileHeld + this:
plusBtn.doWhileHeld(this.lifecycleScope) {
if(isEmpty(PenaltyTimeInputTxt.text)) {
PenaltyTimeInputTxt.setText("0")
}
penaltyInput = PenaltyTimeInputTxt.text.toString().toInt()
while (isActive) {
if(penaltyInput < 99) {
penaltyInput++
PenaltyTimeInputTxt.setText(penaltyInput.toString())
}
else {
Toast.makeText(this#PenaltyConfigureActivity, "Penalty time cannot go over 99", Toast.LENGTH_SHORT).show()
break
}
delay(200)
}
}
Here is a helper class and function for this, which lets you do whatever you want while the button is held down:
fun View.doWhileHeld(
coroutineScope: CoroutineScope,
block: suspend CoroutineScope.() -> Unit
) = setOnTouchListener(object : View.OnTouchListener {
var job: Job? = null
var pointerInBounds = false
#SuppressLint("ClickableViewAccessibility")
override fun onTouch(view: View, event: MotionEvent): Boolean {
if (!isEnabled) {
job?.cancel()
return false
}
when (event.action) {
MotionEvent.ACTION_DOWN -> {
job = coroutineScope.launch(block = block)
pointerInBounds = true
}
MotionEvent.ACTION_MOVE -> {
val movedInBounds = event.x.roundToInt() in 0..view.width
&& event.y.roundToInt() in 0..view.height
if (pointerInBounds != movedInBounds) {
pointerInBounds = movedInBounds
if (pointerInBounds) {
job = coroutineScope.launch(block = block)
} else {
job?.cancel()
}
}
}
MotionEvent.ACTION_UP -> {
job?.cancel()
}
}
return false // allow click interactions
}
})
It runs a coroutine that restarts every time you click and hold. It also stops the coroutine and restarts it if you drag off the button and then back on, which is a conventional UI behavior.
To use it for your behavior, you can use a while loop:
plusBtn.doWhileHeld(viewLifecycleOwner.lifecycleScope) {
if(isEmpty(PenaltyTimeInputTxt.text)) {
PenaltyTimeInputTxt.setText("0")
}
penaltyInput = PenaltyTimeInputTxt.text.toString().toInt()
while (isActive) {
if(penaltyInput < 99) {
penaltyInput++
PenaltyTimeInputTxt.setText(penaltyInput.toString())
if (penaltyInput == 99) { // optional, might be nicer than showing toast
plusBtn.isEnabled = false
break
}
}
else {
Toast.makeText(this, "Penalty time cannot go over 99", Toast.LENGTH_SHORT).show()
break
}
delay(500) // adjust for how fast to increment the value
}
}
try below code may help
plusBtn.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
// button pressed
object : CountDownTimer(99000, 1000) {
override fun onTick(millisUntilFinished: Long) {
val temp = 99000 - (millisUntilFinished / 1000)
PenaltyTimeInputTxt.setText(""+temp)
}
override fun onFinish() {
Toast.makeText(this, "Penalty time cannot go over 99", Toast.LENGTH_SHORT).show()
}
}.start()
}
if (event.action == MotionEvent.ACTION_UP) {
// button released
}
true
}
I am trying to validate a form by calling the same functions multiple times. My goal is that the isFormValid function should "wait" till all functions are called and then return the boolean.
My current solution works but it looks really od. Ain't there a better way?
FormValidator Class
class FormValidator(private val context: Context) {
// some strings
private fun String.isValidEmail() = android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
fun validateNormalET(editText: MutableLiveData<String>, editTextEM: MutableLiveData<String>): Boolean {
if (editText.value.isNullOrEmpty()) {
editTextEM.value = emptyFieldError
return false
}
return true
}
fun validateMinLengthET(editText: MutableLiveData<String>, editTextEM: MutableLiveData<String>, minLength: Int): Boolean {
val errorMessage = when {
minLength < 5 -> postCodeTooFewChar
minLength < 7 -> btNumberTooFewChar
else -> "Error"
}
if (editText.value.isNullOrEmpty()) {
editTextEM.value = emptyFieldError
return false
} else if (editText.value.toString().length < minLength) {
editTextEM.value = errorMessage
return false
}
return true
}
fun validateEmail(editText: MutableLiveData<String>, editTextEM: MutableLiveData<String>): Boolean {
if (editText.value.isNullOrEmpty()) {
editTextEM.value = emptyFieldError
return false
} else if (!editText.value.toString().isValidEmail()) {
editTextEM.value = emailNotValidError
return false
}
return true
}
Current isFormValid Function
fun isFormValid(): Boolean =
formValidator.validateMinLengthET(btNumber, btNumberEM, 7) and
formValidator.validateNormalET(etFirstName, etFirstNameEM) and
formValidator.validateNormalET(etLastName, etLastNameEM) and
formValidator.validateEmail(etEmail, etEmailEM) and
formValidator.validateMinLengthET(etPostCode, etPostCodeEM, 5) and
formValidator.validateNormalET(etCity, etCityEM) and
formValidator.validateNormalET(etStreet, etStreetEM) and
formValidator.validateNormalET(etHouseNumber, etHouseNumberEM)
I appreciate every help, thank you. If there was already a question like this, then I am sorry that I opened another one..
You could use a list with its .all implementation:
fun isFormValid(): Boolean = listOf(
formValidator.validateMinLengthET(btNumber, btNumberEM, 7),
formValidator.validateNormalET(etFirstName, etFirstNameEM),
formValidator.validateNormalET(etLastName, etLastNameEM),
formValidator.validateEmail(etEmail, etEmailEM),
formValidator.validateMinLengthET(etPostCode, etPostCodeEM, 5),
formValidator.validateNormalET(etCity, etCityEM),
formValidator.validateNormalET(etStreet, etStreetEM),
formValidator.validateNormalET(etHouseNumber, etHouseNumberEM)
)
.all { it }
That way you're following the Open/Closed Principle.
If you want to make it slightly shorter, use with(formValidator) {}-scope like #iknow posted in the comment.
EDIT:
If you want it to use as little resources as possible, you could convert the list type to a boolean producer: () -> Boolean
fun isFormValid(): Boolean = listOf(
{ formValidator.validateMinLengthET(btNumber, btNumberEM, 7) },
{ formValidator.validateNormalET(etFirstName, etFirstNameEM) },
...
)
.all { it() }
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
I am trying to make a popup menu and have things happen depending on which item is clicked on. The on click listener for the menu items expects a return type of type Boolean. I have given it a return type but it still all shows up in red with a message of "Expected a value of type Boolean". Could someone tell me what I have wrong here? ( I am aware I haven't made the menu clicks do anything)
val menuButton = findViewById<Button>(R.id.categoryImageButton)
menuButton.setOnClickListener(View.OnClickListener {
fun onClick(view: View){
val popup = PopupMenu(this,menuButton)
popup.menuInflater.inflate(R.menu.popup_menu, popup.menu)
popup.setOnMenuItemClickListener(PopupMenu.OnMenuItemClickListener {
**fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.techItem -> {
return true
}
R.id.clothItem -> {
return true
}
else -> return false
}
}**
})
}
})
What you have right now is somewhere half way between object expressions and a SAM constructor. Here are some options for fixing it.
You can use the full object expression syntax, which looks like this:
popup.setOnMenuItemClickListener(object: PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.techItem -> {
return true
}
R.id.clothItem -> {
return true
}
else -> return false
}
}
})
You can improve the above slightly by using when as an expression, and returning it:
popup.setOnMenuItemClickListener(object: PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.techItem -> {
true
}
R.id.clothItem -> {
true
}
else -> false
}
}
})
Or you can just use SAM conversion to define the single function that you'd have to implement in a lambda:
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.techItem -> {
true
}
R.id.clothItem -> {
true
}
else -> false
}
}