35

I'm porting a codebase from native Holo (Theme.Holo etc) to the appcompat-v7 (Theme.AppCompat and so on). The last section contains the tl;dr if you don't want to read the details.


The issue

Everything's working but I had issues replicating one behaviour that was quite easy to have using the old ActionBar. I have a video player, and in landscape I want it to behave like YouTube: hide (animating) the player controls, app bar and status bar. On user interaction, the UI controls should leave this "lights out" mode and go back to the normal state. A timer would then go back to lights out mode if the user doesn't touch the screen for X seconds. The same code that worked with the ActionBar doesn't do the trick with a Toolbar.

So, what I am using is:

  • An opaque Status Bar
  • setSystemUiVisibility() using one of these combos:
    • default: View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
    • lights out: View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
  • minSdkVersion is 16
  • to show and hide the ActionBar I simply called show() and hide() on it
  • to implement the same behaviour on the app bar I subclassed Toolbar and added a show() and a hide() method that do the same (first simply using setVisibility(), and then using animations -- getting the same results)

The LAYOUT_STABLE made so that the appbar would end up behind the status bar, of course (as it implies a fitSystemWindows. Since the appbar is a normal View in the view hierarchy and is not in the decor like the ActionBar was, it is affected by that flag. This was what I was seeing on screen:

App bar behind the status bar

Not immediately clear what the toolbar bounds are, as the app bar is dark on dark, but you can see the title is cut and "misaligned". This is because the toolbar was correctly sized but behind the status bar. My main problem was at that point that there is no public API to get the status bar height, rectangle or anything else to shift my app bar vertically to show below the status bar.

Testing was performed mostly on a N5 on LPX13D (latest Lollipop public build at the time of writing), but the same could be seen happening on Android 4.4.


The hacky solution

That said, after quite some time and some failed attempts at making it work in a not-too-hacky way (including the rather desperate attempt of trying to put it into the decor myself), I resorted to this nasty way of making it work:

  1. In onResume:
    a. Call setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE) even when I wouldn't normally do it (landscape)
    b. Register an OnPreDrawListener

  2. In the OnPreDrawListener:
    a. Get the root view using View root = getRootView().findViewById(R.id.my_root)
    b. Get the root view height: int rootTop = getAbsoluteViewTop(root) (see below)
    c. Use that top (intending it as a status bar height) as a paddingTop for the appbar
    d. Set the SystemUiVisibility I'd normally use (LAYOUT_STABLE etc)
    e. Suppress the drawing pass

  3. In the next drawing pass, unregister the OnPreDrawListener

This is the getAbsoluteViewTop() method:

private int getAbsoluteViewTop(View view) {
    int[] location = new int[2];
    view.getLocationOnScreen(location);
    return location[LOCATION_Y];
}

tl;dr and question

tl;dr: there is no way to do a proper "lights out" mode with a Toolbar unless hacky hacks are employed.

Question: is there an official and clean way to implement this lights out mode with Toolbar?
I couldn't find anything in the docs. The status bar is not accessible (and all the ways to find out its size you can find on SO are broken, FYI). But maybe I'm missing something. I would love to get rid of this hack.

Rishabh Srivastava
  • 3,683
  • 2
  • 30
  • 58
rock3r
  • 701
  • 4
  • 14
  • Ander Webb suggested on G+ that one could subclass the container (`FrameLayout` in my case), and override `fitsSystemWindows()` to get the system bars sizes. I have to see if this works with `setSystemUiVisibility(LAYOUT_STABLE)`; I'll update the question/add an answer if it's the case. – rock3r Nov 11 '14 at 09:34
  • 2
    **UPDATE** I've finally managed to get a PoC solution that uses (a tweaked version of) Chris Banes' `SystemUiHelper`, plus `fitSystemWindows`, plus appcompat-v7 r21's `Toolbar`. It's been tougher than I had anticipated but it works, and looks slightly less hackish. I'm planning on open source that PoC as soon as I have some time to clean up the code. – rock3r Nov 11 '14 at 14:36
  • I am having the same issue... Have you already published your code somewhere or would you mind sharing it? Thanks! – jeff_bordon Nov 06 '15 at 09:22

2 Answers2

1

I've found fitssystemwindows pretty unreliable as well when you want some elements of the view hierarchy to be fullscreen and some elements to be below the status bar or action bar. But I've never seen the action bar behind the status bar. Is the Toolbar set as the activity actionbar? Are you using Window.FEATURE_ACTION_BAR_OVERLAY? Anyway, if all you need is to get a number of pixels for the paddingTop in onResume(), it might be a bit cleaner (still hacky though) to do something like the following rather than use OnPreDrawListeners.

class MyVideoActivity extends Activity {
    Toolbar bar;

    //...

    @Override protected void onResume() {
        super.onResume();
        bar.setPaddingTop(getStatusBarHeight());
    }

    int getStatusBarHeight() {
        int result = 0;
        Resources res = getResources();
        int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = res.getDimensionPixelSize(resourceId);
        }
        return result;
    }
}
bkDJ
  • 750
  • 7
  • 10
  • Yes, I do `setSupportActionBar(toolbar)`, and no, the actionbar is not set as overlay (I have no ActionBar as I'm using the AppCompat theme and toolbars). Your way of getting the status bar height is one of the ones you find floating on SO and **doesn't work**. First example: Samsung uses a different dimension for their status bar, which has got a different value from the (private and without any public contract) dimension you're proposing to use. So... not a valid solution, sorry. – rock3r Nov 11 '14 at 12:00
1

Not sure if this will help but have you looked at using

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);

in the onCreate method of your video activity? It might be a good starting point. This basically hides the status bar and makes my video full screen.

This is the layout I also use:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:background="@color/black">

        <VideoView
            android:id="@id/videoSurface"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:layout_centerInParent="true"
            android:layout_alignParentTop="true"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_alignParentRight="true"
            android:layout_gravity="center"
            />

</RelativeLayout>

I know some of the parameters are redundant but in the end it gives me the desired effect I wanted. It might be a good starting point for you?