I am developing a custom UI on top of ExoPlayer, and I noticed that the controls (PlaybackControlView) hide when I touch the screen, not when I click.
I wanted to change to a click and checked how I can change the event listener, but so far could not find an easy solution. I checked the source SimpleExoPlayerView.java and I noticed that it actually is hardcoded:
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
return false;
}
if (controller.isVisible()) {
controller.hide();
} else {
maybeShowController(true);
}
return true;
}
So far I could think of two solutions. One is to change the ExoPlayer's source code, but I do not like it since I will have to make modifications every time I update the ExoPlayer.
The second solution I could think of is simply to try to handle it myself, for example, to add my own listeners, and show and hide the controls myself. I have not tried this yet, but it seems possible.
Is there another better solution, like overriding the listeners, etc?
Update: I am using custom UI by inflating exo_playback_control_view.xml
By looking at this answer you can see that an OnTouchListener#onTouch is called BEFORE the View#onTouchEvent so you can set an OnTouchListener to the view, consume the MotionEvent and it will not be passed onto the onTouchEvent method.
For example, using this code only "onTouch: LISTENER!!!" is logged when touching the view, and not "onTouchEvent: onTouchEvent!!!":
EDIT - to add your request for a click event handling I added the use of GestureDetector, using this answer - so now upon click "onSingleTapUp: TAP DETECTED" is logged as well.
public class TouchingView extends View {
private final static String TAG="TouchingView";
private OnTouchListener touchListener;
private GestureDetector gestureDetector;
public TouchingView(Context context) {
super(context);
touchListener = new TouchListener();
gestureDetector = new GestureDetector(getContext(),
(GestureDetector.OnGestureListener) touchListener);
setOnTouchListener(touchListener);
}
public TouchingView(Context context, AttributeSet attrs) {
super(context, attrs);
touchListener = new TouchListener();
gestureDetector = new GestureDetector(getContext(),
(GestureDetector.OnGestureListener) touchListener);
setOnTouchListener(touchListener);
}
public TouchingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
touchListener = new TouchListener();
gestureDetector = new GestureDetector(getContext(),
(GestureDetector.OnGestureListener) touchListener);
setOnTouchListener(touchListener);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent: onTouchEvent!!!"); //not logged
return super.onTouchEvent(event);
}
private class TouchListener extends GestureDetector.SimpleOnGestureListener
implements View.OnTouchListener{
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG, "onTouch: LISTENER!!!"); //logged upon every touch event. twice upon click (for UP and for DOWN)
gestureDetector.onTouchEvent(event);
return true; //preventing the view's onTouchEvent from firing
}
#Override
public boolean onSingleTapUp(MotionEvent e) { //you can override onSingleTapConfirmed if you don't want doubleClick to fire it
Log.d(TAG, "onSingleTapUp: TAP DETECTED"); //logged only upon click
return true;
}
}
}
I am facing same problem but i resolved this problem by flowing set controllerHideOnTouch property of exoplayerview
i used by following line of code
mExoPlayerView!!.controllerHideOnTouch=false
its working for me
To always show controls on exo player use hide_on_touch property:
<com.google.android.exoplayer2.ui.PlayerView
android:id="#+id/video_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:controller_layout_id="#layout/layout_exo_player"
app:show_timeout="0"
app:hide_on_touch="false"/>
Related
Through my research in the question about getting button listeners across Activities and Fragments, the common response is to interface your listener and implement it in each the Fragments/Activities.
However, as long as your listener is generic (or global, affecting all buttons), this is redundant.
So enter in implementation idea 2:
I could extend my own Button class and implement the OnTouch listener. Sounds great in theory, but I'm not sure how to actually set the listener in this state. Is this on the right track, or is there a better way that I'm missing here? The only other option I thought of would be creating a different class that implements the listener, but I didn't want to instantiate the class every time I make a new button.
This is my idea:
public final class EffectsButton extends androidx.appcompat.widget.AppCompatButton implements View.OnTouchListener {
public EffectsButton(Context context) {
super(context);
}
public EffectsButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN)
handleButtonEffect(v, "onTouch.down");
else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL)
handleButtonEffect(v, "onTouch.up");
return false;
}
public static void handleButtonEffect(View view, String listenerType) {
if (listenerType.equals("onTouch.down")) {
view.getBackground().setColorFilter(0x77000000, PorterDuff.Mode.SRC_ATOP);
} else if (listenerType.equals("onTouch.up") || listenerType.equals("onClick")) {
view.getBackground().clearColorFilter();
}
view.invalidate();
}
}
Main Activity:
// onCreate();
MyButton button = findViewById(R.id.mybutton);
button.setOnTouchListener(/* what to do here? */);
Never mind, I found the answer.
All you have to do is refer to itself:
MyButton button = findViewById(R.id.mybutton);
button.setOnTouchListener(button);
How would I create a dialog which does not consume touch events. Ie. I want to be able to interact with the underlying activity as normal.
public class LoadingDialog extends Dialog {
public LoadingDialog(Context context) {
super(context);
setContentView(R.layout.loading_dialog);
setCanceledOnTouchOutside(false);
getWindow().setBackgroundDrawable(new
ColorDrawable(android.graphics.Color.TRANSPARENT));
}
}
I tried the following with no success
#Override
public boolean dispatchTouchEvent(#NonNull MotionEvent ev) {
return false;
}
Thanks
Rather than use a dialogue, you can use a view that covers the whole screen with a tint and another view that looks like a dialogue on top of the tint and pass all touch events to the view below. Remove and show the dialogue by toggling the visibility of the view.
Can I modify data which is sent to OnTouchListener? My situation looks like this:
class A extends View{
public A(Context context){
super(context);
}
...
}
And in some activity I have:
A a = new A(this);
a.setOntouchListener(new View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
// event should be modified
....
}
}
If a touch event occurs, I want to modify it, inside class A (for example change event's coordinates) so that inside onTouch method I can use modified event.
There is no way to modify what gets sent to the onTouch, I would go this route instead. Note that Coordinates is just a made up name, I am sure something similar exists but you could just create this class if not. Any parameters that can be accessed from within the touch listener could be sent to the method as well, if not you may have to set a class variable to get the value you need to check inside of the getModifiedCoordinates method
A a = new A(this);
a.setOntouchListener(new View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
Coordinates coords = getModifiedCoordinates(event);
//do something with the coordinates
}
}
...
private Coordinates getModifiedCoordinates(MotionEvent event) {
boolean shouldBeModified = <the conditions you are checking for>;
if(shouldBeModified)
return new Coordinates(modified_x,modified_y);
else
return new Coordinates(event.getX(), event.getY());
}
I get this warning (from the question title) in a custom Android view I am developing.
Why do I get warned? What's the logic behind it i.e. why is it a good
practice to also override performClick when you override onTouchEvent?
What's the purpose?
In some of the other answers you can see ways to make the warning go away, but it is important to understand why the system wants you to override performClick() in the first place.
There are millions of blind people in the world. Maybe you don't normally think about them much, but you should. They use Android, too. "How?" you might ask. One important way is through the TalkBack app. It is a screen reader that gives audio feedback. You can turn it on in your phone by going to Settings > Accessibility > TalkBack. Go through the tutorial there. It is really interesting. Now try to use your app with your eyes closed. You'll probably find that your app is extremely annoying at best and completely broken at worst. That's a fail for you and a quick uninstall by anyone's who's visually impaired.
Watch this excellent video by Google for an introduction into making your app accessible.
Developing Accessible Apps for Blind and Visually-Impaired Users
How to override performClick()
Let's look at a example custom view to see how overriding performClick() actually works. We'll make a simple missile launching app. The custom view will be the button to fire it.
It sounds a lot better with TalkBack enabled, but animated gifs don't allow audio, so you will just have to try it yourself.
Code
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<net.example.customviewaccessibility.CustomView
android:layout_width="200dp"
android:layout_height="200dp"
android:contentDescription="Activate missile launch"
android:layout_centerInParent="true"
/>
</RelativeLayout>
Notice that I set the contentDescription. This allows TalkBack to read out what the custom view is when the user feels over it.
CustomView.java
public class CustomView extends View {
private final static int NORMAL_COLOR = Color.BLUE;
private final static int PRESSED_COLOR = Color.RED;
public CustomView(Context context) {
super(context);
init();
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setBackgroundColor(NORMAL_COLOR);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setBackgroundColor(PRESSED_COLOR);
return true;
case MotionEvent.ACTION_UP:
setBackgroundColor(NORMAL_COLOR);
// For this particular app we want the main work to happen
// on ACTION_UP rather than ACTION_DOWN. So this is where
// we will call performClick().
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();
launchMissile();
return true;
}
private void launchMissile() {
Toast.makeText(getContext(), "Missile launched", Toast.LENGTH_SHORT).show();
}
}
Notes
The documentation also uses an mDownTouch variable which appears to be used to filter out extra touch up events, but since it isn't well explained or strictly necessary for our app, I left it out. If you make a real missile launcher app, I suggest you look more into this.
The primary method that launches the missile (launchMissile()) is just called from performClick(). Be careful not to call it twice if you also have it in onTouchEvent. You will need to decide exactly how and when to call your business logic method depending on the specifics of your custom view.
Don't override performClick() and then do nothing with it just to get rid of the warning. If you want to ignore the millions of blind people in the world, then you can suppress the warning. At least that way you are honest about your heartlessness.
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent event) { ... }
Further study
Accessibility overview
Build accessible custom views (especially the Handle custom touch events section)
Make apps more accessible
This warning tells you to override performClick
#Override
public boolean performClick() {
// Calls the super implementation, which generates an AccessibilityEvent
// and calls the onClick() listener on the view, if any
super.performClick();
// Handle the action for the custom click here
return true;
}
But it is not compulsory. As I have created a custom knobView and it is working quite good where I am also facing this warning.
The onTouchEvent is not called by some Accessibility services, as explained by clicking the "more..." link in the warning details.
It recommends that you override performClick for your desired action, or at least override it alongside your onTouchEvent.
If your code is more fitting for the touch event, you can use something similar to:
#Override
public boolean performClick() {
if (actionNotAlreadyExecuted) {
MotionEvent myEvent = MotionEvent.obtain(long downTime, long eventTime, int action, float x, float y, int metaState);
onTouch(myView, myEvent);
}
return true; // register it has been handled
}
More information on accessing touch events through code is available at trigger ontouch event programmatically
I have a texSwitcher to which I add two text views (created dynamically using TextView class). I am switching between the child text views using gesture detector. But when the text is large to fit in the current viewable area, the scrolling doesn't work for textswitcher.
When I tried using setTextMovement method of child text views, then the TextSwitcher stopped listening to horizontal swipe gestures.
Has anybody been successful in showing scrollable text views inside a TextSwitcher.
I solved this problem with creating my own TextSwitcher.
public class MyOwnSwitcher extends ViewSwitcher {
public MyOwnSwitcher (Context context) {
super(context);
}
public MyOwnSwitcher (Context context, AttributeSet attrs) {
super(context, attrs);
}
}
I moved my "onTouchEvent"-Method into that new Class. Then I had to Override the "onInterceptTouchEvent"-Method like that:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
onTouchEvent(ev);
return super.onInterceptTouchEvent(ev);
}
I also had to move some of my Fields and Variables from my Activity to that new class.
But you can also use methods of your activity too with:
Activity ac = (Activity) this.getContext();
That should return your Activity.