I am trying to change accessibility content description for Android menu item.
Here is my code and talk back announcing => Test search, Search, double tap to activate.
<item
android:id="#+id/menuItemSearch"
android:icon="#drawable/search"
android:iconTintMode="src_atop"
android:title="Search"
android:visible="false"
app:iconTint="#color/primary"
app:contentDescription="Test Search"
app:showAsAction="always"/>
How can change it to => Search. Button. Double tap to search.
Solution
Add -> app:actionViewClass="android.widget.ImageButton"
<item
android:id="#+id/menuItemSearch"
android:icon="#drawable/icongel_search"
android:iconTintMode="src_atop"
android:title="#string/toolbarSearchIcon"
android:visible="false"
app:iconTint="#color/primary"
app:showAsAction="always"
app:actionViewClass="android.widget.ImageButton"/>
Then
menu.findItem(R.id.menuItemSearch).apply {
val searchIcon = this.actionView as ImageButton
searchIcon.apply {
setImageResource(R.drawable.search)
setColorFilter(ContextCompat.getColor(context, R.color.primary), PorterDuff.Mode.SRC_ATOP)
contentDescription = getString(R.string.toolbarSearchIcon)
setBackgroundColor(ContextCompat.getColor(context, R.color.transparent))
ViewCompat.setAccessibilityDelegate(
this,
object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.addAction(
AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfoCompat.ACTION_CLICK, getString(R.string.toolbarSearchIcon)
)
)
}
}
)
}
}
There are 2 issues here:
1. Menu items not announcing as a button
You need to import the latest material library in your app's build.gradle file.
implementation 'com.google.android.material:material:1.7.0'
Be wary as there may be other dependencies.
2. Create a custom action label
Currently this is not possible as you would need to get access to the view in the Toolbar. Then you could use the following method as described in the documentation:
ViewCompat.replaceAccessibilityAction(
// View that contains touch & hold action
itemView, // <-- this is what we don't have
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
// Announcement read by TalkBack to surface this action
getText(R.string.favorite),
null
)
You could probably raise a bug on the Issue Tracker for problem 2.
Related
I am working on the accessibility and currently I want to set the Button Role on the MenuItem. We have checked but not got the proper solution for the same. I tried by setting the custom action layout and then giving the custom action layout Button Role it detect it as button but click need to be handled by setting the click listener on action layout. Which I want to avoid, is there any possibility that we can set role to MenuItem. So it will announce like "Setting Button Double Tap To Activate"
`#JvmStatic
fun View.setCustomRole(roleInfo: String) {
ViewCompat.setAccessibilityDelegate(this,
object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
v: View,
info: AccessibilityNodeInfoCompat
) {
super.onInitializeAccessibilityNodeInfo(v, info)
info.roleDescription = roleInfo
}
})
}`
Tried above method by setting action layout which work but I need to change the click handling from app which I want to avoid.
This was answered fairly recently, however I think I can clean it up somewhat.
Option 1
You can make MenuItem's buttons by default by ensuring you have the latest material library imported.
implementation 'com.google.android.material:material:1.7.0'
The sample app I created for my answer was 1.5.0 and it still had the default "button" announcement.
Option 2
In Material 1.7.0:
I didn't need any of this code to achieve the solution to the question
onInitializeAccessibilityNodeInfo host and info are not nullable!
Ensure that your MenuItem has an actionViewClass associated with it.
<item
...
android:icon="ICON_REFERENCE"
app:showAsAction="ifRoom"
app:actionViewClass="android.widget.ImageButton"
...
/>
Bonus to option 2:
To be able to customize a11y attributes, you can then get the item and assign custom role descriptions or extra actions:
// inside onCreateOptionsMenu
val menuActionView = menu
.findItem(R.id.action_settings)
.actionView as ImageButton
ViewCompat.setAccessibilityDelegate(menuActionView, object: AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View?,
info: AccessibilityNodeInfoCompat?
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info?.apply {
// not required as this is already a button
// always use a built in class as this will be localized
// automatically for you
// roleDescription = Button::class.java.simpleName
// I found I had to set this here, and not in the menu xml
contentDescription = getString(R.string.action_settings)
// to replace the term "activate" in "double tap to activate"
// in production apps, use a localized string!
addAction(
AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfoCompat.ACTION_CLICK, "Open menu"
)
)
}
}
})
When Accessibility Talkback is ON. I have a requirement to show my view with some custom actions when the user draws angular gesture i.e. swipe up and right.
Similar like Gmail messages as shown in the pic below
enter image description here
this popup is shown when the user draws action gesture i.e. swipe up and right when the focus is on any message.
Action menu
You can add a custom action to the AccessibilityNodeInfo object inside the onInitializeAccessibilityNodeInfo method of an AccessibilityDelegate.
If the action is then selected by the user, the performAccessibilityAction method is called on the View.
In the example "myCustomAction" is the text that is displayed to the user.
MyCustomView.kt
init {
accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) {
super.onInitializeAccessibilityNodeInfo(host, info)
info?.addAction(AccessibilityNodeInfo.AccessibilityAction(R.id.myCustomAccessibilityEvent, "myCustomAction"));
}
}
}
override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
if (action == R.id.myCustomAccessibilityEvent) Log.d("TAG", "Accessibility event triggered")
return true;
}
res/values/accessibility.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="myCustomAccessibilityEvent"/>
</resources>
In order to add an accessibility action the below line of code suffices, using ViewCompat which is backwards compatible to API 21.
ViewCompat.addAccessibilityAction(viewToAddActionTo,"String to describe what the action does", (v,b) -> actionCalled);
//a sample request would look like
ViewCompat.addAccessibilityAction(swipeLayout,"Delete Item in List",(v,b) -> deleteItem(position);
Hopefully this suffices, the action to be done i.e delete, star, archive (Similar to Gmail) can be added to custom views using this.
Key Points are to figure out which View needs the action and what should the action perform.
I'm trying to implement the share functionality within my app. So far it works fine and I can share text to all other apps. The problem is the way it's shown.
I want something like just the share icon visible and then when user taps on it, it opens the OS dialog and lets user choose the app they want to share content to.
var share_article = menu.FindItem (Resource.Id.action_share);
var share_article_provider = (Android.Support.V7.Widget.ShareActionProvider) Android.Support.V4.View.MenuItemCompat.GetActionProvider (share_article);
share_article_provider.SetShareIntent (CreateIntent ());
and the xml:
<item
android:id="#+id/action_share"
myapp:showAsAction="ifRoom"
android:title="share"
myapp:actionProviderClass="android.support.v7.widget.ShareActionProvider" />
My app currently looks like this:
There's also a white border around it that I don't like.
Is there any way to change the icon??
How do I fix it??
You just want to turn off your share history.There is no official API to do this, but you can make your own ShareActionProvider. Actually there are two similar question on SO:
How do you turn off share history when using ShareActionProvider?
How to hide the share action (which use most) icon near the share action provider?
Wish these could help you.
As mentioned here when using support library this can be fixed really easily. This method won't turn off the share history but will hide the icons from actionbar.
I just needed to subclass the Android.Support.V7.Widget.ShareActionProvider like the following: (C# using Xamarin)
public class MyShareActionProvider : Android.Support.V7.Widget.ShareActionProvider
{
public SingleArticleShareActionProvider (Context context) : base (context)
{}
public override View OnCreateActionView ()
{
return null;
}
}
and then inside OnCreateOptionsMenu use the MyShareActionProvider like:
var share_article = menu.FindItem (Resource.Id.action_share);
var share = new SingleArticleShareActionProvider (globalContext);
Android.Support.V4.View.MenuItemCompat.SetActionProvider (share_article, share);
share_article.SetIcon (Resource.Drawable.abc_ic_menu_share_mtrl_alpha);
share.SetShareIntent (CreateIntent ());
You can use any icon you like with the method SetIcon.
I have created listview using xamarin.forms, I am searching for a way not to highlight the viewcell while tapping on the listview.
please check on the image below.
Thanks in advance :)
on your ListView SelectedItem event handler, you can do:
listview.SelectedItem = null;
that'll give you the on click highlight, but the state will be transient only.
In your case, I guess you'd like this because you're using 2 Images instead of Buttons for the arrows on the right, with a TapGestureRecognizer. Do you know the Button has an Image property? When clicking on a Button in a Cell, the Cell selected state shouldn't change.
Just put this in your custom theme:
<item name="android:colorActivatedHighlight">#android:color/transparent</item>
I want to share another solution I really like, because it's simple, you have to implement it once and it's really easy to use in XAML.
First we have to implement our own behavior. That's quite simple:
public class DeselectItemBehavior : Behavior<ListView>
{
protected override void OnAttachedTo(ListView bindable)
{
base.OnAttachedTo(bindable);
bindable.ItemSelected += ListView_ItemSelected;
}
protected override void OnDetachingFrom(ListView bindable)
{
base.OnDetachingFrom(bindable);
bindable.ItemSelected -= ListView_ItemSelected;
}
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
((ListView)sender).SelectedItem = null;
}
}
So we're just registering to the event when the behavior is set and unregister it when it's unset.
The event itself uses Stephane Delcroix approach.
Now everything you have to do in the ListView is to add the behavior like this:
<ListView ...>
<ListView.Behaviors>
<behaviors:DeselectItemBehavior />
</ListView.Behaviors>
I'm using: ListView SelectionMode = "None" in Xamarin.Forms.
For disabling Listview cell selection highlight in both Android and iOS, this link was very helpful
https://gist.github.com/jessejiang0214/63b29b3166330c6fc083
ProjectName.Android/Resources/values/styles.xml
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<style name="CustomTheme" parent="android:Theme.Holo.Light">
<item name="android:colorActivatedHighlight">#android:color/transparent</item>
</style>
</resources>
CustomAllViewCellRendereriOS.cs
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer (typeof(ViewCell), typeof(MyAPP.iOS.CustomAllViewCellRendereriOS))]
namespace MyAPP.iOS
{
public class CustomAllViewCellRendereriOS : ViewCellRenderer
{
public override UIKit.UITableViewCell GetCell (Cell item, UIKit.UITableViewCell reusableCell, UIKit.UITableView tv)
{
var cell = base.GetCell (item, reusableCell, tv);
if (cell != null)
cell.SelectionStyle = UIKit.UITableViewCellSelectionStyle.None;
return cell;
}
}
}
This seems to be the right & best way...!
You can't, you have to implement a custom render. If you just set selected item to null, it will remove the selected color. But you will first have selected the item and then deselected it again (multible events) but your did'nt see it :-). If you enabled tilt effect on Windows Phone, the tilt is still happen because of all the event!
But i would like to see the Xamarin Forms team implement a CanSelect properties on the listview.
I want to suggest you another solution.
You can add:
IsEnabled="False"
in your listview widget.
In my case this solution works well.
I'd like to have all of the menu items that don't fit into the ActionBar go into the overflow menu (the one that is reached from the Action Bar not the menu button) even on devices that do have a Menu button. This seems much more intuitive for users than throwing them into a separate menu list that requires the user to jump from a touch(screen) interaction to a button based interaction simply because the layout of the ActionBar can't fit them on the bar.
On the emulator I can set the "Hardware Back/Home Keys" value to "no" and get this effect.
I've searched for a way to do this in code for an actual device that has a menu button but can't fine one. Can anyone help me?
You can also use this little hack here:
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if (menuKeyField != null) {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
} catch (Exception ignored) {
}
Good place to put it would be the onCreate-Method of your Application class.
It will force the App to show the overflow menu. The menu button will still work, but it will open the menu in the top right corner.
[Edit] Since it has come up several times now: This hack only works for the native ActionBar introduced in Android 3.0, not ActionBarSherlock. The latter uses its own internal logic to decide whether to show the overflow menu. If you use ABS, all platforms < 4.0 are handled by ABS and are thus subjected to its logic. The hack will still work for all devices with Android 4.0 or greater (you can safely ignore Android 3.x, since there aren't really any tablets out there with a menu button).
There exists a special ForceOverflow-Theme that will force the menu in ABS, but apperently it is going to be removed in future versions due to complications.
EDIT: Modified to answer for the situation of physical menu button.
This is actually prevented by design. According to the Compatibility Section of the Android Design Guide,
"...the action overflow is available from the menu hardware key. The resulting actions popup... is displayed at the bottom of the screen."
You'll note in the screenshots, phones with a physical menu button don't have an overflow menu in the ActionBar. This avoids ambiguity for the user, essentially having two buttons available to open the exact same menu.
To address the issue of consistency across devices: Ultimately it's more important to the user experience that your app behave consistently with every other app on the same device, than that it behave consistently with itself across all devices.
I use to workaround it by defining my menu like this (also with ActionBarSherlock icon used in my example):
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/menu_overflow"
android:icon="#drawable/abs__ic_menu_moreoverflow_normal_holo_light"
android:orderInCategory="11111"
android:showAsAction="always">
<menu>
<item
android:id="#+id/menu_overflow_item1"
android:showAsAction="never"
android:title="#string/overflow_item1_title"/>
<item
android:id="#+id/menu_overflow_item2"
android:showAsAction="never"
android:title="#string/overflow_item2_title"/>
</menu>
</item>
</menu>
I admit that this may require manual "overflow-management" in your xml, but I found this solution useful.
You can also force device to use HW button to open the overflow menu, in your activity:
private Menu mainMenu;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO: init menu here...
// then:
mainMenu=menu;
return true;
}
#Override
public boolean onKeyUp(int keycode, KeyEvent e) {
switch(keycode) {
case KeyEvent.KEYCODE_MENU:
if (mainMenu !=null) {
mainMenu.performIdentifierAction(R.id.menu_overflow, 0);
}
}
return super.onKeyUp(keycode, e);
}
:-)
If you are using the action bar from the support library (android.support.v7.app.ActionBar), use the following:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:yorapp="http://schemas.android.com/apk/res-auto" >
<item
android:id="#+id/menu_overflow"
android:icon="#drawable/icon"
yourapp:showAsAction="always"
android:title="">
<menu>
<item
android:id="#+id/item1"
android:title="item1"/>
<item
android:id="#+id/item2"
android:title="item2"/>
</menu>
</item>
</menu>
This kind of method is prevented by the Android Developers Design System, but I found a way to pass it:
Add this to your XML menu file:
<item android:id="#+id/pick_action_provider"
android:showAsAction="always"
android:title="More"
android:icon="#drawable/ic_action_overflow"
android:actionProviderClass="com.example.AppPickActionProvider" />
Next, create a class named 'AppPickActionProvider', and copy the following code to it:
package com.example;
import android.content.Context;
import android.util.Log;
import android.view.ActionProvider;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.SubMenu;
import android.view.View;
public class AppPickActionProvider extends ActionProvider implements
OnMenuItemClickListener {
static final int LIST_LENGTH = 3;
Context mContext;
public AppPickActionProvider(Context context) {
super(context);
mContext = context;
}
#Override
public View onCreateActionView() {
Log.d(this.getClass().getSimpleName(), "onCreateActionView");
return null;
}
#Override
public boolean onPerformDefaultAction() {
Log.d(this.getClass().getSimpleName(), "onPerformDefaultAction");
return super.onPerformDefaultAction();
}
#Override
public boolean hasSubMenu() {
Log.d(this.getClass().getSimpleName(), "hasSubMenu");
return true;
}
#Override
public void onPrepareSubMenu(SubMenu subMenu) {
Log.d(this.getClass().getSimpleName(), "onPrepareSubMenu");
subMenu.clear();
subMenu.add(0, 1, 1, "Item1")
.setIcon(R.drawable.ic_action_home).setOnMenuItemClickListener(this);
subMenu.add(0, 2, 1, "Item2")
.setIcon(R.drawable.ic_action_downloads).setOnMenuItemClickListener(this);
}
#Override
public boolean onMenuItemClick(MenuItem item) {
switch(item.getItemId())
{
case 1:
// What will happen when the user presses the first menu item ( 'Item1' )
break;
case 2:
// What will happen when the user presses the second menu item ( 'Item2' )
break;
}
return true;
}
}
Well I think that Alexander Lucas has provided the (unfortunately) correct answer so I'm marking it as the "correct" one. The alternative answer I'm adding here is simply to point any new readers to this post in the Android Developers blog as a rather complete discussion of the topic with some specific suggestions as to how to deal with your code when transitioning from pre-level 11 to the new Action Bar.
I still believe it was a design mistake not have the menu button behave as a redundant "Action Overflow" button in menu button enabled devices as a better way to transition the user experience but its water under the bridge at this point.
I'm not sure if this is what you're looking for, but I built a Submenu within the ActionBar's Menu and set its icon to match the Overflow Menu's Icon. Although it wont have items automatically sent to it, (IE you have to choose what's always visible and what's always overflowed) it seems to me that this approach may help you.
In the gmail app that comes with ICS pre-installed, the menu button is disabled when you have multiple items selected. The overflow menu is here "forced" to be triggered by the use of the overflow button instead of the physical menu button. Theres a 3rd-party lib called ActionBarSherlock which lets you "force" the overflow menu. But this will only work on API level 14 or lower(pre-ICS)
If you use Toolbar, you can show the overflow on all versions and all devices, I've tried on some 2.x devices, it works.
Sorry if this problem is dead.
Here is what I did to resolve the error. I went to layouts and created two ones containing toolbars. One was a layout for sdk version 8 and the other was for sdk version 21. On version 8, I used the android.support.v7.widget.Toolbar while I used android.widget.Toolbar on the sdk 21 layout.
Then I inflate the toolbar in my activity. I check the sdk to see if it was 21 or higher. I then inflate the corresponding layout. This forces the hardware button to map onto the toolbar you actually designed.
For anyone using the new Toolbar:
private Toolbar mToolbar;
#Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
...
}
#Override
public boolean onKeyUp(int keycode, KeyEvent e) {
switch(keycode) {
case KeyEvent.KEYCODE_MENU:
mToolbar.showOverflowMenu();
return true;
}
return super.onKeyUp(keycode, e);
}