Background
The fragment creates it's views and then starts a network operation. When the network operation is completed, various types of subviews are created and added to the fragment's view, based on the results of the network operation.
Problem
If a Spinner is added by the fragment, it's initial value is announced by TalkBack. This is very undesirable because the Spinner is usually buried deep within the form.
Failed Solutions
I've tried:
wrapping addView with setImportantForAccessibility
not setting the initial Spinner value (not an acceptable solution in any case) but the initial value is still announced when added
setting the Spinner's contentDescription to non-breaking space before adding and restoring it in onAttachedToWindow *
Question
Spinner is created and dynamically added, after the fragment is initially created. How can I prevent TalkBack from announcing the initial value of the Spinner?
Here's a workaround that I did in my project which worked as expected i.e. Spinner's content is only read out by TalkBack when it's in focus.
I subclassed the AppCompatSpinner class and overrode its onInitializeAccessibilityEvent function as follows:
#Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
super.onInitializeAccessibilityEvent(event);
}
}
1) set important for accessibility to false to all spinners in your form in onCreate().
spinner.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
2) setOnHoverListener to each spinner and inside it enable the accessibility again and send accessibility events to announce them properly.
spinner.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
spinner.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
spinner.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
spinner.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
3) setOnTouchListener to each spinner to perform click after double tap.
spinner.performClick();
Related
When I have a recycler view with 12 items, when accessibility focus goes onto cell number 3, for example, talkback announces "Double tap to activate. Item 3 of 12".
I want to keep the action but stop it from announcing the item position and item count. How can I do this? I have tried to assign a delegate to the recycler view but not sure what to override in the delegate.
How can I do this?
So I figured it out. An AccessibilityNodeInfo has a method called SetCollectionInfo(). CollectionInfo has properties like rowCont and columnCount.
I simply set the info to null.
Note, the below is xamarin:
private class TabLayoutTabAccessibilityDelegate: View.AccessibilityDelegate
{
public override void OnInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)
{
base.OnInitializeAccessibilityNodeInfo(host, info);
info.SetCollectionInfo(null);
}
}
This is a functionality that should not be disabled: it lets people using Talkback know that they are in a list, how long is the list, which element is currently focused (are there more elements above/below).
Question 1
I was looking at the example code on this page that uses SortedList with RecyclerView.
At line 127, after the CheckBox status changed, recalculatePositionOfItemAt() method was used. The javadocs for SortedList<T> says that recalculatePositionOfItemAt() is for adjusting item positions without triggering onChanged() callback. And updateItemAt() will call onChanged() and/or onMoved() if necessary.
In the case of the example code, the item's field boolean mIsDone changed. I thought updateItemAt() would be more appropriate here?
Question 2 (related)
I tried to play around to use updateItemAt() with a sorted list, but some times java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling was triggered and I found lines 122-125, 152-154 in the example code helps to avoid the exception. I thought the checkbox checked status changing event can only happen when the user checks/unchecks the checkbox. Why are these lines necessary? Some times random tapping or scrolling events far away from the checkboxes can trigger the event?
I have several EditViews in my activity's layout with editable enabled. When the activity's view is brought up, the first EditView gains focus: there is a blinking cursor and soft keyboard. However I can't get focus on any of the other EditViews in order to type into them. I have them enabled and editable. What other things could cause this?
If I simply load the layout in the activity without running any of my code, this problem does not occur, so it must be caused in the code at runtime--this is a ton of ported legacy code that is too much to share, but I wonder what types of properties or method side effects could cause enabled EditViews to not receive focus when tapped.
Another cause of this is having a Touch listener that does not set the event's TouchEventArgs.Handled property to true (I'm using Xamarin/monodroid vernacular here).
This was my code's problem. You must be sure to set TouchEventArgs.Handled to false. It was defaulting to true, which caused Android's handler which sets focus to not be run in the chain of responsibility!
_view.Touch += (sender, args) =>
{
Click(sender, new EventArgs());
args.Handled = false;
};
I have an unusual issue with my ListView. I currently have a "deselectAll()" method which iterates through the items in my ListView and sets them to unchecked (the items implement the Checkable interface). The "checked" variable gets changed correctly (the view reports as not being checked), but the visual indicator (in this case, a background change) does not show the view as unchecked (the background stays the color of a checked item).
I am iterating and deselecting through my listview like so (I also added my declerations):
private ListView vw_entryList;
private void deselectAll() {
for (int i = 0; i < sAdapter.getCount(); i++) {
((Entry)vw_entryList.getItemAtPosition(i)).setChecked(false);
}
}
The code for my implemented setChecked() is as follows:
public void setChecked(boolean checked) {
_checked = checked;
if (checked) {
setBackgroundResource(R.drawable.listview_checked);
}
else {
setBackgroundResource(R.drawable.listview_unchecked);
}
invalidate();
}
It should be noted that when the items are clicked, they are toggled between checked and unchecked in the OnItemClickListener, and this works ok, with the background change and everything. The code for toggling is very similar:
public void toggle() {
_checked = !_checked;
setBackgroundResource(_checked ?
R.drawable.listview_checked : R.drawable.listview_unchecked);
invalidate();
}
The only difference I can see is where the methods are called from. toggle() is called from within the OnItemClickListener.onClick() method, while my deselectAll() is called from within a button's standard OnClickListener, both in the same class. Does anyone have any ideas as to why the background doesn't change when I call my deselectAll() function?
Do you have custom, non-standard color for the background? If so you might take a look at http://www.curious-creature.org/2008/12/22/why-is-my-list-black-an-android-optimization/ - it boils down to setting android:cacheColorHint attribute of your list to the background color. Maybe that will help.
Edited after further discussion:
I think you need to call getAdapter().notifyDataSetChanged() on the List rather than invalidate(). List is really build in the way that it is relying on adapter to provide the data. What you are doing in fact you have an implicit adapter - Entry is really kept in the adapter and by setting checked, you are changing the data model really, but if you do not call notifyDataSetChanged() the list does not really know that the model has changed and will not recreate the views (invalidate() will only redraw the existing ones).
After trying everything (thanks for your help Jarek), I found a solution that works for my purposes. Instead of implicitly calling the setChecked() within the view that was clicked, I leave it up to the setItemChecked() method within the ListView class.
My updated code:
private void deselectAll() {
for (int i = 0; i < sAdapter.getCount(); i++) {
vw_entryList.setItemChecked(i, false);
}
}
My best guess is that the ListView knows that its items implement the Checkable class, and thus requires itself to be the handler of all item operations. Something along those lines. If anyone can explain in more detail why this solution works while the others did not, I'll reward them with the answer and an upvote.
I am writing a music player that uses a custom Adapter extending BaseAdapter (efficiency adapter) that I want to display in an AlertDialog using setAdapter() where the user can either click on one of the songs to switch to that position in the playlist OR check songs to remove from the playlist. I tried using a custom click listener so that a user could just long click to remove the item from the list but the listview just doesn't work right... it was removing the wrong items (the ones at the end) even though the ArrayList contained the correct playlist items... (when I removed the item from the ArrayList, I passed it to the adapter which called notifyDataSetChanged... but that just didn't work as I mentioned. There is definitely a bug in the AlertDialog ListView... because there is no reason for it to have popped off the results from the end rather than the correct item.
So... the next method I would like to try is to use the setMultiChoiceItems() method of the AlertDialog... but it appears that it doesn't work with a custom adapter... only simple arrays. Will I have to subclass AlertDialog and Override the setMultiChoiceItems() method or is there a way I can make it work with an ArrayAdapter?
Basically, I can't figure out how to even iterate the list that the AlertDialog creates or whether it even passes that view somehow. In addition, I don't think I can even listen to clicks on checkboxes if I add those to the row. Any help will be greatly appreciated.
EDIT: Asking questions here is like magic... I answered my own question... this is how I did it. I added a hint to each checkbox which is the position of the item in the ArrayList. Then I used OnCheckedChangeListener to capture the selections. When you set a hint it adds text to the checkbox... since the background of the AlertDialog is white (even for clicked items?) I just set the hint text color to transparent.
holder.check.setHintTextColor(Color.TRANSPARENT);
holder.check.setHint(String.valueOf(position));
holder.check.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
int position = Integer.parseInt((String) buttonView.getHint());
Log.v("onCheckedChanged", "Checked: "+isChecked+" returned: "+position+" which should be "+getItem(position).name);
}
});
Refer This and This
then
Pass a reference to byte[] in setMultiChoiceItems().
final boolean[] booleans = {false, true, false, true, false, false, false};
Then check the value of booleans inside setPositiveButton().
If you need to pass this AlertDialog around, then extend AlertDialog and have create a field boolean as described in 1.