to top
Android APIs
 

CustomViewAccessibilityActivity.java

← Back

The file containing the source code shown below is located in the corresponding directory in <sdk>/samples/android-<version>/...

/*
 * Copyright (C) 2011 The Android开源工程
 *
 * 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();
        }
    }
}