I've created my own set of alphabetical sounds (replacing the regular a,b,c,..z). I'd like to customize the talkback functionality so it uses my own sounds instead of the built-in ones when using my own app. This means that when a visually impaired user would try to read the content of a label in my app he'd hear my own sounds. Is this possible? If so, what would be the right way to achieve this?
Assuming your own sounds have a "phonetic spelling" this would be pretty easy. Let's say you had the letter A and you wanted it to pronounce as "A" and not the sound "uh". You could simply replace "A" with "ay" and TalkBack would pronounce it correctly. Assuming this is the case, what you want to do is very easy. If what you have created is actual sounds, and cannot simply use phonetic spellings as I am assuming, as alanv has said, this is impossible. Or at least, involves more than just changes to your app!!!
What you want to do is intercept all accessibility events coming from your application, and then when you intercept the events, replace the content description with your phonetically spelled content description. The tricky part is emulating TalkBack logic for grabbing text from Accessibility events, so that you grab the correct text! Otherwise you end up modifying the wrong string, or just nothing.
If you attach this accessibility delegate to the views within your view heirarchy, you can override the content description of the accessibility node infos, and replace it with your phonetic pronunciations. I attached all relevant portions of my solution. There may be a way to get this working by only mucking with the root view's accessibility delegate, and not the entire view hierarchy. I may investigate more later, but this works just dandy, and is a linear operation on load (and view addition for dynamic content) which isn't bad at all.
Add this code to your onCreate method, and modify the "convertText" function to suit your needs, and you should be all set!
final View.AccessibilityDelegate accessiblityDelegate = new View.AccessibilityDelegate() {
String convertText(String argString) {
//Do your phonetic conversion in here!
//A little Regex. A little String replacement and you're golden!
return argString;
}
#Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo nodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, nodeInfo);
String text = null;
if (nodeInfo.getContentDescription() != null) {
text = convertText(nodeInfo.getContentDescription().toString());
} else if (nodeInfo.getText() != null) {
text = convertText(nodeInfo.getText().toString());
} else if (host instanceof TextView) {
TextView textView = (TextView)host;
text = convertText(textView.getText().toString());
}
if (text != null) nodeInfo.setContentDescription(text);
}
};
rootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
addAccessibilityDelegateToViews(v);
}
private void addAccessibilityDelegateToViews(View v) {
v.setAccessibilityDelegate(accessiblityDelegate);
if (v instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)v;
for (int i = 0; i < viewGroup.getChildCount(); ++i) {
View view = viewGroup.getChildAt(i);
addAccessibilityDelegateToViews(view);
}
}
}
});
Related
When using a TimePicker set to spinner mode, if I click on a number (minutes or hours), the number keyboard shows up.
But whenever I scroll any of the spinners, the keyboard changes to the text inputType.
How can I avoid this?
I've tried calling timePicker.setAddStatesFromChildren(true) and setting an OnTimeChangedListener, but that won't work, for if I scroll just enough for the spinner to move but not for the time to change, the listener is not triggered but the keyboard changes to text inputType anyway.
Also, timePicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS) is not what I'm looking for, for I still want the keyboard to show up, but only that it won't change its inputType to text.
In the end, I couldn't find which view was making the keyboard appear. I tried removing the next focus from every view inside the TimePicker, but nothing. Then, I thought the problem was that, since I was using a 24-hour format spinner, the view to blame was the hidden AM/PM CustomTextView inside the TimePicker. I made it non-focusable, but still the same issue. So I concluded that the problem was somewhere in the implementation of the TimePicker itself, who manages some event and displays the keyboard.
So I decided to iterate over the NumberPickers inside TimePicker —which are three— and set an OnScrollListener on them that hides the keyboard. But still, I get to see the text keyboard appearing before being dismissed. But that's the best I've managed to do.
public static <T extends View> List<T> getViewsByClassNameFromView(ViewGroup viewGroup, Class<T> clazz) {
final List<T> matches = new LinkedList<>();
final int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = viewGroup.getChildAt(i);
if (clazz.isInstance(child)) {
matches.add((T) child);
} else if (child instanceof ViewGroup) {
matches.addAll(getViewsByClassNameFromView((ViewGroup) child, clazz));
}
}
return matches;
}
public void hideSoftKeyboard(View view) {
InputMethodManager imm =
(InputMethodManager) view.getContext().getApplicationContext()
.getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
view.clearFocus();
}
private void fixTimePicker() {
final List<NumberPicker> numberPickers = ViewUtil.getViewsByClassNameFromView(timePicker, NumberPicker.class);
for (final NumberPicker numberPicker: numberPickers) {
numberPicker.setOnScrollListener(new NumberPicker.OnScrollListener() {
#Override
public void onScrollStateChange(NumberPicker view, int scrollState) {
hideSoftKeyboard(view);
}
});
}
}
android:descendantFocusability="blocksDescendants" use this attribute in DatePicker Xml and it will resolve your issue.
I have extended EditTextPreference, but the Dialog Message won't display. This happens if I add the dialogMessage programatically or in the the preferences.xml.
Here is my onBindDialogView:
AutoCompleteTextView editText = mEditText;
editText.setText(getText());
ViewParent oldParent = editText.getParent();
if (oldParent != view) {
if (oldParent != null) {
((ViewGroup) oldParent).removeView(editText);
}
onAddEditTextToDialogView(view, editText);
}
Is the dialog message really absent? It's probably there but its text color might make it less (or not) visible. (Or try to dismiss software keyboard). Try experimenting with dialog messages having a number of "\n" characters and see if that affects dialog layout. If so, it means the dialog message is actually there but camouflaged too well.
EditTextPreference brings a text view (in the preference_dialog_edittext.xml) that replaces the existing one (in the alert_dialog.xml) for the dialog message, but unfortunately with different text style, which might cause a visibility problem under certain themes. Even their sizes are different.
One solution might be to obtain the text color and size from the original text view to be replaced and apply them to the new one, but I would suggest retaining the original text view instead, because it's more likely to be visually consistent if there are any future UI changes. Try adding the following overrides
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
builder.setMessage(getDialogMessage()); // bring back the original text view
}
protected void onAddEditTextToDialogView(View dialogView, EditText editText) {
int id = getContext().getResources().getIdentifier("edittext_container", "id", "android");
ViewGroup container = (ViewGroup) dialogView.findViewById(id);
container.removeAllViews(); // remove the new text view
super.onAddEditTextToDialogView(dialogView, editText);
}
If you think the dialog message and the edittext view is too far apart, they can be brought together a little closer by adding another override:
protected void showDialog(Bundle state) {
super.showDialog(state);
int id = getContext().getResources().getIdentifier("message", "id", "android");
TextView message = (TextView) getDialog().findViewById(id);
message.setPadding(message.getPaddingLeft(), message.getPaddingTop(), message.getPaddingRight(), 0);
}
and add the following line in the onAddEditTextToDialogView method after calling removeAllViews:
container.setPadding(container.getPaddingLeft(), 0, container.getPaddingRight(), container.getPaddingBottom());
Why links in ListView are lost, when scrolling? From debugging it's clear, that spans are not added second time on a TextView from the convertView.
Here's a piece of code which is called from adapter's getView.
...
String body = MyItemDetails.getBody(); // String to linkify
final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
viewHolder.textView.setText(spannable);
viewHolder.textView.setTextIsSelectable(true); // adds additional spans
viewHolder.textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
viewHolder.textView.setAutoLinkMask(Linkify.WEB_URLS);
...
MyCustomUri.addHashtagSpans() creates a SpannableString with MyCustomSpan with extends URLSpan.
Problem is that when I scroll up and down in the ListView links are lost. Whereas when screen is opened 1st time it's set correctly.
Now I made a dirty fix by disabling reuse of convertView :( Any ideas how to solve this problem better?
Some of the spannable information is likely being lost when the textview's data is written to a parcel for retention.
See TextView.onSaveInstanceState(), TextView.onRestoreInstanceState(), and TextView.SavedState.
It can often be very frustrating to determine what android will and will not retain. I often just setSaveEnabled(false) on my views to disable the unpredictable default behaviours of the base widgets.
Also, the viewholder pattern is only really intended for retaining view/layout instance hierarchies. To save you from having to inflate or find your views every getView(). It's always your responsibility to update a view's data when presenting it from getView().
You don't need to completely disable the viewholder pattern, instead just simply update the text every getView(), as you may already be doing.
Hello Use this custom class
public class MyCustomSpannable extends ClickableSpan {
String Url;
Context mContext;
public MyCustomSpannable(String Url, Context context) {
this.Url = Url;
mContext = context;
}
#Override
public void updateDrawState(TextPaint ds) {
// Customize your Text Look if required
ds.setColor(mContext.getResources().getColor(R.color.red_text));
ds.setFakeBoldText(true);
// ds.setStrikeThruText(true);
ds.setTypeface(CommonFunctios.getfontNormal(mContext));
// ds.setUnderlineText(true);
// ds.setShadowLayer(10, 1, 1, Color.WHITE);
// ds.setTextSize(15);
}
#Override
public void onClick(View widget) {
}
public String getUrl() {
return Url;
}
}
and in adapter replace your code with this
String text = holder.txt_terms.getText().toString();
SpannableStringBuilder stringBuilder = new SpannableStringBuilder(text);
MyCustomSpannable customSpannable = new MyCustomSpannable(text,
mcontext) {
#Override
public void onClick(View widget) {
Log.e("on click", "message");
((OpticalOffersActivity) mcontext).callDialogBox(position);
}
};
stringBuilder.setSpan(customSpannable, 0, text.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
holder.txt_terms.setText(stringBuilder, BufferType.SPANNABLE.SPANNABLE);
holder.txt_terms.setMovementMethod(LinkMovementMethod.getInstance());
Hope it will help you.
if(convertView==null)
{
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
...
String body = MyItemDetails.getBody(); // String to linkify
final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
viewHolder.textView.setText(spannable);
viewHolder.textView.setTextIsSelectable(true); // adds additional spans
viewHolder.textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
viewHolder.textView.setAutoLinkMask(Linkify.WEB_URLS);
...
That spannable code must be placed outside the if-else loop in the getView() method, like the way I did it in the above code.
There are a couple problems at play here, so let me address them one at a time. The issues you've asked about directly (links disappearing) is a side effect of the fact that the auto linking behavior in TextView doesn't necessarily work that well when you are also adding your own spans to the text manually...best not to use it. Remove the setAutoLinkMask() trigger and the disappearing links issue will go away.
Instead, you can easily add the same web linking behavior directly into your text span with Linkify. However, this is only part of your problem. The MovementMethod you have chosen isn't really compatible with clickable links. The reason it (partially) works in your code now is because the auto link mask is causing the MovementMethod of the view to be secretly massaged under the hood to a LinkMovementMethod...which then gets reset after the view is recycled. A pattern I typically use (applied to your code example) would be:
final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
Linkify.addLinks(spannable, Linkify.WEB_URLS);
viewHolder.textView.setText(spannable);
addLinkMovementMethod(textView);
Where addLinkMovementMethod() is a helper I have that looks like this:
private void addLinkMovementMethod(TextView t) {
MovementMethod m = t.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod)) {
if (t.getLinksClickable()) {
t.setMovementMethod(LinkMovementMethod.getInstance());
}
}
}
This simply keeps from resetting the value on each view recycle if it isn't necessary. The previous code block will give you links that click properly and never disappear...
However, I'm guessing from the methods you've called that you are also attempting to make the linked text in the list selectable (e.g. calling setTextIsSelectable() and choosing the ArrowKeyMovementMethod). This gets a little trickier because of the MovementMethod issue I discussed above. In order to create a MovementMethod that supports both link clicks and text selection, I'll direct you to this existing SO post on the subject which includes sample code on the customizations you need to make: Can a TextView be selectable AND contain links?
Is it possible to get a list of all Windows in my Android app?
If not, is it possible to get notifications on creation of a new View or a Window?
Cheers :)
For example: I would like to know if there's a visible keyboard view on the screen, or if there's an alert dialog on screen. Is that possible? Can I get the View or Window instance holding it?
Yes this is possible in a number of different ways. All views being displayed on the screen are added to a ViewGroup, which are usually layouts such as R.layout.main, LinearLayout, RelativeLayout, etc.
You can access the views at runtime, after the layouts have been built, using a handler such as onWindowFocusChanged:
#Override
public void onWindowFocusChanged(boolean hasFocus) {
int count = myLayout.getChildCount();
for(int i = 0; i < count; i++)
{
View v = myLayout.getChildAt(i);
...
}
}
You can simply set up a thread inside onWindowFocusChanged that would notify you if a keyboard is created by constantly checking the number of children views of the current layout.
For the keyboard issue, you can use your own keyboard view instance with KeyboardView in your layout: http://developer.android.com/reference/android/inputmethodservice/KeyboardView.html
Use the same principle for the other views you want to handle: manage them yourself in your layout. I don't know if you can in the software you plan to do but this is a way which can work.
You can only get views which are managed by your application.
This includes all views except the status and navigation bars(for higher than HoneyComb). If you choose to have your own InputMethod, that view can be yours as well but you'll need to register the proper keyboard views. See this question for more on that.
Otherwise, if you want to get all the views in your window:
ViewGroup decor = (ViewGroup)activity.getWindow().getDecorView();
int count = decor.getChildCount();
for(int i = 0; i < count; i++) {
View view = decor.getChildAt(i); //voila
}
hey use this code this will help you to find if any dialog is created in your activity
class MyActivity extends Activity {
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
Log.d("TAG", "New Window ATTACHED");
}
}
onAttachedToWindow will be called every time user creates new dialog or something
I have a situation where I would like the user to complete a sentence for me. For example, consider a EditText with a hint of "The last time I ". Normally, when a user clicks an EditText, the hint disappears, but I would like it to stay. Additionally, I would like the text to be permanent, so that it cannot be erased... leaving the user with only one option... complete the sentence.
The first part is fairly simple, just use the setText() method of EditText to place the hint. The difficult part is the latter. How can I have text in an EditText that the user cannot erase?
Well couldn't you do it in code? Some algorithim like, if the text is less than 16 characters (length of "The last time I ") then set the text to that. Therefore whenever they clicked it, if they tried to erase it, it would just go back to the default text.
Also, another idea..why don't you just make a TextView thats right edge aligns with the left edge of the EditText box, the user would never know that it was another box. This is acutally the best solution, if you don't want the text ever to be edited, just make it a TextView
Described problem can be solved using android.text.TextWatcher.
public class CompleteSentenceWathcher implements TextWatcher {
private final String initialText;
private int start;
private int after;
private int count;
public CompleteSentenceWathcher(String initialText) {
this.initialText = initialText;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
this.start = start;
this.count = count;
this.after = after;
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if(start < initialText.length()) {
if(s.toString().startsWith(initialText)) {
return;
}
if(count >= 1 && after == 0) {
if(start+count+1 <= initialText.length()) {
s.replace(start, start+count, initialText.substring(start, start+count+1));
} else {
s.replace(start, start, initialText.substring(start, start+1));
}
} else if(count == 0 && after >= 1) {
s.delete(start, start+after);
}
}
}
}
Create an instance of EditText and add the TextWatcher.
EditText editText = new EditText(this);
editText.setText("I love");
editText.addTextChangedListener(new CompleteSentenceWathcher(editText.getText().toString()));
I've implemented this with an InputFilter, where _PERMANENT_HINT_TEXT is the text at the end of the EditText that I don't want the user to be able to modify. I recommend adding a color span to it, so that it is grayed out to hopefully look like a hint/disabled section of text. This should hopefully improve the UX as they should automatically assume it is unmodifiable, and not just wonder why some part of the EditText (that they usually can completely change) isn't "working". This approach allowed the text to be set after
the InputFilter was set on the EditText, which was a requirement for me since I used this on an EditTextPreference.
To be clear, I needed the permanent text to exist at the end of the EditText, instead of the beginning, but that should be symmetrical to my implementation.
new InputFilter() {
#Override
public CharSequence filter(CharSequence source, int source_start, int source_end,
Spanned destination, int destination_start, int destination_end) {
final int protected_text_start = (TextUtils.isEmpty(destination)? source.length() : destination.length()) - _PERMANENT_HINT_TEXT.length();
// Allows input into unprotected region
if (source_start + destination_start - source_end < protected_text_start)
return null;
// Prevents deletion of protected region
else if (TextUtils.isEmpty(source))
return destination.subSequence(destination_start, destination_end);
// Ignores insertion into protected region
else
return "";
}
}
use EditText.setFilters(new InputFilters[] { /* InputFilter goes here */ }; to add it to the desired EditText.
Just checking for the length wouldn't be adequate... I could type "This is a really long text I put into the box" and it would accept it even though it doesn't begin with "The last time I" string.
Personally, I would probably go for the prevention method suggested of using a TextView over that of a check on the way out. But if you're going to validate it afterwards, you'd actually need to check the beginning of the returned string.