Currently the action mode toolbar shows above the toolbar, moving the whole layout down, and I want it to show on top of my current toolbar. I tried all solutions in this post and this one:
I tried using windowActionModeOverlay with and without android: prefix, no success
I tried both view.ActionMode and support.v7.view.ActionMode, no success
I tried using startActionMode both on my toolbar and my activity, neither worked.
setContentView is called before super.onCreate, this is not the problem
Right now I just set toolbar visibility manually, which looks awful. Also I set the activity theme before super.onCreate, could it be the problem? If not what is it? How can I make the attribute work?
My activity:
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
// import ...
public class FileExplorerActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.Theme_NoActionBar);
super.onCreate(savedInstanceState);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// ...
}
private void setActionMode(boolean enabled) {
if (enabled) {
actionMode = startSupportActionMode(new ActionMode.Callback() {
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.cab, menu);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// ... change icons color for theme
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// ... handle item clicks
return true;
}
#Override
public void onDestroyActionMode(ActionMode mode) {}
});
} else {
actionMode.finish();
}
}
}
My theme:
<style name="Theme.NoActionBar" parent="Theme.AppCompat.NoActionBar">
<item name="android:actionModeBackground">?attr/colorPrimary</item>
<item name="android:actionModeSplitBackground">#null</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="windowActionModeOverlay">true</item>
</style>
I have tried all the combination and found that you are using values-v21 folder for defining styles for lollipop and above devices and you have forgot to add <item name="windowActionModeOverlay">true</item> in styles.xml in values-v21 folder.
Just to make sure you are following the right path I have just posted a sample project on Github showing how to show ActionBar on the top of Custom toolbar.
You can also download the sample APK and try it.
Answering all the question you have posted in the question
I tried using windowActionModeOverlay with and without android: prefix, no success
You don't have to use android prefix. The windowActionModeOverlay is valid.
<item name="windowActionModeOverlay">true</item>
I tried both view.ActionMode and support.v7.view.ActionMode, no success
This is required as you are trying to use support lib which has a separate Action Bar namespace import android.support.v7.view.ActionMode;
I tried using startActionMode both on my toolbar and my activity, neither worked.
It works in both ways. If you want to look at example you can see it in the github repo.
setContentView is called before super.onCreate, this is not the problem
This isn't a problem. It will work.
Related
I am trying to use the android.view.ActionMode with the new android.support.v7.widget.Toolbar, in addition to the traditional android.app.ActionBar. I am able to display it with:
toolbar.startActionMode(callback);
The problem is that the ActionMode is displayed over the ActionBar, and not over the Toolbar. Is there a way to change that?
I tryied to set the following in my theme, but it does not seem to change anything:
<item name="windowActionModeOverlay">true</item>
Since you are using the Toolbar, I also assume you are using the
AppCompatActivity and have replaced the built in ActionBar with
your custom Toolbar using setSupportActionBar(toolbar);
First of all ensure you are importing the correct namespace:
import androidx.appcompat.view.ActionMode;
// Or
import android.support.v7.view.ActionMode;
and NOT
import android.view.ActionMode;
then use
_actionMode = startSupportActionMode(this);
and NOT
_actionMode = startActionMode(this);
Do not start it on your activity, but on your toolbar. In you activity:
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.startActionMode(mActionModeCallback)
and you have to use
<item name="windowActionModeOverlay">true</item>
in your theme as stated by Andre.
Try this in your theme:
<item name="windowActionModeOverlay">true</item>
I think the one thing people are not making clear is that the line
<item name="windowActionModeOverlay">true</item>
is placed in the Base theme i.e AppTheme and not the AppTheme.NoActionBar
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="windowActionModeOverlay">true</item>
</style>
<style name="transparent" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">#android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
find your AndroidManifest.xml ,next add below code in your application or Activity theme
<item name="windowActionModeOverlay">true</item>
so like:
<style name="WorkTimeListTheme" parent="AppTheme.NoActionBar">
<item name="windowActionModeOverlay">true</item>
<item name="actionModeBackground">#color/theme_primary</item>
</style>
So, after days going thru this thread, I finally got it working.
I'd like to summarize what I did.
Note: This is the solution using a Toolbar to replace the default ActionBar in AppCompatActivity.
I added this line to my AppTheme: It tells android that you want your action mode to overlay the toolbar
<item name="windowActionModeOverlay">true</item>
Use the right imports:
You have to use these imports in order to work with AppCompatActivity:
import androidx.appcompat.view.ActionMode;
// or
import android.support.v7.view.ActionMode;
Start the ActionMode on your Activity like so:
actionMode = startSupportActionMode(callback);
And not like so:
actionMode = startActionMode(callback);
You Activity creates the ActionMode on the toolbar automatically, because it's set as the supportActionToolbar.
The style handles the dsiplaying as overlay.
Thanks to #Kuffs and #Lefty.
This is the solution I made.
In my onCreateActionMode method of ActionMode.Callback, I add this:
StandaloneActionMode standaloneActionMode = (StandaloneActionMode) actionMode;
Field mContextView;
try {
mContextView = StandaloneActionMode.class.getDeclaredField("mContextView");
mContextView.setAccessible(true);
View contextView = (View) mContextView.get(standaloneActionMode);
MarginLayoutParams params = (MarginLayoutParams) contextView.getLayoutParams();
params.topMargin = mToolbar.getTop();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
It works for me.
I have tried all the methods above, but it still doesn`t work. And then, I tried the below method:
private class ActionModeCallback implements ActionMode.Callback {
#Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
actionMode.getMenuInflater().inflate(R.menu.note_find_action, menu);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
((AppCompatActivity) getActivity()).getSupportActionBar().hide();
return false;
}
#Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
return false;
}
#Override
public void onDestroyActionMode(ActionMode actionMode) {
((AppCompatActivity) getActivity()).getSupportActionBar().show();
}
}
Here, I used action mode and startSupportActionMode method of support library. At the same time I have also tried to modify the theme of given activity. Surely, it doesn`t work. So, if you really have no better choice you may try this one.
Just recently, I have found that I used the Colorful frame to enable multiple theme of my app, this will change the theme in code. When I tried to modify the style in this framework, it works.
Hope it works.
You can leave all the rubbish that only works in certain scenarios/phones/blue moons and simply hack it (in a very clean way):
Create 2 menu groups:
<menu>
<group android:id="#+id/group_normal">
<item id="#+id/action_edit"/>
</group>
<group android:id="#+id/group_action_mode"
android:visible="false">
<item id="#+id/action_mode_1"/>
<item id="#+id/action_mode_2"/>
..
</group>
</menu>
In your Fragment (or Activity):
class MyFragment: Fragment() {
private var actionMode = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
toolbar.apply {
setNavigationOnClickListener {
if(actionMode) endActionMode(this)
else findNavController().navigateUp()
}
setOnMenuItemClickListener {
when(it.itemId) {
R.id.action_edit -> {
// start action mode
startActionMode(this)
}
R.id.action_mode_1 -> {
// todo
}
R.id.action_mode_2 -> {
// todo
endActionMode(this)
}
...
else -> return#setOnMenuItemClickListener false
}
true
}
}
}
private fun startActionMode(toolbar: Toolbar) {
with(toolbar.menu) {
setGroupVisible(R.id.group_normal, false)
setGroupVisible(R.id.group_action_mode, true)
}
toolbar.title = 0 // todo format number of selected items
actionMode = true
}
private fun endActionMode(toolbar: Toolbar) {
with(toolbar.menu) {
setGroupVisible(R.id.group_normal, true)
setGroupVisible(R.id.group_action_mode, false)
}
toolbar.setTitle(R.string.original_title)
actionMode = false
}
}
Works every time as intended. Add extra functionality as needed.
If you see the view tree,You will can write below code:
ViewGroup decorView = (ViewGroup) getWindow().getDecorView();
traverseView(decorView);
/**
* traverse find actionmodebar
* #param root view
*/
public void traverseView(View root) {
if (root==null){
return;
}
if (root instanceof ActionBarContextView){
root.setVisibility(View.GONE);
return;
}
if ((root instanceof ViewGroup)) { // If view is ViewGroup, apply this method on it's child views
ViewGroup viewGroup = (ViewGroup) root;
for (int i = 0; i < viewGroup.getChildCount(); ++i) {
traverseView(viewGroup.getChildAt(i));
}
}
}
Make sure to put this line:
<item name="windowActionModeOverlay">true</item>
in the style of the theme that you're using in your app (it's written in your manifest file).
After hours of debugging this problem, I realized that I was making changes to a style that I DIDN'T EVEN USE!
Is there a way to change the ActionMode Overflow icon without changing the icon for the "normal" ActionBar?
I still need to figure out how to only change the Overflow-Icon inside of the ActionMode-Actionbar as I changed my Overflow-Icon in the default-Actionbar which is not visible in the ActionMode-Actionbar (and no, I don't want to change the background of my ActionMode-Actionbar!)
Okay.
Let's start with defining some styles. I will try and explain why we are defining them in this fashion:
// This is just your base theme. It will probably include a lot more stuff.
// We are going to define the style 'OverflowActionBar' next.
<style name="BaseTheme" parent="android:Theme.Holo.Light">
....
....
....
<item name="android:actionOverflowButtonStyle">#style/OverflowActionBar</item>
</style>
// Assigning a parent to this style is important - we will inherit two attributes -
// the background (state-selector) and the content description
<style name="OverflowActionBar" parent="#android:style/Widget.Holo.ActionButton.Overflow">
<item name="android:src">#drawable/overflow_menu_light</item>
</style>
// Next up is an extension to our 'BaseTheme'. Notice the parent here.
<style name="ChangeOverflowToDark" parent="#style/BaseTheme">
<item name="android:actionOverflowButtonStyle">#style/OverflowActionMode</item>
</style>
// One last thing is to define 'OverflowActionMode'. Again, we inherit useful
// attributes by assigning 'Widget.Holo.ActionButton.Overflow' as the parent.
<style name="OverflowActionMode" parent="#android:style/Widget.Holo.ActionButton.Overflow">
<item name="android:src">#drawable/overflow_menu_dark</item>
</style>
All our work with styles.xml is done. The very last bit happens at runtime. I suppose you already have an implementation of ActionMode.Callback.
In your activity, define a method - changeOverflowIcon():
public void changeOverflowIcon() {
getTheme().applyStyle(R.style.ChangeOverflowToDark, true);
}
You will be calling this method from onCreateActionMode(...) of your ActionMode.Callback implementation:
public class CustomActionModeCallback implements ActionMode.Callback {
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
changeOverflowIcon()
// other initialization
return true;
}
#Override
public boolean onPrepareActionMode(final ActionMode mode, Menu menu) {
return true;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
#Override
public void onDestroyActionMode(ActionMode mode) {}
}
A bit of explanation:
The assignment in 'BaseTheme' is for the ActionBar. It will pick the drawable overflow_menu_light since we are assigning it in the base theme of your app.
getTheme().applyStyle(R.style.ChangeOverflowToDark, true)
The second argument true forces the current theme to override the old attributes with the new ones. Since we only define one attribute in ChangeOverflowToDark, its value is overwritten. The ActionBar is not affected because it has already used the old attribute. But, the action mode is yet to be created (it will be created when we return true from onCreateActionMode(...)). When the action mode checks for this attributes value, it gets the new one.
There's more...
The answer given by Manish is quite awesome. I could have never thought of using the content description to find the exact ImageButton. But what if you could find the ImageButton using a straightforward findViewById()?
Here's how you can:
First, we will need unique ids. If your project doesn't currently have a res/values/ids.xml file, create one. Add a new id to it:
<item type="id" name="my_custom_id" />
The setup I discussed above will remain the same. The only difference will be in OverflowActionMode style:
<style name="OverflowActionMode" parent="#android:style/Widget.Holo.ActionButton.Overflow">
<item name="android:src">#drawable/overflow_menu_dark</item>
<item name="android:id">#id/my_custom_id</item>
</style>
The id we defined above will be assigned to the ImageButton when we call getTheme().applyStyle(R.style.ChangeOverflowToDark, true);
I'll borrow the code snippet from Manish's answer here:
private ActionMode.Callback mCallback = new ActionMode.Callback()
{
#Override
public boolean onPrepareActionMode( ActionMode mode, Menu menu )
{
mDecorView.postDelayed(new Runnable() {
#Override
public void run() {
ImageButton btn = (ImageButton) mDecorView.findViewById(R.id.my_custom_id);
// Update the image here.
btn.setImageResource(R.drawable.custom);
}
}, 500); // 500 ms is quite generous // I would say that 50 will work just fine
return true;
}
}
Best of both worlds?
Let's say we need R.drawable.overflow_menu_light for ActionBar and R.drawable.overflow_menu_dark for ActionMode.
Styles:
<style name="BaseTheme" parent="android:Theme.Holo.Light">
....
....
....
<item name="android:actionOverflowButtonStyle">#style/OverflowActionMode</item>
</style>
<style name="OverflowActionMode" parent="#android:style/Widget.Holo.ActionButton.Overflow">
<item name="android:src">#drawable/overflow_menu_dark</item>
<item name="android:id">#id/my_custom_id</item>
</style>
As defined in our style, the ActionBar will pick R.drawable.overflow_menu_dark - but don't we need the light version for the ActionBar? Yes - we will assign that in the activity's onPrepareOptionsMenu(Menu) callback:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
ImageButton ib = (ImageButton)
getWindow().getDecorView()
.findViewById(R.id.my_custom_id);
if (ib != null)
ib.setImageResource(R.drawable.overflow_menu_light);
}
}, 50L);
return super.onPrepareOptionsMenu(menu);
}
We are doing this here because before onPrepareOptionsMenu(Menu), the ImageButton would not have been created.
Now, we don't need to deal with ActionMode - because it will pick the dark drawable from the theme.
My apologies for this gigantic post. I really hope it helps.
ImageButton is the widget used to display the menu overflow. actionOverflowButtonStyle is used for styling the ImageButton. This styling is applied in ActionMenuPresenter.
private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
public OverflowMenuButton(Context context) {
super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
...
}
}
ActionMenuPresenter class is used for building action menus both in action bar and action modes. Hence by overriding the theme files will apply same style in both modes. The only way to accomplish is it programatically as it is done here for the action bar.
Here is the code of how it can be done for action mode overflow icon. You can assign the drawable to the ImageButton in ActionMode.Callback.onPrepareActionMode method.
public class MainActivity extends Activity {
ViewGroup mDecorView;
public void onCreate(Bundle savedInstanceState) {
// Assign mDecorView to later use in action mode callback
mDecorView = (ViewGroup) getWindow().getDecorView();
}
private ActionMode.Callback mCallback = new ActionMode.Callback()
{
#Override
public boolean onPrepareActionMode( ActionMode mode, Menu menu )
{
// We have to update the icon after it is displayed,
// hence this postDelayed variant.
// This is what I don't like, but it is the only way to move forward.
mDecorView.postDelayed(new Runnable() {
#Override
public void run() {
ArrayList<View> outViews = new ArrayList<View>();
// The content description of overflow button is "More options".
// If you want, you can override the style and assign custom content
// description and use it here.
mDecorView.findViewsWithText(outViews, "More Options", View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
if(!outViews.isEmpty()) {
View v = outViews.get(0);
if(v instanceof ImageButton) {
ImageButton btn = (ImageButton) v;
// Update the image here.
btn.setImageResource(R.drawable.custom);
}
}
}
}, 500);
return true;
}
}
}
You should be able to do that using styles:
ActionBarSherlock:
<style name="MyTheme" parent="Theme.Sherlock.Light">
<item name="actionOverflowButtonStyle">#style/MyTheme.OverFlow</item>
</style>
<style name="MyTheme.OverFlow" parent="Widget.Sherlock.ActionButton.Overflow">
<item name="android:src">#drawable/YOUR_ICON_GOES_HERE</item>
</style>
ActioBar:
<style name="MyTheme" parent="#android:style/Theme.Holo">
<item name="android:actionOverflowButtonStyle">#style/MyTheme.OverFlow</item>
</style>
<style name="MyTheme.OverFlow" parent="#android:style/Widget.Holo.ActionButton.Overflow">
<item name="android:src">#drawable/YOUR_ICON_GOES_HERE</item>
</style>
Make sure to set MyTheme in the manifest.
Is there a way to change the ActionMode Overflow icon without changing the icon for the "normal" ActionBar?
Regards how to change the overflow icon, I think there are many answers as above.
If you just want to change the color of the overflow icon, you can use a simple way.
<style name="BaseAppTheme" parent="Theme.xxxx.Light.NoActionBar.xxx">
...
<item name="actionOverflowButtonStyle">#style/ActionMode.OverFlow</item>
</style>
<style name="ActionMode.OverFlow" parent="#style/Widget.AppCompat.ActionButton.Overflow">
<item name="android:tint">#color/black</item> #or any color you want.#
</style>
It works for me. I investigated a bit, just check this screenshot http://prntscr.com/vqx1ov you will know the reason.
And I don't suggest to set the colour of colorControlNormal, it will change the color of "back arrow" and "overflow icon" on ActionBar.
In my case, I just want a different color of the three dots icon, and to achieve it, I set <item name="actionBarTheme">#style/Widget.ActionMode.ActionBar</item> in my theme, and Widget.ActionMode.ActionBar looks like below:
<style name="Widget.ActionMode.ActionBar" parent="#style/ThemeOverlay.AppCompat.Light">
<item name="colorControlNormal">the color I want</item>
</style>
I have a dynamically visible transparent ActionBar in my app (using v4 compatibility) that appears and disappears when the user long presses on the screen. This is working fine, and I'm able to use the ActionBar to execute my actions via public boolean onOptionsItemSelected(MenuItem item)
However, when I display a DialogFragment on screen, only the visible (those not in overflow) ActionBar actions fire onOptionsItemSelected. The overflow menu appears as before but I cannot click on them. They act like they are disabled.
The DialogFragments are setup so that background motion events trigger
final Window w = getDialog().getWindow();
w.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
My ActionBar xml isn't too complicated, but here it is.
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:showAsAction="ifRoom" android:id="#+id/settings" android:title="#string/settings" android:icon="#android:drawable/ic_menu_preferences"/>
<item android:showAsAction="ifRoom" android:id="#+id/history" android:title="#string/menu_history" android:icon="#drawable/ic_menu_archive"/>
<item android:showAsAction="ifRoom" android:id="#+id/new_game" android:title="#string/new_game" android:icon="#android:drawable/ic_menu_add"/>
<item android:showAsAction="ifRoom" android:id="#+id/stats" android:title="#string/menu_stats" android:icon="#android:drawable/ic_menu_agenda"/>
<item android:showAsAction="ifRoom" android:id="#+id/help" android:title="#string/help" android:icon="#android:drawable/ic_menu_help"/>
</menu>
Other than that, I'm not doing anything too strange in my app. Any ideas why the overflow menu fails to work when a DialogFragment is displayed?
My DialogFragment class calls setHasOptionsMenu(true) and I can see onCreateOptionsMenu(Menu menu, MenuInflater inflater) being called, but my onOptionsItemSelected doesn't get called for the overflow items.
EDIT
I decided to throw away the ActionBar implementation and use a Navigation Drawer slider for my app. After I implemented everything, I ran into the same problem. With no DialogFragments visible, I can use the Navigation Drawer just fine and make selections. However when my DialogFragment is visible, I can drag the drawer in and out, but I am unable to make any selections on the ListView.
Here's my code for the child dialog.
public class CChildDialog extends DialogFragment {
public interface DialogDismissHandler {
abstract public void onDismiss(CChildDialog dlg);
}
protected Game m_game;
protected DialogDismissHandler m_dismissListener;
public CChildDialog() {
super();
}
public boolean isDialogShowing() {
Dialog d = getDialog();
if (d != null)
return d.isShowing();
else
return false;
}
public void setGame(Game g) {
m_game = g;
}
public void setOnDismissHandler(DialogDismissHandler dismissListener) {
m_dismissListener = dismissListener;
}
public void onDismiss(DialogInterface dlg) {
super.onDismiss(dlg);
if (m_dismissListener != null) {
m_dismissListener.onDismiss(this);
m_dismissListener = null;
}
m_game = null;
}
public void onPreferencesChanged(Game game) {
}
#Override
public void onStart() {
final Window w = getDialog().getWindow();
w.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
WindowManager.LayoutParams lp = w.getAttributes();
if (m_game.isAdVersion()) {
// move it up 25 pixels
lp.y = -25;
}
// check for super small screens
if (UIUtil.GetWindowSize(m_game.getWindowManager()).y < 400) {
lp.y -= 30; // move it up a little more
}
super.onStart();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_TITLE,R.style.Theme_CustomDialog);
}
And here is my custom theme
<style name="Theme.CustomDialog" parent="#android:style/Theme.Holo.Dialog">
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowNoTitle">true</item>
</style>
The answer is to also add the FLAG_NOT_FOCUSABLE LayoutParams flag. Adding this allows full use of the overflow menu when a DialogFragment is visible.
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
return true;
case R.id.searchIcon:
return true;
case R.id.startRefresh:
refreshItem = item;
refresh();
return true;
case R.id.stopRefresh:
if (refreshItem != null && refreshItem.getActionView() != null) {
refreshItem.getActionView().clearAnimation();
refreshItem.setActionView(null);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void refresh() {
if (FeedActivity.this != null) {
/*
* Attach a rotating ImageView to the refresh item as an ActionView
*/
LayoutInflater inflater = (LayoutInflater) FeedActivity.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ImageView iv = (ImageView) inflater.inflate(
R.layout.refresh_action_view, null);
Animation rotation = AnimationUtils.loadAnimation(
FeedActivity.this, R.anim.clockwise_refresh);
rotation.setRepeatCount(Animation.INFINITE);
iv.startAnimation(rotation);
refreshItem.setActionView(iv);
}
}
Before Clicking:
After Clicking:
Here the icon is being animated(rotating).
Problem:
why is it shifting to the left?
once it shifts to the left, the icon becomes non clickable and strangely the device back button also doesn't work
EDIT:
In comments below this answer:
Animated Icon for ActionItem
Jake Warton says if you are using a square and correct sized icon for the menu item, you wont get this weird behaviour, to someone who has the same problem.
But i am using a 32x32 image on a device which uses mdpi drawables. Which as stated there must work :(
Thank You
EDIT:
refresh_action_view.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
style="#style/Widget.Sherlock.ActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_refresh" />
Custom Style i use in my app
<style name="My_solid_ActionBar" parent="#style/Widget.Sherlock.Light.ActionBar.Solid.Inverse">
<item name="background">#drawable/ab_solid_My</item>
<item name="backgroundStacked">#drawable/ab_stacked_solid_My</item>
<item name="backgroundSplit">#drawable/ab_bottom_solid_My</item>
<item name="progressBarStyle">#style/My_ProgressBar</item>
<item name="android:background">#drawable/ab_solid_My</item>
<item name="android:backgroundStacked">#drawable/ab_stacked_solid_My</item>
<item name="android:backgroundSplit">#drawable/ab_bottom_solid_My</item>
<item name="android:progressBarStyle">#style/My_ProgressBar</item>
</style>
The issue is that you're not handling all menu inflation in onCreateOptionsMenu(). The basic logic for an ActionBar refresh animation I've seen used in apps with open source , for example Andlytics (and also used myself in projects), is to implement a boolean flag in onCreateOptionsMenu() to decide whether to show the refresh animation.
You can implement it like this: When your refresh() method is called, it sets the boolean isRefreshing flag to true and calls inValidateOptionsMenu() which 'behind the scene' calls onCreateOptionsMenu() to start the animation:
Inflate the menu in onCreateOptionsMenu(...):
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
super.onCreateOptionsMenu(menu, inflater);
//inflate a menu which shows the non-animated refresh icon
inflater.inflate(R.menu.my_ab_menu, menu);
if (isRefreshing) {
//if we're refreshing, show the animation
MenuItem item = menu.findItem(R.id.refreshMenuItem);
item.setActionView(R.layout.action_bar_indeterminate_progress);
ImageView iv = (ImageView) item.getActionView().findViewById(R.id.loadingImageView);
((AnimationDrawable) iv.getDrawable()).start();
}
}
Start animation like so:
public void refresh(){
isRefreshing = true;
inValidateOptionsMenu();
}
If you want the user to start the animation when he taps the refresh icon, do like this in onOptionsItemSelected():
case R.id.refreshMenuItem:
isRefreshing = true;
item.setActionView(R.layout.action_bar_indeterminate_progress);
ImageView iv = (ImageView) item.getActionView().findViewById(R.id.loadingImageView);
((AnimationDrawable) iv.getDrawable()).start();
//...
To stop the animation call:
isRefreshing = false;
invalidateOptionsMenu();
This code is from a Fragment so you may have to tweak if for an Activity, but I think it communicates the basic idea.
I tried the exact same code you use and it works just fine for me. The only things that might be different are two things:
1) the refresh_action_view layout (here's mine for comparison):
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
style="?attr/actionButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_menu_refresh" />
2) The display options of your action bar (here's my styles.xml for comparison).
<resources>
<style name="AppTheme" parent="Theme.Sherlock.Light.DarkActionBar">
<item name="android:actionBarStyle">#style/Widget.ActionBar</item>
<item name="actionBarStyle">#style/Widget.ActionBar</item>
</style>
<style name="Widget.ActionBar" parent="Widget.Sherlock.Light.ActionBar.Solid.Inverse">
<item name="android:displayOptions">showHome|useLogo|showCustom</item>
<item name="displayOptions">showHome|useLogo|showCustom</item>
</style>
</resources>
Can you share yours as well?
I am trying to use the new api's from google, specifically the action bar.
When the build was set at api 10, if I pressed the menu button, I got nice looking menu options, each with a picture and icon. When using api 14, No matter what I try, it always puts the icon in the action bar with NO text. I have tried everything I can think of. I gave it the "with text" property, changed the text to a single character (in case it was a room issue), but nothing.
I have seen this done before, even in the developer guide at android.developer, but I can't seem to find an answer as to HOW to get it to show up.
I suspect that it was a conscious decision by the Android developers to never display a single menu item's text and icon on a narrow action bar. But if you really want to do so, you can use android:actionLayout in your menu.xml file. The Android ActionBar documentation has a slightly better explanation.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/menu_foo"
android:title="#string/menu_foo"
android:icon="#drawable/ic_menu_foo"
android:showAsAction="always"
android:actionLayout="#layout/action_button_foo" />
</menu>
Then create your action_button_foo.xml layout:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:gravity="center"
android:text="#string/menu_foo"
android:drawableLeft="#drawable/ic_menu_foo"
android:background="#drawable/bg_btn_action_bar"
android:clickable="true" />
and use a selector for its background bg_btn_action_bar.xml, so it changes color when you tap it:
<?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/bg_action_bar_pressed" />
<item
android:drawable="#color/transparent" />
</selector>
Now you'll need to make your custom view handle click events. In your Activity, I like to do this, so that I can handle the click in onOptionsItemSelected along with all my other, non-custom items.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.my_menu, menu);
final MenuItem item = menu.findItem(R.id.menu_foo);
item.getActionView().setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
onOptionsItemSelected(item);
}
});
return super.onCreateOptionsMenu(menu);
}
This is definitely the same thing I've observed on my Nexus S running 4.0.4. My app uses an action bar with several tabs that are implemented as fragments. My various fragments make adjustments to the menu options displayed on the action bar while the their tab is visible.
This appears to be a bug in ICS, because it performs consistently as follows, both on my Nexus S and in the emulator (both HVGA and WVGA800):
In portrait mode, my logo/up button appears on the top row of the action bar, tabs appear on the second row, and any actions appear as icons only (no text) in the right side of the top row.
But if when I rotate to landscape, the action bar collapses to a single row, and tabs move up to the top bar as a spinner (drop-down list) next to my up button. But notably, then the text appears next to my action icons.
I noticed some other glitches with the tab spinner that lead me to believe that this little corner of ICS is a bit messy/buggy. If I tell the application to split the action bar on narrow displays (by adding android:uiOptions="splitActionBarWhenNarrow" in the manifest, ICS always pushes those items to the bottom bar, even though there's still plenty of room at the top. And even with the extra bar, it still doesn't display the text, just the icon.
On my Xoom running 4.0.4, tabs and action items always appear the way you'd expect them to appear because there's plenty of room.
Workaround: if you really want text on the action bar in portrait mode, you need to give up the icon. Remove the icon from your menu item and the text will appear. This isn't exactly what we're after though.
I've posted a bug report here: https://code.google.com/p/android/issues/detail?id=30180.
If you want your Options Menu to show up in your action bar with Honeycomb, I did this:
In your activity, override this function:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.actionbar_universe, menu);
return true;
}
where R.menu.actionbar_universe define your menu item like this:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/crossholdings" android:showAsAction="always|withText"
android:title="Cross Holdings" android:icon="#drawable/actionbar_cross"/>
</menu>
Note the showAsAction="always|withText" and specify android:title.
If you have that and its not working please copy|paste your menu resource here.
EDIT: This answers the wrong question, but it is the original text.
I use this bit of code to set the title of the action bar, and paint it red with my companies logo. It works well in 3.0.
public ActionBar setActionBarStyle(String title) {
ActionBar actionBar = setActionBarStyle();
actionBar.setTitle(title);
return actionBar;
}
public ActionBar setActionBarStyle() {
ActionBar actionBar = getActionBar();
ShapeDrawable actionBackground = new ShapeDrawable();
actionBackground.getPaint().setColor(Color.RED);
actionBackground.setBounds(0, 0, 5, 5);
actionBar.setBackgroundDrawable(actionBackground);
actionBar.setDisplayUseLogoEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
return actionBar;
}
The "withText" property works with most tablets, but an easy way to get icons and text on smaller devices is to add the text next to the icon as one image (PNG file).
That way, both the text and icon will be seen as one icon and the whole thing will display.
You can use the original icon for tablets by using the withText property.
You have to create an extra menu folder in the res directory titled "menu-w600dp".
The optionmenu.xml in this folder will only apply to screen widths bigger than 600dp
(the ones that will show the icons and text with no problems).
Fixed this issue by reading "If your app is using the Support Library" section under Specify the Actions in XML.
...for compatibility on versions as low as Android 2.1, the showAsAction attribute is not available from the android: namespace. Instead this attribute is provided by the Support Library and you must define your own XML namespace and use that namespace as the attribute prefix. (A custom XML namespace should be based on your app name, but it can be any name you want and is only accessible within the scope of the file in which you declare it.) For example:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
<!-- Search, should appear as action button -->
<item android:id="#+id/action_search"
android:icon="#drawable/ic_action_search"
android:title="#string/action_search"
yourapp:showAsAction="ifRoom" />
...
</menu>
If none of these other things work for you, this may.
I tried many options and I came up with a simple "trick" without any weird line of code, without images. And first solution with custom actionLayout simply did not work for me with API level 10 compatibility.
If you want to display text AND icon on a small action bar it means you know you have the space, right? So you can use 2 menu items:
First with the icon ONLY (ignore warning, if you set a title tablets will show it twice)
Second with the text ONLY
And choose the text action to 'ifRoom' if needed so that if you do need space, the text will go away. It WILL take some more space on the action bar though but was a good compromise for me.
I end up with the following code:
<item
android:id="#+id/menu_save"
android:icon="#drawable/save"
pelmel:showAsAction="always">
</item>
<item
android:id="#+id/menu_save_text"
android:title="#string/profileSave"
pelmel:showAsAction="ifRoom">
</item>
(EDIT Where "pelmel" is your app name END EDIT)
And then your selection handler just has to catch both IDs :
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_save:
case R.id.menu_save_text:
// Your code here
return true;
}
}
Here's another option, based roughly on dgmltn's. The advantages:
More control - e.g. I've swapped the text and image over in my layout.
Easier to use - only requires two extra lines in your activities/fragments.
Only requires two extra files.
Possibly slightly more correct, but it's still a bit of a hack IMO.
I've assumed you're using ActionBarSherlock in this example. First, create the view layout you want. This one is based on ActionBarSherlock's. All I changed was swapping the image/view over, reducing the shared margin/padding to 0 so they are closer, and resolving all the ABS styles.
<com.example.views.ActionMenuTextItemView xmlns:android="http://schemas.android.com/apk/res/android"
style="#android:style/Widget.Holo.ActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:addStatesFromChildren="true"
android:focusable="true"
android:gravity="center"
android:clickable="true"
android:paddingLeft="4dip"
android:paddingRight="4dip" >
<com.actionbarsherlock.internal.widget.CapitalizingButton
android:id="#+id/abs__textButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#null"
android:ellipsize="none"
android:focusable="false"
android:minHeight="48dip"
android:minWidth="48dip"
android:paddingBottom="4dip"
android:paddingLeft="4dip"
android:paddingRight="0dip"
android:paddingTop="4dip"
android:singleLine="true"
android:textAppearance="#android:style/TextAppearance.Holo.Widget.ActionBar.Menu"
android:textColor="#fff3f3f3" />
<ImageButton
android:id="#+id/abs__imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="4dip"
android:layout_marginLeft="0dip"
android:layout_marginRight="4dip"
android:layout_marginTop="4dip"
android:adjustViewBounds="true"
android:background="#null"
android:focusable="false"
android:scaleType="fitCenter"
android:visibility="gone" />
</com.example.views.ActionMenuTextItemView>
Then create the corresponding View class. You may want to copy CapitalizingButton if you are worried about using internal things. Oh, also I never fixed the minimum width stuff. Don't think it really matters though.
package com.example.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import com.actionbarsherlock.R;
import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.app.SherlockFragment;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.app.SherlockListActivity;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.internal.widget.CapitalizingButton;
import com.actionbarsherlock.view.MenuItem;
#SuppressLint({ "NewApi" })
public class ActionMenuTextItemView extends LinearLayout implements OnClickListener
{
private ImageButton mImageButton;
private CapitalizingButton mTextButton;
private Object mTarget;
private MenuItem mItem;
// Set up all the data. Object must be a sherlock activity or fragment with an onMenuItemSelected().
public void initialise(MenuItem item, Object target)
{
mItem = item;
mTarget = target;
setIcon(mItem.getIcon());
setTitle(mItem.getTitle());
}
public ActionMenuTextItemView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ActionMenuTextItemView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
public void onFinishInflate()
{
super.onFinishInflate();
mImageButton = (ImageButton) findViewById(R.id.abs__imageButton);
mTextButton = (CapitalizingButton) findViewById(R.id.abs__textButton);
mImageButton.setOnClickListener(this);
mTextButton.setOnClickListener(this);
setOnClickListener(this);
}
#Override
public void setEnabled(boolean enabled)
{
super.setEnabled(enabled);
mImageButton.setEnabled(enabled);
mTextButton.setEnabled(enabled);
}
public void setIcon(Drawable icon)
{
mImageButton.setImageDrawable(icon);
if (icon != null)
mImageButton.setVisibility(VISIBLE);
else
mImageButton.setVisibility(GONE);
}
public void setTitle(CharSequence title)
{
mTextButton.setTextCompat(title);
setContentDescription(title);
}
#Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event)
{
onPopulateAccessibilityEvent(event);
return true;
}
#Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
super.onPopulateAccessibilityEvent(event);
final CharSequence cdesc = getContentDescription();
if (!TextUtils.isEmpty(cdesc))
event.getText().add(cdesc);
}
#Override
public boolean dispatchHoverEvent(MotionEvent event)
{
// Don't allow children to hover; we want this to be treated as a single component.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
return onHoverEvent(event);
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int minWidth = 0;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int specSize = MeasureSpec.getSize(widthMeasureSpec);
final int oldMeasuredWidth = getMeasuredWidth();
final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, minWidth) : minWidth;
if (widthMode != MeasureSpec.EXACTLY && minWidth > 0 && oldMeasuredWidth < targetWidth)
{
// Remeasure at exactly the minimum width.
super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
}
}
#Override
public void onClick(View v)
{
if (mTarget == null)
return;
else if (mTarget instanceof SherlockActivity)
((SherlockActivity)mTarget).onOptionsItemSelected(mItem);
else if (mTarget instanceof SherlockFragmentActivity)
((SherlockFragmentActivity)mTarget).onOptionsItemSelected(mItem);
else if (mTarget instanceof SherlockListActivity)
((SherlockListActivity)mTarget).onOptionsItemSelected(mItem);
else if (mTarget instanceof SherlockListFragment)
((SherlockListFragment)mTarget).onOptionsItemSelected(mItem);
else if (mTarget instanceof SherlockFragment)
((SherlockFragment)mTarget).onOptionsItemSelected(mItem);
else
throw new IllegalArgumentException("Target must be a sherlock activity or fragment.");
}
}
Ok now you're ready to use it. In your menu items that you want to have text, you do the same as what dgmltn said:
<item
android:id="#+id/menu_foo"
android:icon="#drawable/..."
android:showAsAction="always|withText" // Doesn't do anything really.
android:title="Sell"
android:titleCondensed="Sell"
android:actionLayout="#layout/view_action_menu_text_item"/> // Or whatever you called it.
And finally, just add this code to your activity/fragment:
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
getSupportMenuInflater().inflate(R.menu.activity_main, menu);
// The magic lines.
MenuItem it = menu.findItem(R.id.menu_foo);
((ActionMenuTextItemView)it.getActionView()).initialise(it, this);
And that's it!