How to disable all click events of a layout? - android

I have a layout that contains many views. Is there an easy way to disable all its views click events?

You can pass View for disable all child click event.
public static void enableDisableView(View view, boolean enabled) {
view.setEnabled(enabled);
if ( view instanceof ViewGroup ) {
ViewGroup group = (ViewGroup)view;
for ( int idx = 0 ; idx < group.getChildCount() ; idx++ ) {
enableDisableView(group.getChildAt(idx), enabled);
}
}
}

Rather than iterating through all the children view, you can add this function to the parent Layout view
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
This will be called before the onTouchEvent for any child views, and if it returns true, the onTouchEvent for child views wont be called at all. You can create a boolean field member to toggle this state on and off if you want.

I would create a ViewGroup with all the views that you want to enable/disable at the same time and call setClickable(true/false) to enable/disable clicking.

Here is a Kotlin extension function implementation of Parag Chauhan's answer
fun View.setAllEnabled(enabled: Boolean) {
isEnabled = enabled
if (this is ViewGroup) children.forEach { child -> child.setAllEnabled(enabled) }
}

You need to call setEnabled(boolean value) method on the view.
view.setClickable(false);
view.setEnabled(false);

If you dont want to set all child views to disable state (because they may look different to enable state) you can use this approach:
private fun toogleTouchable(canTouch: Boolean) {
container.descendantFocusability =
if (value) {
container.requestFocus()
ViewGroup.FOCUS_BLOCK_DESCENDANTS
} else {
ViewGroup.FOCUS_AFTER_DESCENDANTS
}
}

my layout has two modes. One preview, when children should not be clickable and the other when children should be clickable.
Of all the solutions mentioned here, I found overriding onInterceptTouchEvent most appropriate. Very little code, with no iteration on children.
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(isPreviewMode())
{
//No child is clickable in preview mode.
return true;
}
//All children are clickable otherwise
return false;
}
Though disabling children is most popular solution, I am not confortable with that.
You may need to reEnable them and the logic should match exactly. The more is the code, the higher the chances of bugs in the future.

Extension function in Kotlin, with the support of Recycler view.
fun ViewGroup.setEnabledStateForAllChildren(enable: Boolean) {
children.forEach {
when (it) {
is RecyclerView -> {
it.addOnItemTouchListener(object : SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
return enable.not()
}
})
}
is ViewGroup -> {
it.setEnabledStateForAllChildren(enable)
}
else -> {
it.isEnabled = enable
}
}
}
}

I would implement onClickListener interface in your activity class and return false in onClick method. I feel it's the easiest way to solve your problem.

Related

How to make EditText Scrollable only when it is focused inside a scroll view (Kotlin)

I have a scrollable EditText inside a ScrollView, and i want to scroll the ScrollView even when the touch is over the EditText (not focused), and make the EditText scroll only when it is focused
i can write more explanation if needed
I found the solution, maybe it will help someone else
CODE SOLUTION:
#SuppressLint("ClickableViewAccessibility")
private fun addScrollToEditText() {
binding.cardYourMessageText.setOnTouchListener(OnTouchListener { v, event ->
if (binding.cardYourMessageText.hasFocus()) {
v.parent.requestDisallowInterceptTouchEvent(true)
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_SCROLL -> {
v.parent.requestDisallowInterceptTouchEvent(false)
return#OnTouchListener true
}
}
}
false
})
}

How to define behavior from the object instance

I have an EditText that I overrode in order to detect clicks on the compound drawables.
class MyEditText
constructor(
context: Context,
attrs: AttributeSet
) : androidx.appcompat.widget.AppCompatEditText( context, attrs) {
override fun onTouchEvent(event: MotionEvent?): Boolean {
val result = super.onTouchEvent(event)
if (event != null) {
// All advice says to use ACTION_UP but that never gets here
if( event.action == MotionEvent.ACTION_DOWN ){
if(event.x <= this.totalPaddingStart){
onStartDrawableClick()
}
if(event.x >= this.width - this.totalPaddingEnd){
onEndDrawableClick()
}
performClick()
return true
}
}
return result
}
// Overriding this avoids an accessibility warning
override fun performClick(): Boolean {
return super.performClick()
}
public fun onEndDrawableClick(){
Log.e(TAG, "onEndDrawableClick: ")
}
public fun onStartDrawableClick(){
Log.e(TAG, "onStartDrawableClick: ")
}
}
This works but I want to be able to define what happens in onEndDrawableClick() from the MyEditText object instance, not in the class. I cant pass a closure to the constructor since its a view with params for xml instantiation. Is there an elegant way to do this?
(Extra bonus points for figuring out why ACTION_UP is never seen)
You can define callback properties that can be set from outside the class. Also, you can make the MotionEvent parameter non-nullable since the Android function will never pass you a null value. Then you don't have to do the null check.
Also, if you don't want other click events to happen (like if you set a click listener on the TextView) when you click on this item, you should not call super when the icon is clicked. And you should call through when the touch misses the icon instead of returning true. Example of rearranging the logic like this below.
class MyEditText(
context: Context,
attrs: AttributeSet
) : androidx.appcompat.widget.AppCompatEditText(context, attrs) {
var onEndDrawableClick: (()->Unit)? = null
var onStartDrawableClick: (()->Unit)? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
if( event.action == MotionEvent.ACTION_DOWN ){
if(event.x <= this.totalPaddingStart){
onStartDrawableClick?.invoke()
performClick()
return true
}
if(event.x >= this.width - this.totalPaddingEnd){
onEndDrawableClick?.invoke()
performClick()
return true
}
}
return super.onTouchEvent(event)
}
// Overriding this avoids an accessibility warning
override fun performClick(): Boolean {
return super.performClick()
}
}
Handling a click that behaves as users are probably accustomed, i.e. has visual/audible feedback on touch down, is cancellable by moving your finger off the target before releasing, and firing only if released over the touch target; is not trivial. You might want to build this UI component out of a RelativeLayout or ConstraintLayout that contains an EditText and two Buttons in it to provide a nicer experience.
Apologies for my rusty Kotlin, but something like this should work:
var endClickHandler: View.OnClickListener?
public fun onEndDrawableClick(){
Log.e(TAG, "onEndDrawableClick: ")
endClickHandler?.onClick(this)
}

Kotlin OnTouchListener called but it does not override performClick

How to override performClick in Kotlin to avoid warning?
next.setOnTouchListener(View.OnTouchListener { view, motionEvent ->
when (motionEvent.action){
MotionEvent.ACTION_DOWN -> {
val icon: Drawable = ContextCompat.getDrawable(activity.applicationContext, R.drawable.layer_bt_next)
icon.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY)
next.setImageDrawable(icon)
}
MotionEvent.ACTION_UP -> {
//view.performClick()
next.setImageResource(R.drawable.layer_bt_next)
}
}
return#OnTouchListener true
})
view.performClick does not work.
Try this way :
next.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> //Do Something
}
return v?.onTouchEvent(event) ?: true
}
})
Okay, I solved my own problem by overriding the OnTouch listener.
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (view) {
next -> {
Log.d("next", "yeyy")
when (motionEvent.action){
MotionEvent.ACTION_DOWN -> {
val icon: Drawable = ContextCompat.getDrawable(activity.applicationContext, R.drawable.layer_bt_next)
icon.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY)
next.setImageDrawable(icon)
}
MotionEvent.ACTION_UP -> {
view.performClick()
next.setImageResource(R.drawable.layer_bt_next)
}
}
}
previous -> {
//ingredients here XD
}
}
return true
}
And in that way, I can call single onTouch and implement it to many button and also can use the onClick by :
view.performClick()
Don't forget to implement :
View.OnTouchListener
And set the listener :
next.setOnTouchListener(this)
previous.setOnTouchListener(this)
I don't think your solution will actually solve them problem presented by the warning. The warning states that certain accessibility functions use performClick() to activate buttons. If you look in the View class, the performClick() funtions calls the onClickListener directly, meaning the code in the onTouchListener will not be executed (next.setImageResource(R.drawable.layer_bt_next)) for these accessibility functions, since the view will never be physically touched, and thus your onTouch code won't run. You have to do one of either:
Subclass the view you are setting the onTouchListener on, and override performClick to execute the code, or
Set an onClickListener on the view that executes the code.
You could just implement onClickListener in your onTouchListener class and manually call onClick() from your onTouchListener (where you have view.performClick() now), and then move your executable code to the onClick override. You would also have to set BOTH onTouchListener and onClickListener on your views.
I'm not sure this is the same issue you saw, but since I found this page searching for my issue, I thought I'd add my experience to help others :)
In my case the warning was being generated because the nullable view could have been of type Void. Calling the following:
nullableView?.setOnTouchListener(this)
produced the error:
Custom view Void has setOnTouchListener called on it but does not override performClick
Performing a null check and casting to a View before setting the listener solved for me in this case, since View will override performClick:
if (nullableView != null) (nullableView as View).setOnTouchListener(this)
After a ton of digging, and not being able to fix my variation of this issue with anything in this thread, I finally found a fix. Maybe it will work for some of you. I had this widget listener setter in my MainActivity onCreate function:
findViewById<TextView>(R.id.tvAnimalList).setOnTouchListener { v, event ->
mGestureDetector.onTouchEvent(event)
}
Which results in the warnings:
'onTouch' lambda should call 'View#performClick' when a click is detected
Custom view "TextView" has 'setOnTouchListener' called on it but does not override 'performClick'
First, I added a call to v.performClick(), which got rid of the first warning. Like this:
findViewById<TextView>(R.id.tvAnimalList).setOnTouchListener { v, event ->
v.performClick()
mGestureDetector.onTouchEvent(event)
}
I got rid of the second warning by changing the findViewById cast from <TextView> to <View>. Here's my warning-free result:
findViewById<View>(R.id.tvAnimalList).setOnTouchListener { v, event ->
v.performClick()
mGestureDetector.onTouchEvent(event)
}
private fun closeKeyboard(binding: ContollerMeterBinding) {
binding.scrollView.apply {
setOnTouchListener(OnTouchListener { v, event ->
if (event != null && event.action == MotionEvent.ACTION_MOVE) {
val imm =
activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val isKeyboardUp = imm.isAcceptingText
if (isKeyboardUp) {
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
}
performClick()
false
})
}
}
This works for me: (not directly related to onTouch event but yields the same warning, might be helpful to someone)
takePhotoButton.setOnTouchListener { _, motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
//when user touch down
}
MotionEvent.ACTION_UP -> {
//when user touch release
}
}
true
}

Android kotlin onTouchListener wants me to override performClick()

I'm trying to get rid of a warning where Android Studio wants my onTouchListener to override performClick which I do, but the warning remains.
draggableBar!!.setOnTouchListener(View.OnTouchListener { view, motionEvent ->
when (motionEvent.getAction()) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_UP -> {
view.performClick()
}
}
return#OnTouchListener true
})
Could this be an Android Studio bug or am I doing something wrong?
Okay, I have the same problem but i fixed it by overriding the onTouch listener.
The default onTouch wants us to override performClick(), but this does not work even calling the method by view.performClick().
So therefore override your onTouch like this:
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (view) {
draggableBar -> {
when (motionEvent.getAction()) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_UP -> {
view.performClick()
}
}
}
otherButtonHere -> {
//your welcome
}
}
return true
}
And in that way, you can use single onTouch() in all clickable views you have.
Don't forget to implement to your Class:
View.OnTouchListener
And set the listener:
draggableBar!!.setOnTouchListener(this)
HOPE IT HELPS! :)

Android - Espresso - scrolling to a non-list View item

Is there a general approach for scrolling to non-list View items that are not yet visible on the screen?
Without any precautions, Espresso will indicate that "No Views in hierarchy found matching with id .....
I found this answer ... is this the best approach?
onView( withId( R.id.button)).perform( scrollTo(), click());
According to the scrollTo JavaDoc, to use the code you specified ( onView( withId( R.id.button)).perform( scrollTo(), click()); ), the preconditions are: "must be a descendant of ScrollView" and "must have visibility set to View.VISIBLE". If that is the case, then that will work just fine.
If it is in an AdapterView, then you should use onData instead. In some cases, you may have to implement the AdapterViewProtocol, if your AdapterView is not well behaved.
If it is neither in an AdapterView nor a child of a ScrollView, then you would have to implement a custom ViewAction.
If you have a view inside android.support.v4.widget.NestedScrollView instead of scrollView scrollTo() does not work.
In order to work you need to create a class that implements ViewAction just like ScrollToAction but allows NestedScrollViews:
public Matcher<View> getConstraints() {
return allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
isDescendantOfA(anyOf(
isAssignableFrom(ScrollView.class),
isAssignableFrom(HorizontalScrollView.class),
isAssignableFrom(NestedScrollView.class))
)
);
}
extra tip and access the action like:
public static ViewAction betterScrollTo() {
return actionWithAssertions(new AllScrollViewsScrollToAction());
}
But with this scroll it does not trigger events from the layout managers.
Code that worked for me is:
ViewInteraction tabView = onView(allOf(
childAtPosition(childAtPosition(withId(R.id.bottomControlTabView), 0), 1),
isDisplayed()));
tabView.perform(click());
tabView.perform(click());
public static Matcher<View> childAtPosition(final Matcher<View> parentMatcher,
final int position) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
#Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
The code onView( withId( R.id.button)).perform( scrollTo(), click()); will work if the view is descendant of ScrollView, HorizontalScrollView or ListView.
If we have NestedScrollView instead of ScrollView and for those who don't want to look into ScrollToAction class code I wrote the sample.
As Bruno Oliveira said we can do something like this:
class ScrollToActionImproved : ViewAction {
override fun getConstraints(): Matcher<View> {
return allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
isDescendantOfA(
anyOf(
isAssignableFrom(ScrollView::class.java),
isAssignableFrom(HorizontalScrollView::class.java),
isAssignableFrom(NestedScrollView::class.java)
)
)
)
}
override fun getDescription(): String = "scroll to view"
override fun perform(uiController: UiController?, view: View?) {
if (isDisplayingAtLeast(90).matches(view)) {
//View is already displayed
return
}
val rect = Rect()
view!!.getDrawingRect(rect)
if (!view.requestRectangleOnScreen(rect, true)) {
//Scrolling to view was requested, but none of the parents scrolled.
}
uiController!!.loopMainThreadUntilIdle()
if (!isDisplayingAtLeast(90).matches(view)) {
throw PerformException.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(
RuntimeException(
"Scrolling to view was attempted, but the view is not displayed"
)
)
.build()
}
}
}
And use it like this:
fun scrollToImproved(): ViewAction =
actionWithAssertions(ScrollToActionImproved())
/* some logic */
onView(withId(R.id.button)).perform(scrollToImproved())
It should work.

Categories

Resources