I am developing an android app which tracks touch events for the whole application. For that purpose, I want to override onTouchEvent() without an activity; i.e., inside a simple Java class, I don't whether it's possible. Any ideas are welcome.
No, you cannot arbitrarily override onTouchEvent, particularly in a "simple Java class", i.e. one that does not extend a View.
Solution 1
If you want to capture all touches to your app, override onInterceptTouchEvent() in your main layout. So, if your main layout is a LinearLayout, do something like:
public class MyTouchLayout extends LinearLayout {
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Do whatever with your touch event
return false; // Do not prevent the rest of the layout from being touched
}
}
Then, in your XML, you'd use:
<com.example.MyTouchLayout
...
>
<!-- The rest of your layout -->
</com.example.MyTouchLayout>
Solution 2
This is by far the simpler solution, though I've had varying luck with it in the past and would never suggest that you reply upon it.
You can simply get the View that contains all your content (the root View, a LinearLayout above), then give it a listener.
View v = findViewById(android.R.id.content); // Find the root View; you may have to give it your own ID in the XML, and use that
v.setOnTouchListener(new OnTouchListener {
// ...
}
Related
Bacground
I have been working on stripping out a library that deals with adding Accessibility with Talkback that I have created in an existing app. Originally my custom views were all ViewGroups, so I got everything working amazingly with ViewGroups (focusable navigation with D-pad, initial view focus, and content descriptions)
When I was moving this to a standalone library, I noticed that it didn't work with View. I thought ViewGroup was the superclass, but it turns out that View is the superclass. So I have been trying to find some workarounds to fix my issue. I started to do the following, and have a question based on this approach...
Code In Question
public class Accessibility {
public static ViewGroupAccessibility with(ViewGroup viewGroup) {
return new ViewGroupAccessibility(viewGroup);
}
public static ViewAccessibility with(View view){
return new ViewAccessibility(view);
}
}
I have fully implemented ViewGroupAccessibility and I intend to fully implement ViewAccessibility as it is a stub right now. So far the below code works well with TalkBack, I can do ViewGroup related stuff with ViewGroups, and it appears that I can do View related stuff with Views; however, I am wondering if this is even needed
What I know
Accessibility.with(new RelativeLayout(...)) // Returns ViewGroupAccessibility as RelativeLayout is a ViewGroup
//
...will return a ViewGroupAccessibility that can handle ViewGroup related stuff that can contain many different View and ViewGroup. (See code at the bottom of this post for real usage, and what what methods are available for ViewGroupAccessibility)
Accessibility.with(new Button(...)) // Returns ViewAccessibility as Button is a View
//
...will return a ViewAccessibility that can handle single View only related stuff (that is my assumption). Think only a Button.
What I don't know
// Hypothetical Usage
Accessibility
.with(new ClassThatExtendsView_WithMultipleComponentsThatCanHaveAccessibilitySetOnEachComponentIndividually(...));
// Custom View that extends View
public class ClassThatExtendsView_WithMultipleComponentsThatCanHaveAccessibilitySetOnEachComponentIndividually extends View {
...
}
Is this even possible? If no, then I am good. If yes, then I have a lot extra to think about
It will return a ViewAccessibility that can handle single View only, but then that would be the wrong thing to return.
Another way of asking the question is am I guaranteed that if a user calls Accessibility.with(View) that the given view will ALWAYS be a single view only? Like Just a single Button. Or can the View be made of more than one component
Full Code
You can check out the code at https://codereview.stackexchange.com/questions/134289/easily-add-accessibility-to-your-app-as-an-afterthought-yes-as-an-afterthought (there is also a GitHub link to the original code). I go in incredible detail into how the project was started, my design decisions, and my future goals all to help guide the code review process.
However, here is a snippet of a usage I have for ViewGroup
public class ContributionView extends RelativeLayout implements Mappable<Resume.Contribution> {
// Called from Constructors
private void init(AttributeSet attrs, int defStyle) {
root = (ViewGroup) LayoutInflater.from(getContext()).inflate(
R.layout.internal_contribution_view, this, true);
...
// Declare Navigation Accessibility
Accessibility.with(root)
// Disable certain views in the ViewGroup from ever gaining focus
.disableFocusableNavigationOn(
R.id.contribution_textview_creator,
R.id.contribution_textview_year)
// For all focusable views in the ViewGroup, set the D-pad Navigation
.setFocusableNavigationOn(txtProjectName)
.down(R.id.contribution_textview_description).complete()
.setFocusableNavigationOn(txtContributionDescription)
.up(R.id.contribution_textview_name)
.down(R.id.contribution_textview_link).complete()
.setFocusableNavigationOn(txtProjectLink)
.up(R.id.project_textview_description).complete()
// Set which view in the ViewGroup will have be first to be focused
.requestFocusOn(R.id.contribution_textview_name);
invalidateView();
}
private void invalidateView() {
...
// Declare Content Description Accessibility
Accessibility.with(root)
// Set the content descriptions for each focusable view in the ViewGroup
// Set the content description for the Contribution Name
.setAccessibilityTextOn(txtProjectName)
.setModifiableContentDescription(getProjectName())
.prepend("Contribution occurred on the Project called ")
.append(String.format(" by %s in %s",
getProjectCreator(),
getContributionYear())).complete()
// Set the content description for the Contribution Description
.setAccessibilityTextOn(txtContributionDescription)
.setModifiableContentDescription(getContributionDescription())
.prepend("Description: ").complete()
// Set the content description for the Contribution URL
.setAccessibilityTextOn(txtProjectLink)
.setModifiableContentDescription(getProjectLink())
.prepend("URL is ").complete();
}
...
}
Yes, there is a way to move accessibility amongst the various areas/components of a View. It requires a little work, though.
Start here:
https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeProvider.html
Quick, probably simple, question. I have a view whose background is animating, and during that time, I want to disable the user from interacting with the view. My view is a FrameLayout, and I'm capturing touch events with onTouchEvent(). Solutions I've tried:
1) First setOnTouchListener(null), then setOnTouchListener(this). Problem is, my view only calls onTouchEvent(MotionEvent), and not onTouch(View, MotionEvent), so I can't pump through the Events there.
2) First setEnabled(false), then setEnabled(true). The source code says: A disabled view that is clickable still consumes the touch events, it just doesn't respond to them. Problem is, MotionEvents still get pumped through in onTouchEvent().
3) requestDisallowInterceptTouchEvent(false), then (true). This only handles touch events from the parent.
The solution I have working is using a boolean variable isAnimating, and checking the value of that in onTouchEvent(). I'd rather not do this, because it looks ugly to me and I'd rather use the API for it than reinvent the wheel, sooo... anyone got any ideas? Thanks.
Try:
setFocusable() and setFocusableInTouchMode()
Try:
#Override
public boolean onTouchEvent(MotionEvent event) {
//Check if event is on your view
if(event.getY()>view.getTop()&&event.getY()<view.getBottom()&&event.getX()>view.getLeft()&&event.getX()<view.getRight()){
//event occured inside your view
}
//here return super or as your logic prefer
return super.onTouchEvent(event);
}
I'm working on improving the accessibility within my app.
I have pretty complicated layout with cards. Each card has some clickable objects inside it, but it also has the global click-listener.
When I enable Talkback, select the card (not something inside it!), double-clicking (to open the card), the card gets the touch-event in the middle of the card.
As a result, nested object got click event and react respectively.
The question is how to determine, which item is in TalkBack's focus (green-rectangle-thing for me)? The idea is to disable inside touch-listeners, if card itself is in focus.
API level I want to support is 16 (Android 4.1+)
Thanks!
I think what would work best for you, is to override the accessibility delegate of layout view, listening for accessibility focus events. When focus is added to a card, remove listeners, when focus leaves your cards re attach your listeners. Attach this delegate to your layout view, and you should be able to watch as various views within your layout obtain and give up accessibility focus.
class MyAccessibilityDelegate extends View.AccessibilityDelegate {
#Override
public boolean onRequestSendAccessibilityEvent(ViewGroup viewGroup, View child, AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
//Do stuff in here! Maybe also do different stuff when focus is cleared!
}
return super.onRequestSendAccessibilityEvent(viewGroup, child, event);
}
}
The apis for this were added in API level 14 so you should be good to go!
I'm trying to inspect a code for a very big Android (Amazon Fire TV) activity but i keep loosing the focus in the running app and i don't know what element is being focused.
I'm looking for a way (Wether it's an App, a developer setting - Show Layout Limits gets near - or something i can code inside the activity) to see what view is being focused, without having to change the layout (Selectors) of every single view.
What do you suggest?
Activity has a method called getCurrentFocus().
Maybe you could call hasFocus() on all the Views if the above doesn't work. I imagine the method would look something like this:
public View getFocusedView(View layout)
{
View focusedView = null;
// Note: I'm not sure if FOCUS_DOWN is the right one to use here
// so you may want to see the other constants offered
ArrayList<View> views = layout.getFocusables(View.FOCUS_DOWN)
for(View v: views)
{
if(v.hasFocus())
{
focusedView = v;
}
}
return focusedView;
}
I'm trying to use the new event onHover. I look the sample, the documentation...and nothing works. The onHover event should be done by a mouse pointer.
I create a simple xml file containing 1 LinearLayout and 2 buttons inside.
My java code is following
findViewById(R.id.linearLayout1).setOnHoverListener(new OnHoverListener() {
#Override
public boolean onHover(View v, MotionEvent event) {
Log.e("tag", "tag");
return false;
}
});
LinearLayout1 is my linearlayout containing button. onHover is never called.
I tried a lot of stuff without success.
Let me know if you have an idea.
In a View that contains sub views, the subViews callbacks get called first.
if you have a button in your LinearLayout, all events are being called on the button and all the other views first, and only if all the sub views return false, only then, the event will be called on the parent view.
you should return false from all other subviews first before activating the onHover