Skip to content

Most visited

Recently visited

navigation
BasicAccessibility / src / com.example.android.basicaccessibility /

DialView.java

1
/*
2
 * Copyright (C) 2013 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.example.android.basicaccessibility;
18
 
19
import android.annotation.TargetApi;
20
import android.content.Context;
21
import android.graphics.Canvas;
22
import android.graphics.Color;
23
import android.graphics.Paint;
24
import android.os.Build;
25
import android.util.AttributeSet;
26
import android.view.View;
27
import android.view.accessibility.AccessibilityEvent;
28
 
29
/**
30
 * Custom view to demonstrate accessibility.
31
 *
32
 * <p>This view does not use any framework widgets, so does not get any accessibility features
33
 * automatically. Instead, we use {@link android.view.accessibility.AccessibilityEvent} to provide accessibility hints to
34
 * the OS.
35
 *
36
 * <p>For example, if TalkBack is enabled, users will be able to receive spoken feedback as they
37
 * interact with this view.
38
 *
39
 * <p>More generally, this view renders a multi-position "dial" that can be used to select a value
40
 * between 1 and 4. Each time the dial is clicked, the next position will be selected (modulo
41
 * the maximum number of positions).
42
 */
43
public class DialView extends View {
44
    private static int SELECTION_COUNT = 4;
45
 
46
    private static float FONT_SIZE = 40f;
47
    private float mWidth;
48
    private float mHeight;
49
    private float mWidthPadded;
50
    private float mHeightPadded;
51
    private Paint mTextPaint;
52
    private Paint mDialPaint;
53
    private float mRadius;
54
    private int mActiveSelection;
55
 
56
    /**
57
     * Constructor that is called when inflating a view from XML. This is called
58
     * when a view is being constructed from an XML file, supplying attributes
59
     * that were specified in the XML file.
60
     *
61
     * <p>In our case, this constructor just calls init().
62
     *
63
     * @param context The Context the view is running in, through which it can
64
     *                access the current theme, resources, etc.
65
     * @param attrs   The attributes of the XML tag that is inflating the view.
66
     * @see #View(android.content.Context, android.util.AttributeSet, int)
67
     */
68
    public DialView(Context context, AttributeSet attrs) {
69
        super(context, attrs);
70
        init();
71
    }
72
 
73
    /**
74
     * Helper method to initialize instance variables. Called by constructor.
75
     */
76
    private void init() {
77
        // Paint styles used for rendering are created here, rather than at render-time. This
78
        // is a performance optimization, since onDraw() will get called frequently.
79
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
80
        mTextPaint.setColor(Color.BLACK);
81
        mTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
82
        mTextPaint.setTextAlign(Paint.Align.CENTER);
83
        mTextPaint.setTextSize(FONT_SIZE);
84
 
85
        mDialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
86
        mDialPaint.setColor(Color.GRAY);
87
 
88
        // Initialize current selection. This will store where the dial's "indicator" is pointing.
89
        mActiveSelection = 0;
90
 
91
        // Setup onClick listener for this view. Rotates between each of the different selection
92
        // states on each click.
93
        //
94
        // Notice that we call sendAccessibilityEvent here. Some AccessibilityEvents are generated
95
        // by the system. However, custom views will typically need to send events manually as the
96
        // user interacts with the view. The type of event sent will vary, depending on the nature
97
        // of the view and how the user interacts with it.
98
        //
99
        // In this case, we are sending TYPE_VIEW_SELECTED rather than TYPE_VIEW_CLICKED, because
100
        // clicking on this view selects a new value.
101
        //
102
        // We will give our AccessibilityEvent further information about the state of the view in
103
        // onPopulateAccessibilityEvent(), which will be called automatically by the system
104
        // for each AccessibilityEvent.
105
        setOnClickListener(new OnClickListener() {
106
            @Override
107
            public void onClick(View v) {
108
                // Rotate selection to the next valid choice.
109
                mActiveSelection = (mActiveSelection + 1) % SELECTION_COUNT;
110
                // Send an AccessibilityEvent, since the user has interacted with the view.
111
                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
112
                // Redraw the entire view. (Inefficient, but this is sufficient for demonstration
113
                // purposes.)
114
                invalidate();
115
            }
116
        });
117
    }
118
 
119
    /**
120
     * This is where a View should populate outgoing accessibility events with its text content.
121
     * While this method is free to modify event attributes other than text content, doing so
122
     * should normally be performed in
123
     * {@link #onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent)}.
124
     * <p/>
125
     * <p>Note that the behavior of this method will typically vary, depending on the type of
126
     * accessibility event is passed into it. The allowed values also very, and are documented
127
     * in {@link android.view.accessibility.AccessibilityEvent}.
128
     * <p/>
129
     * <p>Typically, this is where you'll describe the state of your custom view. You may also
130
     * want to provide custom directions when the user has focused your view.
131
     *
132
     * @param event The accessibility event which to populate.
133
     */
135
    @Override
136
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
137
    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
138
        super.onPopulateAccessibilityEvent(event);
139
 
140
        // Detect what type of accessibility event is being passed in.
141
        int eventType = event.getEventType();
142
 
143
        // Common case: The user has interacted with our view in some way. State may or may not
144
        // have been changed. Read out the current status of the view.
145
        //
146
        // We also set some other metadata which is not used by TalkBack, but could be used by
147
        // other TTS engines.
148
        if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED ||
149
                eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
150
            event.getText().add("Mode selected: " + Integer.toString(mActiveSelection + 1) + ".");
151
            event.setItemCount(SELECTION_COUNT);
152
            event.setCurrentItemIndex(mActiveSelection);
153
        }
154
 
155
        // When a user first focuses on our view, we'll also read out some simple instructions to
156
        // make it clear that this is an interactive element.
157
        if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
158
            event.getText().add("Tap to change.");
159
        }
160
    }
162
 
163
    /**
164
     * This is called during layout when the size of this view has changed. If
165
     * you were just added to the view hierarchy, you're called with the old
166
     * values of 0.
167
     *
168
     * <p>This is where we determine the drawing bounds for our custom view.
169
     *
170
     * @param w    Current width of this view.
171
     * @param h    Current height of this view.
172
     * @param oldw Old width of this view.
173
     * @param oldh Old height of this view.
174
     */
175
    @Override
176
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
177
        // Account for padding
178
        float xPadding = (float) (getPaddingLeft() + getPaddingRight());
179
        float yPadding = (float) (getPaddingTop() + getPaddingBottom());
180
 
181
        // Compute available width/height
182
        mWidth = w;
183
        mHeight = h;
184
        mWidthPadded = w - xPadding;
185
        mHeightPadded = h - yPadding;
186
        mRadius = (float) (Math.min(mWidth, mHeight) / 2 * 0.8);
187
    }
188
 
189
    /**
190
     * Render view content.
191
     *
192
     * <p>We render an outer grey circle to serve as our "dial", and then render a smaller black
193
     * circle to server as our indicator. The position for the indicator is determined based
194
     * on mActiveSelection.
195
     *
196
     * @param canvas the canvas on which the background will be drawn
197
     */
198
    @Override
199
    protected void onDraw(Canvas canvas) {
200
        super.onDraw(canvas);
201
        // Draw dial
202
        canvas.drawCircle(mWidth / 2, mHeight / 2, (float) mRadius, mDialPaint);
203
 
204
        // Draw text labels
205
        final float labelRadius = mRadius + 10;
206
        for (int i = 0; i < SELECTION_COUNT; i++) {
207
            float[] xyData = computeXYForPosition(i, labelRadius);
208
            float x = xyData[0];
209
            float y = xyData[1];
210
            canvas.drawText(Integer.toString(i + 1), x, y, mTextPaint);
211
        }
212
 
213
        // Draw indicator mark
214
        final float markerRadius = mRadius - 35;
215
        float[] xyData = computeXYForPosition(mActiveSelection, markerRadius);
216
        float x = xyData[0];
217
        float y = xyData[1];
218
        canvas.drawCircle(x, y, 20, mTextPaint);
219
    }
220
 
221
    /**
222
     * Compute the X/Y-coordinates for a label or indicator, given the position number and radius
223
     * where the label should be drawn.
224
     *
225
     * @param pos    Zero based position index
226
     * @param radius Radius where label/indicator is to be drawn.
227
     * @return 2-element array. Element 0 is X-coordinate, element 1 is Y-coordinate.
228
     */
229
    private float[] computeXYForPosition(final int pos, final float radius) {
230
        float[] result = new float[2];
231
        Double startAngle = Math.PI * (9 / 8d);   // Angles are in radiansq
232
        Double angle = startAngle + (pos * (Math.PI / 4));
233
        result[0] = (float) (radius * Math.cos(angle)) + (mWidth / 2);
234
        result[1] = (float) (radius * Math.sin(angle)) + (mHeight / 2);
235
        return result;
236
    }
237
}
This site uses cookies to store your preferences for site-specific language and display options.

Hooray!

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a one-minute survey?
Help us improve Android tools and documentation.