How to add Action Bar from support library into PreferenceActivity? - android

Action Bar compatibility has been added into support library, revision 18. It now has ActionBarActivity class for creating activities with Action Bar on older versions of Android.
Is there any way to add Action Bar from support library into PreferenceActivity?
Previously I used ActionBarSherlock and it has SherlockPreferenceActivity.

EDIT: In appcompat-v7 22.1.0 Google added the AppCompatDelegate abstract class as a delegate you can use to extend AppCompat's support to any activity.
Use it like this:
...
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
...
public class SettingsActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
#Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(#Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
#Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
#Override
public void setContentView(#LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
#Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
#Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
#Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
#Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
#Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
#Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
#Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}
No more hacking. Code taken from AppCompatPreferenceActivity.java.

There is currently no way to achieve with AppCompat. I've opened a bug internally.

I have managed to create a workaround similar to what the Google Play Store uses. Link to Original Answer
Please find the GitHub Repo: Here
Very Similar to your own code but added xml to allow for set title:
Continuing to use PreferenceActivity:
settings_toolbar.xml :
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/toolbar"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:navigationContentDescription="#string/abc_action_bar_up_description"
android:background="?attr/colorPrimary"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="#string/action_settings"
/>
SettingsActivity.java :
public class SettingsActivity extends PreferenceActivity {
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
Toolbar bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
bar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
}
}
Result :
UPDATE (Gingerbread Compatibility) :
As pointed out here, Gingerbread Devices are returning NullPointerException on this line:
LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
FIX:
SettingsActivity.java :
public class SettingsActivity extends PreferenceActivity {
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
Toolbar bar;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
} else {
ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
ListView content = (ListView) root.getChildAt(0);
root.removeAllViews();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
int height;
TypedValue tv = new TypedValue();
if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}else{
height = bar.getHeight();
}
content.setPadding(0, height, 0, 0);
root.addView(content);
root.addView(bar);
}
bar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
}
}
Any issues with the above let me know!
UPDATE 2: TINTING WORKAROUND
As pointed out in many dev notes PreferenceActivity does not support tinting of elements, however by utilising a few internal classes you CAN achieve this. That is until these classes are removed. (Works using appCompat support-v7 v21.0.3).
Add the following imports:
import android.support.v7.internal.widget.TintCheckBox;
import android.support.v7.internal.widget.TintCheckedTextView;
import android.support.v7.internal.widget.TintEditText;
import android.support.v7.internal.widget.TintRadioButton;
import android.support.v7.internal.widget.TintSpinner;
Then override the onCreateView method:
#Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
// Allow super to try and create a view first
final View result = super.onCreateView(name, context, attrs);
if (result != null) {
return result;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// If we're running pre-L, we need to 'inject' our tint aware Views in place of the
// standard framework versions
switch (name) {
case "EditText":
return new TintEditText(this, attrs);
case "Spinner":
return new TintSpinner(this, attrs);
case "CheckBox":
return new TintCheckBox(this, attrs);
case "RadioButton":
return new TintRadioButton(this, attrs);
case "CheckedTextView":
return new TintCheckedTextView(this, attrs);
}
}
return null;
}
Result:
AppCompat 22.1
AppCompat 22.1 introduced new tinted elements, meaning that there is no longer a need to utilise the internal classes to achieve the same effect as the last update. Instead follow this (still overriding onCreateView):
#Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
// Allow super to try and create a view first
final View result = super.onCreateView(name, context, attrs);
if (result != null) {
return result;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// If we're running pre-L, we need to 'inject' our tint aware Views in place of the
// standard framework versions
switch (name) {
case "EditText":
return new AppCompatEditText(this, attrs);
case "Spinner":
return new AppCompatSpinner(this, attrs);
case "CheckBox":
return new AppCompatCheckBox(this, attrs);
case "RadioButton":
return new AppCompatRadioButton(this, attrs);
case "CheckedTextView":
return new AppCompatCheckedTextView(this, attrs);
}
}
return null;
}
NESTED PREFERENCE SCREENS
A lot of people are experiencing issues with including the Toolbar in nested <PreferenceScreen />s however, I have found a solution!! - After a lot of trial and error!
Add the following to your SettingsActivity:
#SuppressWarnings("deprecation")
#Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
super.onPreferenceTreeClick(preferenceScreen, preference);
// If the user has clicked on a preference screen, set up the screen
if (preference instanceof PreferenceScreen) {
setUpNestedScreen((PreferenceScreen) preference);
}
return false;
}
public void setUpNestedScreen(PreferenceScreen preferenceScreen) {
final Dialog dialog = preferenceScreen.getDialog();
Toolbar bar;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
LinearLayout root = (LinearLayout) dialog.findViewById(android.R.id.list).getParent();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
} else {
ViewGroup root = (ViewGroup) dialog.findViewById(android.R.id.content);
ListView content = (ListView) root.getChildAt(0);
root.removeAllViews();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
int height;
TypedValue tv = new TypedValue();
if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}else{
height = bar.getHeight();
}
content.setPadding(0, height, 0, 0);
root.addView(content);
root.addView(bar);
}
bar.setTitle(preferenceScreen.getTitle());
bar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
}
});
}
The reason that PreferenceScreen's are such a pain is because they are based as a wrapper dialog, so we need to capture the dialog layout to add the toolbar to it.
Toolbar Shadow
By design importing the Toolbar does not allow for elevation and shadowing in pre-v21 devices, so if you would like to have elevation on your Toolbar you need to wrap it in a AppBarLayout:
`settings_toolbar.xml :
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
.../>
</android.support.design.widget.AppBarLayout>
Not forgetting to add the add the Design Support library as a dependency in build.gradle file:
compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:design:22.2.0'
Android 6.0
I have investigated the reported overlapping issue and I cannot reproduce the issue.
The full code in use as above produces the following:
If I am missing something please let me know via this repo and I will investigate.

Found a PreferenceFragment implementation based on support-v4 Fragment:
https://github.com/kolavar/android-support-v4-preferencefragment
Edit: I just tested it and its working great!

Integrating PreferenceActivity with ABC is not possible, at least for me. I tried the two possibilities I could find but none worked:
Option 1:
ActionBarPreferenceActivity extends PreferenceActivity. When you do this you get restricted by ActionBarActivityDelegate.createDelegate(ActionBarActivity activity). Also you need to implement ActionBar.Callbacks which is not accessible
Option 2:
ActionBarPreferenceActivity extends ActionBarActivity. This approach requires rewriting a whole new PreferenceActivity, PreferenceManager and may be PreferenceFragment which means you need access to hidden classes like com.android.internal.util.XmlUtils
The solution to this can only come from Google devs implementing an ActionBarWrapper that can be added to any activity.
If you really need a preference activity, my advice for now is ActionBarSherlock.
However, I managed to implement it here.

Problem Background:
The OP wants to know how can we put MenuItems in the ActionBar of PreferenceActivity for pre-Honeycomb because Android's support library has a bug which doesn't allow this to happen.
My Solution:
I've found a much cleaner way, than already proposed, to achieve the target (and found it in the Android Docs):
android:parentActivityName
The class name of the logical parent of the
activity. The name here must match the class name given to the
corresponding element's android:name attribute.
The system reads this attribute to determine which activity should be
started when the use presses the Up button in the action bar. The
system can also use this information to synthesize a back stack of
activities with TaskStackBuilder.
To support API levels 4 - 16, you can also declare the parent activity
with a element that specifies a value for
"android.support.PARENT_ACTIVITY". For example:
<activity
android:name="com.example.app.ChildActivity"
android:label="#string/title_child_activity"
android:parentActivityName="com.example.myfirstapp.MainActivity" >
<!-- Parent activity meta-data to support API level 4+ -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.app.MainActivity" />
</activity>
Now do what you would normally do in your onOptionsItemSelected(). Since it's a part of Android Docs, it has no side-affects.
Happy coding. :)
Update:
This solution no longer works if you're targeting Lollipop. If you're using AppCompat, this answer is what you should be looking for.

I was able to get android.app.Actionbar by using getActionBar(). It returned a null value at first... then I went to the manifest and changed the theme to:
android:theme="#style/Theme.AppCompat"
Then I was able to have the actionbar again. I'm assuming this will only work for certain build levels. So you might want to do a check for the build number or check if the value returned is null.
It'll be fine for me because the app I'm working on is for ICS/4.0+.

Now the official answer for this problem has been released. It is the v7/v14 Preference Support library.
See How to use the v7/v14 Preference Support library? for the discussion how to use it.

Related

Change theme dynamically for multiple view - Android

I am trying change theme when the app device is offline. But to achieve changing the background color is not gonna help and i needed to change the whole view and the text colors. But for that getting all the view with FindViewById is not an effective method to achieve that as I got lots of views to the Activity and as i tried using Themes
<item name="android:textColor">#android:color/black</item>
<item name="android:windowBackground">#android:color/white</item>
But as it shows only one colored TextView I cant use this method to activity as it has multiple colors and changing theme has to be done before you create the activity.
Please provide a solution which supports changing theme with multiple colored View
public class Utils {
private static int sTheme;
public final static int THEME_MATERIAL_LIGHT = 0;
public final static int THEME_YOUR_CUSTOM_THEME = 1;
public static void changeToTheme(Activity activity, int theme) {
sTheme = theme;
activity.finish();
activity.startActivity(new Intent(activity, activity.getClass()));
activity.overridePendingTransition(android.R.anim.fade_in,
android.R.anim.fade_out);
}
public static void onActivityCreateSetTheme(Activity activity) {
switch (sTheme) {
default:
case THEME_MATERIAL_LIGHT:
activity.setTheme(R.style.Theme_Material_Light);
break;
case THEME_YOUR_CUSTOM_THEME:
activity.setTheme(R.style.Theme_YOUR_CUSTOM_THEME);
break;
}
}
}
In Your Activity:
#Override // Any Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// MUST BE SET BEFORE setContentView
Utils.onActivityCreateSetTheme(this);
// AFTER SETTING THEME
setContentView(R.layout.activity_theme);
}

Hide navigation drawer when user presses back button

I've followed Google's official developer tutorials here to create a navigation drawer.
At the moment, everything works fine, except for when the user uses the native back button Android provides at the bottom of the screen (along with the home and recent app buttons). If the user navigates back using this native back button, the navigation drawer will still be open. If the user instead navigates back using the ActionBar, the navigation drawer will be closed like I want it to be.
My code is nearly identical to the official tutorials, except for how I handle the user selecting an item on the drawer:
mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView parent, View view, int position, long id)
{
switch(position)
{
case 0:
{
Intent intent = new Intent(MainActivity.this, NextActivity.class);
startActivity(intent);
}
}
}
});
How can I have the navigation drawer be closed when the user navigates back using the native back button? Any advice appreciated. Thanks!
You have to override onBackPressed(). From the docs :
Called when the activity has detected the user's press of the back
key. The default implementation simply finishes the current activity,
but you can override this to do whatever you want.
So you can have code like this :
#Override
public void onBackPressed() {
if (this.drawerLayout.isDrawerOpen(GravityCompat.START)) {
this.drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
If is open this method closes it, else falls back to the default behavior.
You need to override onBackPressed() in your activity and check for the condition where the navigation drawer is open. If it is open, then close it, else do a normal back pressed method. Here is some code mixed with some pseudocode to help you:
#Override
public void onBackPressed(){
if(drawer.isDrawerOpen()){ //replace this with actual function which returns if the drawer is open
drawer.close(); // replace this with actual function which closes drawer
}
else{
super.onBackPressed();
}
}
To replace the pseudocode look in the documentation for the drawer. I know both those methods exist.
Here is an alternative solution to your problem.
#Override
public void onBackPressed(){
if(drawerLayout.isDrawerOpen(navigationView)){
drawerLayout.closeDrawer(navigationView);
}else {
finish();
}
}
UPDATE:
As of support library 24.0.0 this is possible without any workarounds. Two new openDrawer and closeDrawer methods have been added to DrawerLayout that allow the drawer to be opened or closed with no animation.
You can now use openDrawer(drawerView, false) and closeDrawer(drawerView, false) to open and close the drawer with no delay.
If you call startActivity() without calling closeDrawer(), the drawer will be left open in that instance of the activity when you navigate back to it using the back button. Calling closeDrawer() when you call startActivity() has several issues, ranging from choppy animation to a long perceptual delay, depending on which workaround you use. So I agree the best approach is to just call startActivity() and then close the drawer upon return.
To make this work nicely, you need a way to close the drawer without a close animation when navigating back to the activity with the back button. (A relatively wasteful workaround would be to just force the activity to recreate() when navigating back, but it's possible to solve this without doing that.)
You also need to make sure you only close the drawer if you're returning after navigating, and not after an orientation change, but that's easy.
Details
(You can skip past this explanation if you just want to see the code.)
Although calling closeDrawer() from onCreate() will make the drawer start out closed without any animation, the same is not true from onResume(). Calling closeDrawer() from onResume() will close the drawer with an animation that is momentarily visible to the user. DrawerLayout doesn't provide any method to close the drawer without that animation, but it's possible to extend it in order to add one.
Closing the drawer actually just slides it off the screen, so you can effectively skip the animation by moving the drawer directly to its "closed" position. The translation direction will vary according to the gravity (whether it's a left or right drawer), and the exact position depends on the size of the drawer once it's laid out with all its children.
However, simply moving it isn't quite enough, as DrawerLayout keeps some internal state in extended LayoutParams that it uses to know whether the drawer is open. If you just move the drawer off screen, it won't know that it's closed, and that will cause other problems. (For example, the drawer will reappear on the next orientation change.)
Since you're compiling the support library into your app, you can create a class in the android.support.v4.widget package to gain access to its default (package-private) parts, or extend DrawerLayout without copying over any of the other classes it needs. This will also reduce the burden of updating your code with future changes to the support library. (It's always best to insulate your code from implementation details as much as possible.) You can use moveDrawerToOffset() to move the drawer, and set the LayoutParams so it will know that the drawer is closed.
Code
This is the code that'll skip the animation:
// move drawer directly to the closed position
moveDrawerToOffset(drawerView, 0.f);
// set internal state so DrawerLayout knows it's closed
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
invalidate();
Note: if you just call moveDrawerToOffset() without changing the LayoutParams, the drawer will move back to its open position on the next orientation change.
Option 1 (use existing DrawerLayout)
This approach adds a utility class to the support.v4 package to gain access to the package-private parts we need inside DrawerLayout.
Place this class into /src/android/support/v4/widget/:
package android.support.v4.widget;
import android.support.annotation.IntDef;
import android.support.v4.view.GravityCompat;
import android.view.Gravity;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class Support4Widget {
/** #hide */
#IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
#Retention(RetentionPolicy.SOURCE)
private #interface EdgeGravity {}
public static void setDrawerClosed(DrawerLayout drawerLayout, #EdgeGravity int gravity) {
final View drawerView = drawerLayout.findDrawerWithGravity(gravity);
if (drawerView == null) {
throw new IllegalArgumentException("No drawer view found with gravity " +
DrawerLayout.gravityToString(gravity));
}
// move drawer directly to the closed position
drawerLayout.moveDrawerToOffset(drawerView, 0.f);
// set internal state so DrawerLayout knows it's closed
final DrawerLayout.LayoutParams lp = (DrawerLayout.LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
drawerLayout.invalidate();
}
}
Set a boolean in your activity when you navigate away, indicating the drawer should be closed:
public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER";
private boolean mCloseNavDrawer;
#Override
public void onCreate(Bundle savedInstanceState) {
// ...
if (savedInstanceState != null) {
mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER);
}
}
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
// ...
startActivity(intent);
mCloseNavDrawer = true;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer);
super.onSaveInstanceState(savedInstanceState);
}
...and use the setDrawerClosed() method to shut the drawer in onResume() with no animation:
#Overrid6e
protected void onResume() {
super.onResume();
if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
Support4Widget.setDrawerClosed(mDrawerLayout, GravityCompat.START);
mCloseNavDrawer = false;
}
}
Option 2 (extend from DrawerLayout)
This approach extends DrawerLayout to add a setDrawerClosed() method.
Place this class into /src/android/support/v4/widget/:
package android.support.v4.widget;
import android.content.Context;
import android.support.annotation.IntDef;
import android.support.v4.view.GravityCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class CustomDrawerLayout extends DrawerLayout {
/** #hide */
#IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
#Retention(RetentionPolicy.SOURCE)
private #interface EdgeGravity {}
public CustomDrawerLayout(Context context) {
super(context);
}
public CustomDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setDrawerClosed(View drawerView) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
// move drawer directly to the closed position
moveDrawerToOffset(drawerView, 0.f);
// set internal state so DrawerLayout knows it's closed
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
invalidate();
}
public void setDrawerClosed(#EdgeGravity int gravity) {
final View drawerView = findDrawerWithGravity(gravity);
if (drawerView == null) {
throw new IllegalArgumentException("No drawer view found with gravity " +
gravityToString(gravity));
}
// move drawer directly to the closed position
moveDrawerToOffset(drawerView, 0.f);
// set internal state so DrawerLayout knows it's closed
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
invalidate();
}
}
Use CustomDrawerLayout instead of DrawerLayout in your activity layouts:
<android.support.v4.widget.CustomDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
...and set a boolean in your activity when you navigate away, indicating the drawer should be closed:
public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER";
private boolean mCloseNavDrawer;
#Override
public void onCreate(Bundle savedInstanceState) {
// ...
if (savedInstanceState != null) {
mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER);
}
}
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
// ...
startActivity(intent);
mCloseNavDrawer = true;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer);
super.onSaveInstanceState(savedInstanceState);
}
...and use the setDrawerClosed() method to shut the drawer in onResume() with no animation:
#Overrid6e
protected void onResume() {
super.onResume();
if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.setDrawerClosed(GravityCompat.START);
mCloseNavDrawer = false;
}
}
Using an implementation of the answer provided by #James Cross worked, but the animation to close the drawer was undesirable and unfixable without much hassle, example.
#Override
public void onResume()
{
super.onResume();
mDrawerLayout.closeDrawers();
}
A work-around is to restart the activity when the device back button is pressed. It does not seem ideal to me, but it works. Overriding onBackPressed(), as suggested by #mt0s and #Qazi Ahmed and passing an extra to determine the calling activity:
mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView parent, View view, int position, long id)
{
switch(position)
{
case 0:
{
Intent intent = new Intent(MainActivity.this, NextActivity.class);
//pass int extra to determine calling activity
intent.putExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY);
startActivity(intent);
}
}
}
});
In NextActivity.class, check for the calling activity:
#Override
public void onBackPressed()
{
int callingActivity = getIntent().getIntExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY);
switch(callingActivity)
{
case CallingActivityInterface.MAIN_ACTIVITY:
{
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}
...
}
}
This way the drawer is closed with no animation when I return to MainActivity regardless of whether I use the up button or the back button. There are probably better ways to do this. My app is relatively simple at the moment and this works, but I await a more effective method if anyone has one.
Why the hassle? Simply close the Drawer when clicking a drawer item. That's how it's done in the official Google Play app.
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
drawerLayout.closeDrawer(GravityCompat.START, false);
selectItem(position);
}
}
You will probably want to make sure the navigation draw is always closed when the activity is opened. Use this to do that:
#Override
public void onResume(){
mDrawerList.closeDrawer(Gravity.LEFT);
}
simple sample:
Drawer resultDrawer;
public void onBackPressed(){
if (this.resultDrawer.isDrawerOpen()) {
this.resultDrawer.closeDrawer();
} else {
super.onBackPressed();
}
}
With androidx.drawerlayout:drawerlayout:1.1.0 or higher, you can keep it simple using isOpen and close().
// YourActivity.kt
override fun onBackPressed() {
if (drawerLayout.isOpen) {
drawerLayout.close()
} else {
super.onBackPressed()
}
}
This how i did it:
#Override
public void onBackPressed() {
if(drawerLayout.isDrawerOpen(navigationView)){
drawerLayout.closeDrawer(Gravity.LEFT);
}else{
super.onBackPressed();
}
}
JETPACK COMPOSE
For someone that using jetpack compose.
use this code in your scaffold:
BackHandler(enabled = drawerState.isOpen) {
scope.launch { drawerState.close() }
}
complete version:
val scope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
Scaffold(
topBar = {},
bottomBar = {},
snackbarHost = {},
content = {
...
BackHandler(enabled = drawerState.isOpen) {
scope.launch { drawerState.close() }
}
},
...
)

My Fragment got overlapped by ActionBar Tabs when using SystemBarTint

Okay, a quick background : I've been developing a simple music player app and to enhance the user experience (especially on newer devices) I decided to implement a tinted StatusBar using the fabled SystemBarTint library.
Imported the .JAR, followed the usage directions, modified the application theme and voila! My app looks fancier than an 10-years old show bike. But wait, there's more!
As you can see, the ListView is now covered by the ActionBar and its tabs. I did my homework and found this workaround -- which works, sort of.
My ListView is no longer covered by the ActionBar but it's still covered by the tabs!
How do I sort this one out?
This is my BaseActivity onCreate method which activates the SystemBarTint library :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SystemBarTintManager tintManager = new SystemBarTintManager(this);
tintManager.setStatusBarTintEnabled(true);
tintManager.setTintColor(getResources().getColor(R.color.wowza_orange));
}
..and this is the workaround, supplied in my Fragment (which houses the ListView) :
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
musicList.setClipToPadding(false); // musicList is my ListView instance.
setInsets(getActivity(), (View) musicList);
}
public static void setInsets(Activity context, View view) {
SystemBarTintManager tintManager = new SystemBarTintManager(context);
SystemBarTintManager.SystemBarConfig config = tintManager.getConfig();
view.setPadding(0, config.getPixelInsetTop(true),
config.getPixelInsetRight(), config.getPixelInsetBottom());
}
Thanks in advance!
Its a little late, but I was facing the same problem. My solution is add this method:
private int getActionBarHeight() {
int actionBarHeight =0;
final TypedValue tv = new TypedValue();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
} else if (context.getTheme().resolveAttribute(R.attr.actionBarSize, tv, true))
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
return actionBarHeight;
}
and replace:
view.setPadding(0, config.getPixelInsetTop(true),
config.getPixelInsetRight(), config.getPixelInsetBottom());
with:
view.setPadding(0, config.getPixelInsetTop(true) + getActionBarHeight(),
config.getPixelInsetRight(), config.getPixelInsetBottom());

Can I edit the text of sign in button on Google?

I am integrating my application with google plus. I have installed google play services and signed in to my account. Also I could publish and plus one for what ever I want.
My problem
I can't change the text of the sign in button.
My code
<com.google.android.gms.common.SignInButton
android:id="#+id/share_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Share on Google+" />
What I have tried?
First, I tried adding this line to the xml
android:text="Share on Google+"
Secondly, I tried to set the text programmatically, however it didn't work.
Any help would be appreciated.
Edit
If it is not possible, is there any way so I can use the same google sign in button on another button?
Here is the technique that I used:
protected void setGooglePlusButtonText(SignInButton signInButton, String buttonText) {
// Find the TextView that is inside of the SignInButton and set its text
for (int i = 0; i < signInButton.getChildCount(); i++) {
View v = signInButton.getChildAt(i);
if (v instanceof TextView) {
TextView tv = (TextView) v;
tv.setText(buttonText);
return;
}
}
}
Here is the easiest way that I used:
TextView textView = (TextView) signInButton.getChildAt(0);
textView.setText("your_text_xyz");
Problem:
Other answers have mentioned a workaround. The underlying implementation of the button may change any time which would cause the code to break. I felt uncomfortable trying to use the hacks. For a clean solution, you would think that setting android:text on the com.google.android.gms.common.SignInButton in your layout file would do the trick. However it turns out that that attribute is not available for SignInButton.
Aim
Google's guidelines
From documentation, Google suggests creating a custom button as mentioned on Customizing the Sign-In Button [Archived now. See the archived page here]. It suggests using the branding guidelines as mentioned at Sign-In Branding Guidelines. This includes using the given custom icons and images in the button, setting specific text size, paddings and other do's and don'ts for the logo.
Clean Solution:
Doing as per Google's suggestion involves some custom work. I was willing to do that but wanted to create something reusable, so that others won't have to go through this again. That's why I wrote a quick small (4KB) library does that. Feel free to contribute to it for everyone's benefit if you find issues.
Step 1: Add the following to your app module level build.gradle file:
dependencies {
implementation 'com.shobhitpuri.custombuttons:google-signin:1.1.0'
}
Step 2: In your XML Layout, have the following:
<RelativeLayout
...
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.shobhitpuri.custombuttons.GoogleSignInButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="#string/google_sign_up"
app:isDarkTheme="true" />
</RelativeLayout>
Usage
android:text="{string}": As usual to set the text on the button.
app:isDarkTheme="{Boolean}": To switch between blue theme and white theme for the button. The library handles changing of text color and background color. It also handles the change of color on button press or button clicks.
Source:
GitHub: https://github.com/shobhitpuri/custom-google-signin-button
Blog Post: http://go.shobhitpuri.com/googlesignin
Hope it helps someone.
android:text will not work because of Google's Sign in button is a FrameLayout but not a Button.
Since text property is only meant for Views representing textual format but not for ViewGroups, your solution is not working.
The only way you can achieve is getting the TextView defined inside FrameLayout as explained by w.donahue.
You can use this class I wrote based on the answer of w.donahue that you can find in this page:
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.google.android.gms.common.SignInButton;
public class GoogleLoginButton extends FrameLayout implements View.OnClickListener{
private SignInButton signInButton;
private OnClickListener onClickListener;
public GoogleLoginButton(Context context) {
super(context);
init();
}
public GoogleLoginButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GoogleLoginButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
signInButton = new SignInButton(getContext());
signInButton.setSize(SignInButton.SIZE_STANDARD);
setGooglePlusButtonText(signInButton, "Test");
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
addView(signInButton, params);
}
protected void setGooglePlusButtonText(SignInButton signInButton, String buttonText) {
// Find the TextView that is inside of the SignInButton and set its text
for (int i = 0; i < signInButton.getChildCount(); i++) {
View v = signInButton.getChildAt(i);
if (v instanceof TextView) {
TextView tv = (TextView) v;
tv.setText(buttonText);
return;
}
}
}
#Override
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
if(this.signInButton != null) {
this.signInButton.setOnClickListener(this);
}
}
#Override
public void onClick(View v) {
if(this.onClickListener != null && v == this.signInButton) {
this.onClickListener.onClick(this);
}
}
}
For Beginners
Avoiding few crashes.
try {
((TextView) mGoogleSignOutBtn.getChildAt(0)).setText(R.string.sign_out);
} catch (ClassCastException | NullPointerException e) {
e.printStackTrace();
}
This worked for me:
XML file
<com.google.android.gms.common.SignInButton
android:id="#+id/google_login_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
/>
<Button
android:id="#+id/new_googlebtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="#color/white"
android:text="#string/google_login"
android:textColor="#color/black"
app:icon="#drawable/googleg_standard_color_18"
app:iconGravity="textStart"
app:iconPadding="10dp"
app:iconTint="#00100D0D"
app:iconTintMode="src_atop" />
UNMODIFIED MAIN ACTIVITY FILE
google_signInButton=findViewById(R.id.google_login_button);
google_signInButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(intent, SIGN_IN);
}
});
UPDATED Main Activity file
google_signInButton=findViewById(R.id.google_login_button);
new_googlebtn=findViewById(R.id.new_googlebtn);
new_googlebtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (v== new_googlebtn) {
google_signInButton.performClick();
}
Intent intent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(intent, SIGN_IN);
}
});
For kotlin you can do this.
val googleTextView: TextView = SignInButton.getChildAt(0) as TextView
googleTextView.text = "Sign In with Google"
SignInButton is reference to the button id you are using.
I encorage not to use those #w.donahue approach since it violates several principles as open/close principle. Best approach it's to customize your own sign in button. If you see documentation about Sign in Google plus button is just a FrameLayout with Textview. At this link https://developers.google.com/+/branding-guidelines#sign-in-button you have material to design the button.
public class GplusButton extends FrameLayout {
private final String logIn="log in with google +";
private final String logOut="log out";
TextView labelTV;
public GplusButton(Context context) {
super(context, null);
}
public GplusButton(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundResource(R.drawable.btn_g_plus_signin_normal);
addTextLabel();
}
public void addTextLabel() {
labelTV = new TextView(getContext());
setTextLogIn();
labelTV.setTextColor(Color.WHITE);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
addView(labelTV, params);
}
public void setTextLogIn(){
labelTV.setText(logIn);
}
public void setTextLogOut(){
labelTV.setText(logOut);
}
The only annoying thing is that even Google + mark with 9 patch extension the PNG they aren't, so you have to edit.

How to change the background color of the options menu?

I'm trying to change the default color for the options menu which is white: I want a black background for every item on the options menu.
I've tried some shoots like android:itemBackground="#000000" on the item element within the menu element but it didn't work.
How can I accomplish this?
After spending a considerable amount of time trying all the options, the only way I was able to get an app using AppCompat v7 to change the overflow menu background was using the itemBackground attribute:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
...
<item name="android:itemBackground">#color/overflow_background</item>
...
</style>
Tested from API 4.2 to 5.0.
This is clearly a problem that a lot of programmers have and to which Google has yet to provide a satisfactory, supported solution.
There are a lot of crossed intentions and misunderstandings floating around posts on this topic, so please read this whole answer before responding.
Below I include a more "refined" and well-commented version of the hack from other answers on this page, also incorporating ideas from these very closely related questions:
Change background color of android menu
How to change the background color of the options menu?
Android: customize application's menu (e.g background color)
http://www.macadamian.com/blog/post/android_-_theming_the_unthemable/
Android MenuItem Toggle Button
Is it possible to make the Android options menu background non-translucent?
http://www.codeproject.com/KB/android/AndroidMenusMyWay.aspx
Setting the menu background to be opaque
I tested this hack on 2.1 (simulator), 2.2 (2 real devices), and 2.3 (2 real devices). I don't have any 3.X tablets to test on yet but will post any needed changes here when/if I do. Given that 3.X tablets use Action Bars instead of Options Menus, as explained here:
http://developer.android.com/guide/topics/ui/menus.html#options-menu
this hack will almost certainly do nothing (no harm and no good) on 3.X tablets.
STATEMENT OF THE PROBLEM (read this before trigger-replying with a negative comment):
The Options menu has vastly different styles on different devices. Pure black with white text on some, pure white with black text on some. I and many other developers wish to control the background color of the Options menu cells as well as the color of the Options menu text.
Certain app developers only need to set the cell background color (not the text color), and they can do this in a cleaner manner using the android:panelFullBackground style described in another answer. However, there is currently no way to control the Options menu text color with styles, and so one can only use this method to change the background to another color that won't make the text "disappear."
We would love to do this with a documented, future-proof solution, but one is simply not available as of Android <= 2.3. So we have to use a solution that works in current versions and is designed to minimize the chances of crashing/breaking in future versions. We want a solution that fails gracefully back to the default behavior if it has to fail.
There are many legitimate reasons why one may need to control the look of Options menus (typically to match a visual style for the rest of the app) so I won't dwell on that.
There is a Google Android bug posted about this: please add your support by starring this bug (note Google discourages "me too" comments: just a star is enough):
http://code.google.com/p/android/issues/detail?id=4441
SUMMARY OF SOLUTIONS SO FAR:
Several posters have suggested a hack involving LayoutInflater.Factory. The suggested hack worked for Android <= 2.2 and failed for Android 2.3 because the hack made an undocumented assumption: that one could call LayoutInflater.getView() directly without currently being inside a call to LayoutInflater.inflate() on the same LayoutInflater instance. New code in Android 2.3 broke this assumption and led to a NullPointerException.
My slightly refined hack below does not rely on this assumption.
Furthermore, the hacks also rely on using an internal, undocumented class name "com.android.internal.view.menu.IconMenuItemView" as a string (not as a Java type). I do not see any way to avoid this and still accomplish the stated goal. However, it is possible to do the hack in a careful way that will fall back if "com.android.internal.view.menu.IconMenuItemView" does not appear on the current system.
Again, understand that this is a hack and by no means am I claiming this will work on all platforms. But we developers are not living in a fantasy academic world where everything has to be by the book: we have a problem to solve and we have to solve it as best we can. For example, it seems unlikely that "com.android.internal.view.menu.IconMenuItemView" will exist on 3.X tablets since they use Action Bars instead of Options Menus.
Finally, some developers have solved this problem by totally suppressing the Android Options Menu and writing their own menu class (see some of the links above). I haven't tried this, but if you have time to write your own View and figure out how to replace Android's view (I'm sure the devil's in the details here) then it might be a nice solution that doesn't require any undocumented hacks.
HACK:
Here is the code.
To use this code, call addOptionsMenuHackerInflaterFactory() ONCE from your activity onCreate() or your activity onCreateOptionsMenu(). It sets a default factory that will affect subsequent creation of any Options Menu. It does not affect Options Menus that have already been created (the previous hacks used a function name of setMenuBackground(), which is very misleading since the function doesn't set any menu properties before it returns).
#SuppressWarnings("rawtypes")
static Class IconMenuItemView_class = null;
#SuppressWarnings("rawtypes")
static Constructor IconMenuItemView_constructor = null;
// standard signature of constructor expected by inflater of all View classes
#SuppressWarnings("rawtypes")
private static final Class[] standard_inflater_constructor_signature =
new Class[] { Context.class, AttributeSet.class };
protected void addOptionsMenuHackerInflaterFactory()
{
final LayoutInflater infl = getLayoutInflater();
infl.setFactory(new Factory()
{
public View onCreateView(final String name,
final Context context,
final AttributeSet attrs)
{
if (!name.equalsIgnoreCase("com.android.internal.view.menu.IconMenuItemView"))
return null; // use normal inflater
View view = null;
// "com.android.internal.view.menu.IconMenuItemView"
// - is the name of an internal Java class
// - that exists in Android <= 3.2 and possibly beyond
// - that may or may not exist in other Android revs
// - is the class whose instance we want to modify to set background etc.
// - is the class we want to instantiate with the standard constructor:
// IconMenuItemView(context, attrs)
// - this is what the LayoutInflater does if we return null
// - unfortunately we cannot just call:
// infl.createView(name, null, attrs);
// here because on Android 3.2 (and possibly later):
// 1. createView() can only be called inside inflate(),
// because inflate() sets the context parameter ultimately
// passed to the IconMenuItemView constructor's first arg,
// storing it in a LayoutInflater instance variable.
// 2. we are inside inflate(),
// 3. BUT from a different instance of LayoutInflater (not infl)
// 4. there is no way to get access to the actual instance being used
// - so we must do what createView() would have done for us
//
if (IconMenuItemView_class == null)
{
try
{
IconMenuItemView_class = getClassLoader().loadClass(name);
}
catch (ClassNotFoundException e)
{
// this OS does not have IconMenuItemView - fail gracefully
return null; // hack failed: use normal inflater
}
}
if (IconMenuItemView_class == null)
return null; // hack failed: use normal inflater
if (IconMenuItemView_constructor == null)
{
try
{
IconMenuItemView_constructor =
IconMenuItemView_class.getConstructor(standard_inflater_constructor_signature);
}
catch (SecurityException e)
{
return null; // hack failed: use normal inflater
}
catch (NoSuchMethodException e)
{
return null; // hack failed: use normal inflater
}
}
if (IconMenuItemView_constructor == null)
return null; // hack failed: use normal inflater
try
{
Object[] args = new Object[] { context, attrs };
view = (View)(IconMenuItemView_constructor.newInstance(args));
}
catch (IllegalArgumentException e)
{
return null; // hack failed: use normal inflater
}
catch (InstantiationException e)
{
return null; // hack failed: use normal inflater
}
catch (IllegalAccessException e)
{
return null; // hack failed: use normal inflater
}
catch (InvocationTargetException e)
{
return null; // hack failed: use normal inflater
}
if (null == view) // in theory handled above, but be safe...
return null; // hack failed: use normal inflater
// apply our own View settings after we get back to runloop
// - android will overwrite almost any setting we make now
final View v = view;
new Handler().post(new Runnable()
{
public void run()
{
v.setBackgroundColor(Color.BLACK);
try
{
// in Android <= 3.2, IconMenuItemView implemented with TextView
// guard against possible future change in implementation
TextView tv = (TextView)v;
tv.setTextColor(Color.WHITE);
}
catch (ClassCastException e)
{
// hack failed: do not set TextView attributes
}
}
});
return view;
}
});
}
Thanks for reading and enjoy!
The style attribute for the menu background is android:panelFullBackground.
Despite what the documentation says, it needs to be a resource (e.g. #android:color/black or #drawable/my_drawable), it will crash if you use a color value directly.
This will also get rid of the item borders that I was unable to change or remove using primalpop's solution.
As for the text color, I haven't found any way to set it through styles in 2.2 and I'm sure I've tried everything (which is how I discovered the menu background attribute). You would need to use primalpop's solution for that.
This is how i solved mine. I just specified the background color and text color
in styles. ie res > values > styles.xml file.
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:itemBackground">#ffffff</item>
<item name="android:textColor">#000000</item>
</style>
Just ran into this issue too, on an App that had to be compatible with Gingerbread and still retain as much of the styling from Holo-enabled devices as possible.
I found a relatively clean solution, that worked OK for me.
In the theme I use a 9-patch drawable background to get a custom background color:
<style name="Theme.Styled" parent="Theme.Sherlock">
...
<item name="android:panelFullBackground">#drawable/menu_hardkey_panel</item>
</style>
I gave up trying to style the text color, and just used a Spannable to set the text color for my item in code:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.actions_main, menu);
if (android.os.Build.VERSION.SDK_INT <
android.os.Build.VERSION_CODES.HONEYCOMB) {
SpannableStringBuilder text = new SpannableStringBuilder();
text.append(getString(R.string.action_text));
text.setSpan(new ForegroundColorSpan(Color.WHITE),
0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
MenuItem item1 = menu.findItem(R.id.action_item1);
item1.setTitle(text);
}
return true;
}
For Android 2.3 this can be done with some very heavy hacking:
The root cause for the issues with Android 2.3 is that in
LayoutInflater
the mConstructorArgs[0] = mContext is only set during running calls to
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/view/LayoutInflater.java/#352
protected void setMenuBackground(){
getLayoutInflater().setFactory( new Factory() {
#Override
public View onCreateView (final String name, final Context context, final AttributeSet attrs ) {
if ( name.equalsIgnoreCase( "com.android.internal.view.menu.IconMenuItemView" ) ) {
try { // Ask our inflater to create the view
final LayoutInflater f = getLayoutInflater();
final View[] view = new View[1]:
try {
view[0] = f.createView( name, null, attrs );
} catch (InflateException e) {
hackAndroid23(name, attrs, f, view);
}
// Kind of apply our own background
new Handler().post( new Runnable() {
public void run () {
view.setBackgroundResource( R.drawable.gray_gradient_background);
}
} );
return view;
}
catch ( InflateException e ) {
}
catch ( ClassNotFoundException e ) {
}
}
return null;
}
});
}
static void hackAndroid23(final String name,
final android.util.AttributeSet attrs, final LayoutInflater f,
final TextView[] view) {
// mConstructorArgs[0] is only non-null during a running call to inflate()
// so we make a call to inflate() and inside that call our dully XmlPullParser get's called
// and inside that it will work to call "f.createView( name, null, attrs );"!
try {
f.inflate(new XmlPullParser() {
#Override
public int next() throws XmlPullParserException, IOException {
try {
view[0] = (TextView) f.createView( name, null, attrs );
} catch (InflateException e) {
} catch (ClassNotFoundException e) {
}
throw new XmlPullParserException("exit");
}
}, null, false);
} catch (InflateException e1) {
// "exit" ignored
}
}
I tested it to work on Android 2.3 and to still work on earlier versions.
If anything breaks again in later Android versions you'll simply see the
default menu-style instead
One thing to note that you guys are over-complicating the problem just like a lot of other posts! All you need to do is create drawable selectors with whatever backgrounds you need and set them to actual items. I just spend two hours trying your solutions (all suggested on this page) and none of them worked. Not to mention that there are tons of errors that essentially slow your performance in those try/catch blocks you have.
Anyways here is a menu xml file:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/m1"
android:icon="#drawable/item1_selector"
/>
<item android:id="#+id/m2"
android:icon="#drawable/item2_selector"
/>
</menu>
Now in your item1_selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="#drawable/item_highlighted" />
<item android:state_selected="true" android:drawable="#drawable/item_highlighted" />
<item android:state_focused="true" android:drawable="#drawable/item_nonhighlighted" />
<item android:drawable="#drawable/item_nonhighlighted" />
</selector>
Next time you decide to go to the supermarket through Canada try google maps!
<style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:itemBackground">#000000</item>
</style>
this works fine for me
If you want to set an arbitrary color, this seem to work rather well for androidx. Tested on KitKat and Pie. Put this into your AppCompatActivity:
#Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (name.equals("androidx.appcompat.view.menu.ListMenuItemView") &&
parent.getParent() instanceof FrameLayout) {
((View) parent.getParent()).setBackgroundColor(yourFancyColor);
}
return super.onCreateView(parent, name, context, attrs);
}
This sets the color of android.widget.PopupWindow$PopupBackgroundView, which, as you might have guessed, draws the background color. There's no overdraw and you can use semi-transparent colors as well.
/*
*The Options Menu (the one that pops up on pressing the menu button on the emulator)
* can be customized to change the background of the menu
*#primalpop
*/
package com.pop.menu;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.LayoutInflater.Factory;
public class Options_Menu extends Activity {
private static final String TAG = "DEBUG";
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
/* Invoked when the menu button is pressed */
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
super.onCreateOptionsMenu(menu);
MenuInflater inflater = new MenuInflater(getApplicationContext());
inflater.inflate(R.menu.options_menu, menu);
setMenuBackground();
return true;
}
/*IconMenuItemView is the class that creates and controls the options menu
* which is derived from basic View class. So We can use a LayoutInflater
* object to create a view and apply the background.
*/
protected void setMenuBackground(){
Log.d(TAG, "Enterting setMenuBackGround");
getLayoutInflater().setFactory( new Factory() {
#Override
public View onCreateView ( String name, Context context, AttributeSet attrs ) {
if ( name.equalsIgnoreCase( "com.android.internal.view.menu.IconMenuItemView" ) ) {
try { // Ask our inflater to create the view
LayoutInflater f = getLayoutInflater();
final View view = f.createView( name, null, attrs );
/*
* The background gets refreshed each time a new item is added the options menu.
* So each time Android applies the default background we need to set our own
* background. This is done using a thread giving the background change as runnable
* object
*/
new Handler().post( new Runnable() {
public void run () {
view.setBackgroundResource( R.drawable.background);
}
} );
return view;
}
catch ( InflateException e ) {}
catch ( ClassNotFoundException e ) {}
}
return null;
}
});
}
}
Thanks Marcus! It works on 2.3 smoothly by fixing some syntax errors, here's the fixed code
protected void setMenuBackground() {
getLayoutInflater().setFactory(new Factory() {
#Override
public View onCreateView(final String name, final Context context,
final AttributeSet attrs) {
if (name.equalsIgnoreCase("com.android.internal.view.menu.IconMenuItemView")) {
try { // Ask our inflater to create the view
final LayoutInflater f = getLayoutInflater();
final View[] view = new View[1];
try {
view[0] = f.createView(name, null, attrs);
} catch (InflateException e) {
hackAndroid23(name, attrs, f, view);
}
// Kind of apply our own background
new Handler().post(new Runnable() {
public void run() {
view[0].setBackgroundColor(Color.WHITE);
}
});
return view[0];
} catch (InflateException e) {
} catch (ClassNotFoundException e) {
}
}
return null;
}
});
}
static void hackAndroid23(final String name,
final android.util.AttributeSet attrs, final LayoutInflater f,
final View[] view) {
// mConstructorArgs[0] is only non-null during a running call to
// inflate()
// so we make a call to inflate() and inside that call our dully
// XmlPullParser get's called
// and inside that it will work to call
// "f.createView( name, null, attrs );"!
try {
f.inflate(new XmlPullParser() {
#Override
public int next() throws XmlPullParserException, IOException {
try {
view[0] = (TextView) f.createView(name, null, attrs);
} catch (InflateException e) {
} catch (ClassNotFoundException e) {
}
throw new XmlPullParserException("exit");
}
}, null, false);
} catch (InflateException e1) {
// "exit" ignored
}
}
protected void setMenuBackground() {
getLayoutInflater().setFactory(new Factory() {
#Override
public View onCreateView (String name, Context context, AttributeSet attrs) {
if (name.equalsIgnoreCase("com.android.internal.view.menu.IconMenuItemView")) {
try {
// Ask our inflater to create the view
LayoutInflater f = getLayoutInflater();
final View view = f.createView(name, null, attrs);
// Kind of apply our own background
new Handler().post( new Runnable() {
public void run () {
view.setBackgroundResource(R.drawable.gray_gradient_background);
}
});
return view;
}
catch (InflateException e) {
}
catch (ClassNotFoundException e) {
}
}
return null;
}
});
}
this is XML file
gradient
android:startColor="#AFAFAF"
android:endColor="#000000"
android:angle="270"
shape
When using Material3, try this:
<item name="popupMenuBackground">#color/white</item>
Kotlin Androidx
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
if (parent?.parent is FrameLayout) {
(parent?.parent as View).setBackgroundColor(Color.parseColor("#33B5E5"))
}
return super.onCreateView(parent, name, context!!, attrs)
}

Categories

Resources