/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.apis.accessibility; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Build; import android.os.Bundle; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.example.android.apis.R; /** * Demonstrates how to implement accessibility support of custom views. Custom view * is a tailored widget developed by extending the base classes in the android.view * package. This sample shows how to implement the accessibility behavior via both * inheritance (non backwards compatible) and composition (backwards compatible). * <p> * While the Android framework has a diverse portfolio of views tailored for various * use cases, sometimes a developer needs a specific functionality not implemented * by the standard views. A solution is to write a custom view that extends one the * base view classes. While implementing the desired functionality a developer should * also implement accessibility support for that new functionality such that * disabled users can leverage it. * </p> */ public class CustomViewAccessibilityActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.custom_view_accessibility); } /** * Demonstrates how to enhance the accessibility support via inheritance. * <p> * <strong>Note:</strong> Using inheritance may break your application's * backwards compatibility. In particular, overriding a method that takes as * an argument or returns a class not present on an older platform * version will prevent your application from running on that platform. * For example, {@link AccessibilityNodeInfo} was introduced in * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH API 14}, thus overriding * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) * View.onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} * will prevent you application from running on a platform older than * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH API 14}. * </p> */ public static class AccessibleCompoundButtonInheritance extends BaseToggleButton { public AccessibleCompoundButtonInheritance(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); // We called the super implementation to let super classes // set appropriate event properties. Then we add the new property // (checked) which is not supported by a super class. event.setChecked(isChecked()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); // We called the super implementation to let super classes set // appropriate info properties. Then we add our properties // (checkable and checked) which are not supported by a super class. info.setCheckable(true); info.setChecked(isChecked()); // Very often you will need to add only the text on the custom view. CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { info.setText(text); } } @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(event); // We called the super implementation to populate its text to the // event. Then we add our text not present in a super class. // Very often you will need to add only the text on the custom view. CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { event.getText().add(text); } } } /** * Demonstrates how to enhance the accessibility support via composition. * <p> * <strong>Note:</strong> Using composition ensures that your application is * backwards compatible. The android-support-v4 library has API that allow * using the accessibility APIs in a backwards compatible manner. * </p> */ public static class AccessibleCompoundButtonComposition extends BaseToggleButton { public AccessibleCompoundButtonComposition(Context context, AttributeSet attrs) { super(context, attrs); tryInstallAccessibilityDelegate(); } public void tryInstallAccessibilityDelegate() { // If the API version of the platform we are running is too old // and does not support the AccessibilityDelegate APIs, do not // call View.setAccessibilityDelegate(AccessibilityDelegate) or // refer to AccessibilityDelegate, otherwise an exception will // be thrown. // NOTE: The android-support-v4 library contains APIs the enable // using the accessibility APIs in a backwards compatible fashion. if (Build.VERSION.SDK_INT < 14) { return; } // AccessibilityDelegate allows clients to override its methods that // correspond to the accessibility methods in View and register the // delegate in the View essentially injecting the accessibility support. setAccessibilityDelegate(new AccessibilityDelegate() { @Override public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { super.onInitializeAccessibilityEvent(host, event); // We called the super implementation to let super classes // set appropriate event properties. Then we add the new property // (checked) which is not supported by a super class. event.setChecked(isChecked()); } @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); // We called the super implementation to let super classes set // appropriate info properties. Then we add our properties // (checkable and checked) which are not supported by a super class. info.setCheckable(true); info.setChecked(isChecked()); // Very often you will need to add only the text on the custom view. CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { info.setText(text); } } @Override public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { super.onPopulateAccessibilityEvent(host, event); // We called the super implementation to populate its text to the // event. Then we add our text not present in a super class. // Very often you will need to add only the text on the custom view. CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { event.getText().add(text); } } }); } } /** * This is a base toggle button class whose accessibility is not tailored * to reflect the new functionality it implements. * <p> * <strong>Note:</strong> This is not a sample implementation of a toggle * button, rather a simple class needed to demonstrate how to refine the * accessibility support of a custom View. * </p> */ private static class BaseToggleButton extends View { private boolean mChecked; private CharSequence mTextOn; private CharSequence mTextOff; private Layout mOnLayout; private Layout mOffLayout; private TextPaint mTextPaint; public BaseToggleButton(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.buttonStyle); } public BaseToggleButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); TypedValue typedValue = new TypedValue(); context.getTheme().resolveAttribute(android.R.attr.textSize, typedValue, true); final int textSize = (int) typedValue.getDimension( context.getResources().getDisplayMetrics()); mTextPaint.setTextSize(textSize); context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true); final int textColor = context.getResources().getColor(typedValue.resourceId); mTextPaint.setColor(textColor); mTextOn = context.getString(R.string.accessibility_custom_on); mTextOff = context.getString(R.string.accessibility_custom_off); } public boolean isChecked() { return mChecked; } public CharSequence getText() { return mChecked ? mTextOn : mTextOff; } @Override public boolean performClick() { final boolean handled = super.performClick(); if (!handled) { mChecked ^= true; invalidate(); } return handled; } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOnLayout == null) { mOnLayout = makeLayout(mTextOn); } if (mOffLayout == null) { mOffLayout = makeLayout(mTextOff); } final int minWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()) + getPaddingLeft() + getPaddingRight(); final int minHeight = Math.max(mOnLayout.getHeight(), mOffLayout.getHeight()) + getPaddingLeft() + getPaddingRight(); setMeasuredDimension(resolveSizeAndState(minWidth, widthMeasureSpec, 0), resolveSizeAndState(minHeight, heightMeasureSpec, 0)); } private Layout makeLayout(CharSequence text) { return new StaticLayout(text, mTextPaint, (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)), Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); canvas.translate(getPaddingLeft(), getPaddingRight()); Layout switchText = mChecked ? mOnLayout : mOffLayout; switchText.draw(canvas); canvas.restore(); } } }