I have an extended view from SurfaceView like this:
public class MyView extends SurfaceView implements View.OnClickListener {
public MyView(Context context)
{
super(context);
mContext = context;
setOnClickListener(this);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case DOWN OR UP OR MOVE: <-- I do something here like move and drag and drop and
// any related behavior to MyView
}
invalidate();
return true;
}
Now I implemented OnClickListener and set it in my constructor.
OnClick method:
#Override
public void onClick(View v){
Log.e("as", "clicked");
}
But onClick method never not called. How can I solved that?
When replaced return true; with return super.onTouchEvent(event); my onClick method called, but when I want to move my custom view, onClick method again called and this is not my porpuse.
I want only when I tapped my custom view, onClick called, and when moved it not called.
Thanks In advance.
The problem is probably your onTouch Listener. You are overriding and not dealing with the onClick possibility. Additionally you are consuming the event with a return true so the onClick will never be called.
Remember that onClick is dealt in onTouchEvent implemented in the original view class.
You can check more info at:
http://developer.android.com/reference/android/view/View.html#onTouchEvent(android.view.MotionEvent)
where you can read:
public boolean onTouchEvent (MotionEvent event)
Added in API level 1 Implement this method to handle touch screen
motion events.
If this method is used to detect click actions, it is recommended that
the actions be performed by implementing and calling performClick().
This will ensure consistent system behavior, including:
obeying click sound preferences dispatching OnClickListener calls
handling ACTION_CLICK when accessibility features are enabled
Parameters event The motion event.
Returns True if the event was
handled, false otherwise.
Since you want to choose when click is performed, you should implement the performClick (like it states in the link I have referenced).
Related
When I try to add onTouchListner() to a button, it gets me the
Button has setOnTouchListener called on it but does not override
performClick
warning. Does anyone know how to fix it?
btnleftclick.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return false;
}
});
Error:
Custom view has setOnTouchListener called on it but does not override
performClick If a View that overrides onTouchEvent or uses an
OnTouchListener does not also implement performClick and call it when
clicks are detected, the View may not handle accessibility actions
properly. Logic handling the click actions should ideally be placed in
View#performClick as some accessibility services invoke performClick
when a click action should occur.
This warning comes up because Android wants to remind you to think about the blind or visually impaired people who may be using your app. I suggest you watch this video for a quick overview about what that is like.
The standard UI views (like Button, TextView, etc.) are all set up to provide blind users with appropriate feedback through Accessibility services. When you try to handle touch events yourself, you are in danger of forgetting to provide that feedback. This is what the warning is for.
Option 1: Create a custom view
Handling touch events is normally something that is done in a custom view. Don't dismiss this option too quickly. It's not really that difficult. Here is a full example of a TextView that is overridden to handle touch events:
public class CustomTextView extends AppCompatTextView {
public CustomTextView(Context context) {
super(context);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_UP:
performClick();
return true;
}
return false;
}
// Because we call this from onTouchEvent, this code will be executed for both
// normal touch events and for when the system calls this using Accessibility
#Override
public boolean performClick() {
super.performClick();
doSomething();
return true;
}
private void doSomething() {
Toast.makeText(getContext(), "did something", Toast.LENGTH_SHORT).show();
}
}
Then you would just use it like this:
<com.example.myapp.CustomTextView
android:id="#+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="Click me to do something"/>
See my other answer for more details about making a custom view.
Option 2: Silencing the warning
Other times it might be better to just silence the warning. For example, I'm not sure what it is you want to do with a Button that you need touch events for. If you were to make a custom button and called performClick() in onTouchEvent like I did above for the custom TextView, then it would get called twice every time because Button already calls performClick().
Here are a couple reasons you might want to just silence the warning:
The work you are performing with your touch event is only visual. It doesn't affect the actual working of your app.
You are cold-hearted and don't care about making the world a better place for blind people.
You are too lazy to copy and paste the code I gave you in Option 1 above.
Add the following line to the beginning of the method to suppress the warning:
#SuppressLint("ClickableViewAccessibility")
For example:
#SuppressLint("ClickableViewAccessibility")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button myButton = findViewById(R.id.my_button);
myButton.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return false;
}
});
}
Solution:
Create a class that extends Button or whatever view you are using and override performClick()
class TouchableButton extends Button {
#Override
public boolean performClick() {
// do what you want
return true;
}
}
Now use this TouchableButton in xml and/or code and the warning will be gone!
Have you tried adding :
view.performClick()
or adding suppresslint annotation :
#SuppressLint("ClickableViewAccessibility")
?
Custom view controls may require non-standard touch event behavior.
For example, a custom control may use the onTouchEvent(MotionEvent)
listener method to detect the ACTION_DOWN and ACTION_UP events and
trigger a special click event. In order to maintain compatibility with
accessibility services, the code that handles this custom click event
must do the following:
Generate an appropriate AccessibilityEvent for the interpreted click
action. Enable accessibility services to perform the custom click
action for users who are not able to use a touch screen. To handle
these requirements in an efficient way, your code should override the
performClick() method, which must call the super implementation of
this method and then execute whatever actions are required by the
click event. When the custom click action is detected, that code
should then call your performClick() method.
https://developer.android.com/guide/topics/ui/accessibility/custom-views#custom-touch-events
At the point in the overridden OnTouchListener, where you interprete the MotionEvent as a click, call view.performClick(); (this will call onClick()).
It is to give the user feedback, e.g. in the form of a click sound.
you can suppress a warning
#SuppressLint("ClickableViewAccessibility")
or call performClick()
[Example]
I have a list view whose item is a linearlayout which has a button as it's child view. I want the ontouchLIstener of the linearlayout to work. I don't want to use onInterceptTouchEvent. Is there a way a I can pass on the touch form the button to the parent listview. I tried this
- returning true from the button's onTouchListener
private View.OnTouchListener buttonListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.i(TAG, "Button On Touch");
return true;
}
};
But this does not work. It does not pass on the touch event to the linearlayout's onTouchListener.
There must be someway it should work.
The chain of events for a View group works like this - The dispatchEvent is called. Then dispatchEvent calls onInterceptTouchEvent. if it returns false, then the touch events are passed on to the children of the ViewGroup. If any of the children consume the event (in this case the button consumes the event) i.e if they return true then the motionevent is not passed on to other methods. Since the button is clickable it returns true in this case. If the onInterceptTouchEvent method returns true then the child views are not given the motion event and instead that ViewGroup's onTouchListener or onTouch method are called. Hence to pass on the touch event to the parent's (View Group) onTouchListener make sure to return true in the onInterceptTouchEvent method of the Parent (ViewGroup). You don't have to override onDispatchTouchEvent()
#Override
public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
Log.i(TAG,"PARENT.onInterceptTouchEvent");
return true;
}
For more details about how the touch navigation is done, please see this stack post
Set your button clickable property to false, using:
button.setClickable(false);
Then in onTouch of button:
return false;
Note: This behavior is specific to button (and any other view that has clickable property set to true) that even if you return false from onTouch it will not propagate event to the parent and onClick method of the button will be called anyway.
EDIT: Another way is extending ListView class and overriding onInterceptTouch:
public class CustomListView extends ListView {
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// here you can listen for button touch events
}
}
when deriving a class from android.view.View, I see 3 possibilities to handle touch events:
use the setOnTouchListener method to add a listener instance
override the View.onTouchEvent method
implement OnTouchListener in derived View
class ExtendedView extends View {
public ExtendedView(Context context) {
super(context);
// 1. set a listener
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return false;
}
});
}
// 2. override onTouchEvent
#Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.onTouchEvent(event);
}
}
// 3. implement and use OnTouchListener
class ExtendedView3 extends View implements OnTouchListener {
public ExtendedView3(Context context) {
super(context);
setOnTouchListener(this);
}
// not override this
// public boolean onTouchEvent() {...}
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return false;
}
}
These alternatives seems to serve the same purpose. The only obvious difference is the extra View parameter, when using OnTouchListener - so one could listen to different views (in this case a non anonymous listener would be used). But this is not needed in my scenario; I just want to listen to the events of the instance of my ExtendedView.
I use - with some success - alternative 2, override onTouchEvent. Overriding the existing method seems to me the natural and simplest way to accomplish the task.
The reason why I'm here: I've see code in which a View derived class uses setOnTouchListener to set an extra listener, instead of overriding onTouchEvent.
So my questions are:
do I miss something?
is alternative 1 (and 3) appropriate, when deriving from a View?
Extra Bonus question: View does NOT implement the OnTouchListener interface, but has the onTouchEvent method. Why? Wouldn't it be more coherent for the View to implement this interface instead of having the onTouchEvent method?
PS: I found a discussion about this issue:
Where should I implement the Android onTouchListener?
Frankly, I don't understand the accepted answer: "edthethird" wrote about overriding onTouchEvent:
There is only 1 extra class instantiated ...
which extra class - I don't see one.
And in the onTouchEvent implementation he compares an event with a view - this doesn't make much sense for me. Additionally he assumes that different views will call onTouchEvent - but - if I understand the override approach right - this will NOT happen.
--
"BeatingToADifferentRobot" wrote "... setOnTouchListener [..] provides more flexibility and follows a general java pattern"
I can't follow this. What more flexibility could that be? I can always derive from the class and override the method.
And overriding a method is - hopefully - nothing "not following a general java pattern"
I have two different objects that implement OnTouchListener.
One of them, uses the event data to modify some internal data, and the other one Updates the UI.
The problem is that if I set them both to listen to the same View, only one of them handles the OnTouch event, the other one's OnTouch function is not called.
I read that if the OnTouch function returns true, that means that the event has been handled, but if I return False, they dont behave properly.
So, the question is:
How can I set two or more listener objects to the same OnTouch events from a View, and allow them all to receive all those events?
Just have one listener call a method for both Objects:
Object uiModifier;
Object internalDataModifier;
//ensure these Objects are initialized
myView.setOnClickListener(new OnTouchListener() {
#Override
public boolean onTouchListener(View v, MotionEvent event)
{
internalDataModifier.handleTouch(v, event);
uiModifier.handleTouch(v, event);
return true;
}
}
and ensure that both internalDataModifier and uiModifier have a handleTouch(View, MotionEvent) method.
Why can't you have the listener point to only the UI activity and then from that activity you can call whatever is updating the internal data? I don't think having 2 event listeners responding to the same event is a good idea even if it is possible(which I doubt). Imagine if someone tried to have 2 UI activities responding to 1 event and the clashes it would cause.
My workaround for this was to have only one class extend SimpleOnGestureListener and implement OnTouchListener. One class was extending SimpleOnGestureListener while the other class was implementing OnTouchListener but both were handling on touch events.
I then moved all the code for the smaller class (in terms of lines of code) to the larger class, then called the larger class in my implementation and it worked. I then declared my class as so
public class MyOnlyListener extends SimpleOnGestureListener implements OnTouchListener
For reference sake I was using one class to pan zoom an image (the larger class) while I was using the other to swipe to the next page in a view.
in Kotlin, i do this:
extend the View, then
override setOnTouchListener, then
call super.setOnTouchListener and pass a initialized listener object.
Snipets:
var customOnTouchListener: OnTouchListener? = OnTouchListener { v, event ->
return#OnTouchListener false
}
override fun setOnTouchListener(l: OnTouchListener?) {
val bridgeListener = OnTouchListener { v, event ->
// Listener 2
customOnTouchListener?.onTouch(v, event)
// Listener 1
l?.onTouch(v, event) ?: false
}
super.setOnTouchListener(bridgeListener)
}
then i use it like this:
customScrollView.setOnTouchListener { v, event ->
// Got the event
}
customScrollView.customOnTouchListener = View.OnTouchListener { v, event ->
// Got the event too
}
I call
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
when my app starts to make my app able to display the full screen.
I want my app's UI to pop up when screen is touched, but Activity.onTouchEvent() is not triggered until the screen is touched a second time. At first touch, only the Virtual Keys are shown.
So, I have to trigger my app's UI to pop up on
public void onSystemUiVisibilityChange(int visibility) {
if (visibility == View.SYSTEM_UI_FLAG_VISIBLE) {
// show my APP UI
}
}
but onSystemUiVisibilityChange with View.SYSTEM_UI_FLAG_VISIBLE will be invoked NOT once per touch (3 times on my Galaxy Nexus) by system, especially if the user touches the screen very fast/often.
project lib 4.0 or 4.03.
Samsung galaxy(9250) with 4.03.
Android 4.4 (API Level 19) introduces a new SYSTEM_UI_FLAG_IMMERSIVE flag for setSystemUiVisibility() that lets your app go truly "full screen." This flag, when combined with the SYSTEM_UI_FLAG_HIDE_NAVIGATION and SYSTEM_UI_FLAG_FULLSCREEN flags, hides the navigation and status bars and lets your app capture all touch events on the screen.
This did work for me:
setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
// show my app UI
}
}
});
What I've done is first imported android.view.GestureDetector so I can use it to detect gestures. Android has a number of default gestures that are automatically detected in the GestureDector class. Most of this info is found here, but below is code in a form that I've used in an actual project that works.
First I've made an anonymous class in my Activity (this can be nested wherever, but I tend to make my anonymous classes at the bottom, right before the closing bracket). NOTE: You can also implement OnGestureListener as part of your class, also.
The code below is for using gesture detection to give a simple hide/show.
I've declared and defined my action bar (my UI, which is initially hidden) as an instance variable, so I can access it here, and wherever else, but you can substitute it for a getActionBar().show() and getActionBar().hide() in the case you don't want to declare it as an instance variable. Substitute your UI in the place of the actionBar here:
public class Example extends ActionBarActivity {
// declared in onCreate() method
private android.support.v7.app.ActionBar actionBar;
private GestureDetectorCompat mDetector;
private YourView view1;
private YourView view2;
private YourView view3;
private YourView view4;
// some other code
class GestureListener extends GestureDetector.SimpleOnGestureListener {
private static final String DEBUG_TAG = "Gestures in Example Class";
#Override
public boolean onDoubleTap(MotionEvent event) {
Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
// if there is a double tap, show the action bar
actionBar.show();
return true;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent event) {
Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
// if the tap is below the action bar, hide the action bar
if (event.getRawY() > getResources().getDimension(R.dimen.abc_action_bar_default_height)) {
actionBar.hide();
return true;
}
return false;
}
#Override
public boolean onDown(MotionEvent event) {
return true;
}
} // end-of-Example Class
Then in my onCreate() I've declared my GestureDetector and also (optionally) set my GestureListeners:
private GestureDetectorCompat mDetector;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// some code here
mDetector = new GestureDetectorCompat(this, new GestureListener());
// this code is for more advanced view logic not needed for a basic set-up
//setGestureListeners();
} // end-of-method onCreate()
Then in order to actually send gestures to be processed we provide the instructions for doing that, there are two ways I know about, first the simplest:
/**
* This method recognizes a touchEvent and passes it to your custom GestureListener
* class.
*/
#Override
public boolean onTouchEvent(MotionEvent event){
this.mDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
The second way is more complex, but if you want to only recognize touch events on certain Views in your layout as in the case where you have overlapping views and can only access the top View, you can create a custom class to pass the event around or up:
class MyOnTouchListener implements View.OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
if (v.equals(view4)) {
return mDetector.onTouchEvent(event);
} else return false;
}
} // end-of-class MyOnTouchListener
and then use it here:
public void setGestureListeners() {
/* when we return false for any of these onTouch methods
* it means that the the touchEvent is passed onto the next View.
* The order in which touchEvents are sent to are in the order they
* are declared.
*/
view1.setOnTouchListener(new MyOnTouchListener());
view2.setOnTouchListener(new MyOnTouchListener());
view3.setOnTouchListener(new MyOnTouchListener());
view4.setOnTouchListener(new MyOnTouchListener());
} // end-of-method setGestureListeners()
In my setGestureListeners method, I gave them all the same set of commands, that essentially only recognizes touchEvents on view4. Otherwise, it just passes the touchEvent to the next view.
This is code using AppCompat, but if you are not building for older versions, you can use the regular GestureDetector and ActionBar.
Have you tried adding code to only show your UI when the state has changed? You have to maintain the last known visibility and only show your UI when you first come into being visible:
int mLastSystemUiVis;
#Override
public void onSystemUiVisibilityChange(int visibility) {
int diff = mLastSystemUiVis ^ visibility;
mLastSystemUiVis = visibility;
if ((diff&SYSTEM_UI_FLAG_VISIBLE) != 0
&& (visibility&SYSTEM_UI_FLAG_VISIBLE) == 0) {
// DISPLAY YOUR UI
}
}
Code sample adopted from the Android docs
The method Activity.onTouchEvent() gets called at the end of the responder chain (meaning after all other views have had a chance to consume the event). If you tap on a view that is interested in touch (i.e. a Button or EditText) there's a good chance your Activity will never see that event.
If you want to have access to touches before they every get dispatched to your view(s), override Activity.dispatchTouchEvent() instead, which is the method called at the beginning of the event chain:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
//Check the event and do magic here, such as...
if (event.getAction() == MotionEvent.ACTION_DOWN) {
}
//Be careful not to override the return unless necessary
return super.dispatchTouchEvent(event);
}
Beware not to override the return value of this method unless you purposefully want to steal touches from the rest of the views, an unnecessary return true; in this spot will break other touch handling.
I got this problem too, and I found this http://developer.android.com/reference/android/view/View.html#SYSTEM_UI_FLAG_HIDE_NAVIGATION
So, no way to help. Even the android system packaged Gallery app used SYSTEM_UI_FLAG_LOW_PROFILE instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION in photo page view. This is at least what we can do.
I had a very similar issue with trying to update the UI from an onTouchEvent() requiring two touches to work, and I tried a bunch of elaborate stuff before finally getting it to work on the first click.
In my case, I was showing a previously hidden item, then getting its height, then moving a button down by that height. The problem I ran into is that the height was showing as 0 until after the first touch event finished. I was able to solve this by calling show() during ACTION_UP for the onTouchEvent() instead of its ACTION_DOWN. Maybe it'd work if you did something similar?
Try to use:
getWindow().getDecorView().setSystemUiVisibility(View.GONE);
instead:
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
After that you can use normal activity in fullscreen and if you want nav keys you need to swipe from bottom to up. Working for me at Galaxy Tab 2 with android 4.1.2