I'm using an Action Bar (a regular one, not sherlock) in my android app, and when the app opens I want to show a refreshing message in the action bar. This means I want to hide the menu items and title (similar to how the GMail app appears when it's refreshing).
What is the best approach for this? Is it using a contextual action bar?
Is it possible to show the refreshing animation just below the action bar, like in the GMail app (ie, the blue lines sliding over).
I know I can use a 3rd party pull-to-refresh, but I'd prefer not to use this (as I don't need the pull-to-refresh capability).
I'm targeting Jelly Bean and newer devices.
Thanks!
I want to hide the menu items and title (similar to how the GMail app
appears when it's refreshing).
This can be done by using WindowManager.addView(View, LayoutParams). Here's an example of displaying a message on top of the ActionBar that should give you a pretty solid idea about how to proceed.
The layout
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#android:color/white"
android:textSize="18sp" />
Implementation
/** The attribute depicting the size of the {#link ActionBar} */
private static final int[] ACTION_BAR_SIZE = new int[] {
android.R.attr.actionBarSize
};
/** The notification layout */
private TextView mMessage;
private void showLoadingMessage() {
// Remove any previous notifications
removeLoadingMessage();
// Initialize the layout
if (mMessage == null) {
final LayoutInflater inflater = getLayoutInflater();
mMessage = (TextView) inflater.inflate(R.layout.your_layout, null);
mMessage.setBackgroundColor(getResources().getColor(android.R.color.holo_blue_dark));
mMessage.setText("Loading...");
}
// Add the View to the Window
getWindowManager().addView(mMessage, getActionBarLayoutParams());
}
private void removeLoadingMessage() {
if (mMessage != null && mMessage.getWindowToken() != null) {
getWindowManager().removeViewImmediate(mMessage);
mMessage = null;
}
}
/**
* To use, #see {#link WindowManager#addView(View, LayoutParams)}
*
* #return The {#link WindowManager.LayoutParams} to assign to a
* {#link View} that can be placed on top of the {#link ActionBar}
*/
private WindowManager.LayoutParams getActionBarLayoutParams() {
// Retrieve the height of the status bar
final Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
final int statusBarHeight = rect.top;
// Retrieve the height of the ActionBar
final TypedArray actionBarSize = obtainStyledAttributes(ACTION_BAR_SIZE);
final int actionBarHeight = actionBarSize.getDimensionPixelSize(0, 0);
actionBarSize.recycle();
// Create the LayoutParams for the View
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, actionBarHeight,
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP;
params.x = 0;
params.y = statusBarHeight;
return params;
}
Results
Conclusion
This implementation is very similar to Gmail and other apps, minus the pull-to-refresh pattern.
When you call showLoadingMessage, post a Runnable or use a View.OnClickListener. You don't want to call WindowManager.addView too early or you'll throw a WindowManager.BadTokenException. Also, it's important to call removeLoadingMessage in Activity.onDestroy, otherwise you run the risk of leaking the View you add to the Window.
Related
I'm creating an AlertDialog with customized view and window background. Setting a ColorDrawable works as expected, but setting a BitmapDrawable from resources makes the dialog appear right at the top of the screen (instead of centered). (Note: I'm talking of the background behind the dialog (normally a transparent grey, not the dialog's background itself!)
Dialog background (#drawable/dialog_bg):
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#color/white" />
<corners android:radius="10dp" />
</shape>
Dialog layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/dialog_bg"
android:orientation="vertical">
<!-- dialog contents -->
</LinearLayout>
Code to show dialog with ColorDrawable: -> works
private void showDialog() {
final AlertDialog dialog;
#SuppressLint("InflateParams") final ViewGroup dialogView = (ViewGroup) activity.getLayoutInflater().inflate(R.layout.my_dialog, null);
dialog = new AlertDialog.Builder(activity).setView(dialogView).create();
// this works:
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.show();
}
Code to show dialog with BitmapDrawable from resources (loading a simple PNG): -> removes centering
private void showDialog() {
final AlertDialog dialog;
#SuppressLint("InflateParams") final ViewGroup dialogView = (ViewGroup) activity.getLayoutInflater().inflate(R.layout.my_dialog, null);
dialog = new AlertDialog.Builder(activity).setView(dialogView).create();
// this sets the background, but un-centers the dialog:
BitmapDrawable drawable = (BitmapDrawable) ResourcesCompat.getDrawable(activity.getResources(), R.drawable.my_bg, null);
dialog.getWindow().setBackgroundDrawable(drawable);
dialog.show();
}
Setting a ColorDrawable works as expected: The background behind the Dialog is colored and the dialog is still centered on screen.
Setting a BitmapDrawable does not work: The background is set but the dialog is moved to the top of the screen.
Things that also didn't work:
loading the drawable with ContextCompat.getDrawable() (which is the same as ResourcesCompat.getDrawable() with the current theme instead of null)
using DisplayMetrics and dialog.getWindow().getAttributes().y (and .x respectively) to calculate margins myself: (height - y) / 2 -> just returns the "normal" dialog margin
setting the gravity to CENTER on either dialog.getWindow().setGravity() or dialog.getWindow().getAttributes().gravity -> this just doesn't change anything
setting the gravity to FILL on either dialog.getWindow().setGravity() or dialog.getWindow().getAttributes().gravity -> this removes dialog margins, but still at the top (even further at the top and left, as margins are removed)
So, does anybody know how to set a background from PNG behind the dialog and keeping its centering on the screen?
We had WindowManger for Dialog to Specify custom window attributes:
The layout params you give here should generally be from values previously retrieved with {#link #getAttributes()}; you probably do not want to blindly create and apply your own, since this will blow away any values set by the framework that you are not interested in.
Just add these property according to your requirement :
/**
* Retrieve the current window attributes associated with this panel.
*
* #return WindowManager.LayoutParams Either the existing window
* attributes object, or a freshly created one if there is none.
*/
WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
params.gravity = Gravity.TOP;
/**
* Set the width and height layout parameters of the window. The default
* for both of these is MATCH_PARENT; you can change them to WRAP_CONTENT
* or an absolute value to make a window that is not full-screen.
*
* #param width The desired layout width of the window.
* #param height The desired layout height of the window.
*
* #see ViewGroup.LayoutParams#height
* #see ViewGroup.LayoutParams#width
*/
dialog.getWindow().setLayout((int) (getScreenWidth(activity)), ViewGroup.LayoutParams.MATCH_PARENT);
dialog.getWindow().setBackgroundDrawableResource(R.drawable.message_email_selected);
/**
* Specify custom window attributes. <strong>PLEASE NOTE:</strong> the
* layout params you give here should generally be from values previously
* retrieved with {#link #getAttributes()}; you probably do not want to
* blindly create and apply your own, since this will blow away any values
* set by the framework that you are not interested in.
*
* #param a The new window attributes, which will completely override any
* current values.
*/
dialog.getWindow().setAttributes(params);
As Example :
final Dialog dialog = new Dialog(getActivity());
WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog_view);
// dialog.getWindow().setBackgroundDrawable(null);
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
dialog.getWindow().setLayout(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
dialog.getWindow().setAttributes(params);
dialog.show();
//Access dialog views
TextView txt_cancel = (TextView) dialog.findViewById(R.id.txt_cancel);
So I just solved this issue, although I have to admit it's a bit hacky.
First, I disabled fading behind the dialog
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
and manually "faded" the activity by adding a view to it, overlaying the activity with semi-transparent black:
final ViewGroup dimBackgroundView = new FrameLayout(activity);
float dimAlpha = 0.5f;
dimBackgroundView.setBackgroundColor(Color.BLACK);
dimBackgroundView.setAlpha(dimAlpha);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
activityLayout.addView(dimBackgroundView, params);
This also requires me to manually darken the statusbar on supporting devices (SDK 21+):
final int statusBarColor;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
statusBarColor = activity.getWindow().getStatusBarColor();
activity.getWindow().setStatusBarColor(
Color.rgb((int) (Color.red(statusBarColor) + 255 * dimAlpha),
(int) (Color.green(statusBarColor) + 255 * dimAlpha),
(int) (Color.blue(statusBarColor) + 255 * dimAlpha)));
} else {
statusBarColor = Color.BLACK;
}
Afterwards, I added the intended background to the activity (dialogBgView on top of the semi-transparent black view) and went on adding the dialog as normal.
Since I now added all these views to the activity, I need to remove them on dialog dismissal:
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialogInterface) {
// remove dim
activityLayout.removeView(dimBackgroundView);
// restore original statusbar color
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.getWindow().setStatusBarColor(statusBarColor);
}
// remove background image
activityLayout.removeView(dialogBgView);
}
});
It works, but it's really not a nice solution. So if anyone discovers a better way, please feel free to post it here.
I have a custom view that adds a variable filter (light effects, etc) over the entire screen. This runs as a service when the main activity is closed, so the user can change apps etc with the filters running.
The code is effectively:
EffectsView view = new EffectsView();
WindowManager windowManager = (WindowManager) service.getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(view, new Params());
private class Params extends WindowManager.LayoutParams
{
public Params()
{
super();
this.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
this.width = WindowManager.LayoutParams.MATCH_PARENT;
this.height = WindowManager.LayoutParams.MATCH_PARENT;
this.flags = 280;
this.format = PixelFormat.TRANSPARENT;
}
}
I'm currently using the flags = 280 parameter as it covers the notification bar, but it doesn't cover the soft nav buttons. This is most noticeable in the App Switcher.
I have tried the using the flag WindowManager.LayoutParams.FLAG_FULLSCREEN, but that doesn't cover the nav buttons or the notification bar.
Is there a way to get the view to cover the nav bar area?
Note: my supported SDK versions are 17+
I am trying to overlap system bottom navigation bar using window manager but i can`t do it. I am Using bellow code.I have set gravity to bottom, therefore it show view layer in bottom of my activity view not not overlapping bottom navigation bar.
public void onCreate(Bundle savedInstancestate)
{
super.onCreate(savedInstancestate);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);7
manager = ((WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
localLayoutParams = new WindowManager.LayoutParams();
localLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
localLayoutParams.gravity = Gravity.BOTTOM;
localLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
// this is to enable the notification to recieve touch events
//WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN |
//WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
// Draws over navigation bar
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
//localLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
localLayoutParams.height = (int) (50 * getResources().getDisplayMetrics().scaledDensity);
localLayoutParams.format = PixelFormat.TRANSPARENT;
view = new customView(this);
manager.addView(view, localLayoutParams);
setContentView(R.layout.Imges);
}
public class customView extends ViewGroup {
public customView(Context context) {
super(context);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.v("customView", "------Intercepted-------");
return true;
}
}
Using this code i can't overlap navigation,Its shows new custom view in bottom of my activity view but can not overlap navigation bar with custom view.
any one can help me on this, to overlap navigation bar with custom view.?
There are actually some solutions.You cannot overlap in the means of making it totally disappear since there are devices in market without the hardware buttons. However you can elegantly arrange your layout accordingly.
For example,
Add this to your styles.xml (v21 )in a values dir:
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
or if it does not work,
boolean hasMenuKey = ViewConfiguration.get(getContext()).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
if(!hasMenuKey && !hasBackKey) {
// Do whatever you need to do, this device has a navigation bar
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
);
params.setMargins(0, 0, 0, 75);
entrancelayout.setLayoutParams(params);
entrancelayout.requestLayout();
}
I had a FrameLayout, you can use it for whatever layout you have. SetMargins adds margin to buttom in example. It assumes System Bar is there if there are no hardware back and menu buttons.
Is there a way on android to keep the status bar while disabling all interaction you can do with it, like pulling it down?
I want to keep the information this bar gives, but I don't want users to interact with it.
This is the method that I like to use. You can unwrap it from the method and place it inside a base Activity instead. iirc, I got this from StackOverflow as well, but I didn't make a note of it so I'm not sure where the original post is.
What it basically does is place a transparent overlay over the top bar that intercepts all touch events. It's worked fine for me thus far, see how it works for you.
You MAY need to put this line in the AndroidManifest:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
I have it in my project, but I can't remember if it's because of this or something else. If you get a permission error, add that in.
WindowManager manager;
CustomViewGroup lockView;
public void lock(Activity activity) {
//lock top notification bar
manager = ((WindowManager) activity.getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE));
WindowManager.LayoutParams topBlockParams = new WindowManager.LayoutParams();
topBlockParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
topBlockParams.gravity = Gravity.TOP;
topBlockParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
// this is to enable the notification to recieve touch events
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
// Draws over status bar
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
topBlockParams.width = WindowManager.LayoutParams.MATCH_PARENT;
topBlockParams.height = (int) (50 * activity.getResources()
.getDisplayMetrics().scaledDensity);
topBlockParams.format = PixelFormat.TRANSPARENT;
lockView = new CustomViewGroup(activity);
manager.addView(lockView, topBlockParams);
}
and CustomViewGroup is
private class CustomViewGroup extends ViewGroup {
Context context;
public CustomViewGroup(Context context) {
super(context);
this.context = context;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i("StatusBarBlocker", "intercepted by "+ this.toString());
return true;
}
}
Also! You also have to remove this view when your activity ends, because I think it will continue to block the screen even after you kill the application. Always, always ALWAYS call this onPause and onDestroy.
if (lockView!=null) {
if (lockView.isShown()) {
//unlock top
manager.removeView(lockView);
}
}
I am currently developing an application in which I use a heavily modified Split Action Bar. Here is a link to the app's current state:
You'll notice a transparent action bar up top, with a custom view inflated into it, with a hacked together split action bar on bottom. The bottom view is actually a single action item with a custom view inflated into it and showAlways=true.
Currently I only support SDK v15+ and I don't really plan on changing that, but with the Lollipop AppCompat library that just released, I decided to implement it, so I could get some of that awesomeness in my app.
I've changed my theme to Theme.AppCompat.Light, and my MainActivity now extends ActionBarActivity instead of Activity.
All references to getActionBar have now been switched to getSupportActionBar, and with only those changes, this is what my activity now looks like:
You'll notice I got a UI dump from the Device Monitor, and it's shoving the bottom action bar into a weird space and calling that the action bar, and getting rid of my top custom view.
Here is my code for setting up my action bar:
public void initializeActionBar(){
View customNav = LayoutInflater.from(this).inflate(R.layout.action_bar_top, null);
actionBar = getSupportActionBar();
actionBar.setBackgroundDrawable(getResources().getDrawable(R.drawable.transparent_fifty_percent));
final PopupWindow window = addPopupWindow();
actionBarOptions = (ImageView)customNav.findViewById(R.id.options);
actionBarOptions.setVisibility(View.GONE);
actionBarOptions.setImageDrawable(app.svgToBitmapDrawable(getResources(), R.raw.vertical_ellipsis, app.scaleByDensity(48)));
actionBarOptions.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
window.showAsDropDown(actionBarOptions, 0, 0);
}
});
TextView title = (TextView) customNav.findViewById(R.id.screen_title);
Typeface font1 = Typeface.createFromAsset(getAssets(), "Merriweather-Italic.ttf");
title.setText("Parsley");
title.setTypeface(font1);
actionBar.setCustomView(customNav);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayUseLogoEnabled(false);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
new MenuInflater(this).inflate(R.menu.test, menu);
LinearLayout fullMenu = (LinearLayout) menu.findItem(R.id.full_menu).getActionView();
ViewGroup.LayoutParams params;
icon1 = (ImageView) fullMenu.findViewById(R.id.action_item1);
params = icon1.getLayoutParams();
params.width = getResources().getDisplayMetrics().widthPixels / 4;
params.height = (int) (48 * getResources().getDisplayMetrics().density);
icon1.setImageDrawable(app.svgToBitmapDrawable(getResources(), R.raw.shopping_list_icon, app.scaleByDensity(32)));
icon2 = (ImageView) fullMenu.findViewById(R.id.action_item2);
icon3 = (ImageView) fullMenu.findViewById(R.id.action_item3);
icon4 = (ImageView) fullMenu.findViewById(R.id.action_item4);
icon2.setImageDrawable(app.svgToBitmapDrawable(getResources(), R.raw.recipe_box_icon, app.scaleByDensity(32)));
icon3.setImageDrawable(app.svgToBitmapDrawable(getResources(), R.raw.icon_search, app.scaleByDensity(32)));
icon4.setImageDrawable(app.svgToBitmapDrawable(getResources(), R.raw.icon_add, app.scaleByDensity(32)));
params = icon2.getLayoutParams();
params.width = getResources().getDisplayMetrics().widthPixels / 4;
params.height = (int) (48 * getResources().getDisplayMetrics().density);
params = icon3.getLayoutParams();
params.width = getResources().getDisplayMetrics().widthPixels / 4;
params.height = (int) (48 * getResources().getDisplayMetrics().density);
params = icon4.getLayoutParams();
params.width = getResources().getDisplayMetrics().widthPixels / 4;
params.height = (int) (48 * getResources().getDisplayMetrics().density);
if (!firstLoad) {
setBottomActionBarActive();
setActiveTab(0);
}
optionsLoaded = true;
return true;
}
initializeActionBar() is called from onCreate in my activity. Any ideas what I'm doing wrong?
Toolbar should be used. In your case it's one toolbar at the top, and one at the bottom. Check android team blog, they have nice integration guide.
If you just want your bottom action bar back, you can simply change back to appcompat v7:20 ,and it works for me. The problem is split action bar is no longer being supported in appcomat v7:21.
While user482277's solution may work for instances with a more traditional split action bar, utilizing action items, navigation drawer, etc, it didn't quite work for me. What I ended up doing was building a pair of custom (compound really) views to emulate both the top and bottom action bar. I found this situation to work much better, especially with backwards compatibility. I don't have to worry about earlier versions supporting action bar, because at the end of the day, it's just a pair of classes that extend LinearLayout. In addition, I don't have to worry about different screen sizes (particularly tablets) not supporting the split version.