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:
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:
In onResume:
a. Call setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE) even when I wouldn't normally do it (landscape)
b. Register an OnPreDrawListener
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
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.
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;
}
}
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?
Related
In the app, I have status bar and hide the bottom control bar.
Last time, I used marginTop="20dp" to fits the under line of status bar. However, I tested out with Galaxy 10 And the status bar is very different. So, My app looked weird, And solved the problem with applying this attribute at the root view.
fitsSystemWindows = "true"
However, Now, I have another problem, that the bottom control bar is hid but there's empty space as big as the bottom control bar after applying the attribute. Is there any way to fits the screen that shows status bar and no bottom control bar(While I hide it)?
(Yellow one is MainActivity and Black one is Fragment)
The pink part must reach the bottom, but it can't reach because of that attribute.
I tested and I think just happens with kitkat and Q.
When I remove that attribute it works with the bottom control bar, but not the status bar...
this is my layout file.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.main.MainActivity">
<fragment
...
/>
...
</FrameLayout>
So, I use only one Activity which has lots of Fragments.
And this is what Activity use:
private fun hideStatusBar() {
DisplayUtil.hideStatusBar(this)
this?.window?.decorView?.setOnSystemUiVisibilityChangeListener { visibility ->
if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
DisplayUtil.hideStatusBar(this)
}
}
}
object DisplayUtil {
fun hideStatusBar(activity: Activity?) {
activity?.window?.decorView?.systemUiVisibility = (
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY // hide status bar and nav bar after a short delay, or if the user interacts with the middle of the screen
)
}
}
I have a screen with a bottom toolbar aligned using the following styling
<style name="BottomToolbar" >
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">#color/WHITE</item>
<item name="android:layout_gravity">bottom</item>
<item name="android:layout_alignParentBottom">true</item>
<item name="android:minHeight">?attr/actionBarSize</item>
</style>
When I first enter my screen the toolbar is nicely aligned to the bottom of the screen.
However, If I go to full screen mode (which hides the toolbar) and then return to normal mode (which re-displays the toolbar) the toolbar is now placed slightly off the screen. It looks as if its shifted down the amount of the status bar that is hidden in full screen mode.
If I background my app and then foreground it, the bottom toolbar is rendered correctly.
I have a bar at the top of the screen (instead of an appBar) and that is rendered in the correct place. I can not work out why it is misplacing the bottom toolbar.
Its as if when it is determining the bottom of the parent it is not adjusting for the fact its not full screen height anymore.
What I have tried
I have tried setting "fitsSystemWindows="true"" on my toolbar when exiting full screen mode but that doesn't make a difference.
I have tried setting the toolbar to INVISIBLE rather than GONE when hiding it but that makes no difference.
EDIT 1 - Added Code snippet of how I go into and out of full screen
I go into full screen using the following
getActivity().getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
I exit full screen by using the following code
getActivity().getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
EDIT 2 - View is not shoved off the bottom, its height is too small
It appears that the bottom view is not shoved off the bottom of the screen, its height is shrunk and smaller than it should be.
The bottom view is a LinearLayout with its height set to "wrap_content" but it appears the height is not calculated correctly as its shorter than its contents and hence the bottom of its contents are cut off.
Edit 3 - devices the issues presents on
Further investigation showed the issue only presents on devices which do not have the on screen navigation bar. For example the issue is on my Samsung 10" Tablet but not on my Nexus 5x.
If I remove the following flag the issue appears to be fixed
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
The Google doc for this flag is "When using other layout flags, we would like a stable view of the content insets given to fitSystemWindows(Rect)."
https://developer.android.com/reference/android/view/View.html
The docs say
"You may also need to use SYSTEM_UI_FLAG_LAYOUT_STABLE to help your app maintain a stable layout." (https://developer.android.com/training/system-ui/status.html) and
"It's good practice to include other system UI flags (such as SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION and SYSTEM_UI_FLAG_LAYOUT_STABLE) to keep the content from resizing when the system bars hide and show." (https://developer.android.com/training/system-ui/immersive.html)
I'm getting same issue. I also searched on google but not getting solution.
I have apply 100 milliseconds delay on onBackPressed() because I have used setSystemUiVisibility but It is taking time.
Issue - Fragment is calling before setSystemUiVisibility because setSystemUiVisibility is taking time so we need to set delay on back press.
public static void hideToolbarFoProfile(Context mContext) {
Activity mActivity = (Activity) mContext;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window w = Objects.requireNonNull(mActivity).getWindow();
w.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
((MainActivity) Objects.requireNonNull(mActivity)).toolbar.setVisibility(View.GONE);
}
public static void showToolbarFoProfile(Context mContext) {
Activity mActivity = (Activity) mContext;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window w = Objects.requireNonNull(mActivity).getWindow();
w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
((MainActivity) Objects.requireNonNull(mActivity)).toolbar.setVisibility(View.VISIBLE);
}
new Handler().postDelayed(() -> {
Objects.requireNonNull(getActivity()).onBackPressed();
}, 100);
My problem:
I exit from a fullscreen activity to a Dark Action theme activity.
I pressed back button on the soft keyboard to go back to fullscreen activity.
The views in the bottom were getting cut off suddenly.
I solved this by overriding onBackPressed() method and restarting my Activity:
#Override
public void onBackPressed() {
startActivity(new Intent(getApplicationContext(), YourActivityHere.class));
}
This should recreate the activity. It is a good hack if you can apply.
When using a SwipeRefreshLayout in combination with a overlay mode ActionBar, the loading animation will be displayed behind the actionbar, making it almost invisible.
Is there anything I can do to show it on top of the actionbar?
In the Material Design version of the appcompat-v7 library (v21.0.0), SwipeRefreshLayout gets a method to set the Progress View offset.
https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html#setProgressViewOffset(boolean,%20int,%20int)
public void setProgressViewOffset (boolean scale, int start, int end)
The refresh indicator starting and resting position is always positioned near the top of the refreshing content. This position is a consistent location, but can be adjusted in either direction based on whether or not there is a toolbar or actionbar present.
Parameters
scale Set to true if there is no view at a higher z-order than where the progress spinner is set to appear.
start The offset in pixels from the top of this view at which the progress spinner should appear.
end The offset in pixels from the top of this view at which the progress spinner should come to rest after a successful swipe gesture.
Try this code:
int top_to_padding=100;
swipe_refresh_layout.setProgressViewOffset(false, 0,top_to_padding);
I hope its work for you.
the answer of Vijay Rajput works for me, but the problem was only on Kitkat or hight so I included:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
swipeLayout.setProgressViewOffset(false, 0,100);
}
Instead of setting paddingTop on the SwipeRefreshLayout, setting the layout_marginTop will make the progress bar visible:
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?android:attr/actionBarSize">
I have currently come across this when I upgraded my dependency from appcompat 19.1.0 to 21.0.0 and made no code changes.
This is actually a known issue with SwipeRefreshLayout in appcompat 21.0.0.
Look here for a workaround:
https://code.google.com/p/android/issues/detail?id=77712
EDIT
This was the best solution before the v21 of the appcompat-v7 library. If you're using v21 or newer, please check the approved answer. In case you're using appcompat-v7 < 21 API this would still help you.
You can fix this easily by copying the SwipeRefreshLayout (and its dependencies) into your project and change where the progress bar is displayed, adding a top margin.
To make it easy for everyone I released a library with the margin implemented. Take a look at this: https://github.com/Naroh091/SwipeRefreshLayoutOverlay
Hope it helps!
I want to show progress in a widget via a wheel-progressbar (not the horizontal one).
This is the progressbar in the xml layoutfile, that shows the content to the user:
<ProgressBar
android:id="#+id/progressBar1"
style="#android:style/Widget.ProgressBar.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateOnly="false"/>
In the appwidget_provider implementation I wanted to set the progressbar like this:
RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.sample_widget);
views.setProgressBar(R.id.progressBar1, 2, 1, false);
appWidgetManager.updateAppWidget(appWidgetId, views);
(sample_widget being the layout shown to the user)
Now, if progressBar1 is a horizontal progressBar, everything is displayed as you would expect.
But if I use any other style, like here, the progressbar is not displayed as soon as setProgressBar() sets indeterminate to false.
I presume that is because the wheel-progressbars are apparently designed to show only indeterminate behaviour, so the drawable for determinate behaviour does not exist, which
leads to the progressbar not being displayed.("If you will use the progress bar to show real progress, you must use the horizontal bar.", according to the documentation here)
My question is now how I would go about making a wheel-progressbar that is capable of
showing actual progress ? Furthermore I wanted to be able to customize the progressbar based
on user preferences or how much progress has currently been made ( maybe dynamically change the colour of the bar, make it brighter/darker, bolder/thinner, etc.).
I would really appreciate it if someone could point me in the right direction, because I assume I am pretty much overthinking it now.I believe there must be a simple way of doing it, I just did not find anything on the matter.
I know that I can make the ActionBar overlay using requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY)
and can toggle/show the status bar in my screen (by switching between FLAG_FULLSCREEN and FLAG_FORCE_NOT_FULLSCREEN).
This works great. However, I don't want my layout moving when I toggle the status bar.
I know that I can make the status bar "overlay" (albeit not transparently) the content using:
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
This works also great, except for the (expected) consequence that, when using an ActionBar, the ActionBar gets half cut off - half of it is under the status bar basically.
So I am wondering, is there a way to "move the ActionBar down" by the status bar's height in this case?
I know that in worse case, I can do this with a custom view that lives within the layout, but I rather not do this (want to benefit from the ActionBar).
thanks!
so apparently, you can do this in Jellybean. google includes two examples in the api docs. here is a link: http://developer.android.com/reference/android/view/View.html#setSystemUiVisibility%28int%29
summary:
use setSystemUiVisibility on a view to toggle visibility (on jellybean+).
It's not perfect, but this is how I have achieved what you want:
In onCreate:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
Then:
private void hideStatusBar() {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
}
private void showStatusBar() {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
I also have a style on the activity with the following:
<style name="readingStyle" parent="#style/Theme.Sherlock">
<item name="android:windowActionBarOverlay">true</item>
<item name="windowActionBarOverlay">true</item>
</style>
Your activity will shift down when the status bar shows, but I don't think it's too noticeable.
Why you dont make it in the Manifest
android:theme="#android:style/Theme.Black.NoTitleBar"
than you have no statusbar and more space for the Actionbar??
Put this method in onCreate() method activity:
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
refer: link