111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette/* 211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Copyright (C) 2013 The Android Open Source Project 311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * 411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Licensed under the Apache License, Version 2.0 (the "License"); 511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * you may not use this file except in compliance with the License. 611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * You may obtain a copy of the License at 711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * 811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * http://www.apache.org/licenses/LICENSE-2.0 911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * 1011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Unless required by applicable law or agreed to in writing, software 1111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * distributed under the License is distributed on an "AS IS" BASIS, 1211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * See the License for the specific language governing permissions and 1411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * limitations under the License. 1511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette */ 1611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 1711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverettepackage com.example.android.supportv4.widget; 1811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 1911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.annotation.TargetApi; 2011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.app.Activity; 2111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.content.Context; 2211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Canvas; 2311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Color; 2411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Paint; 2511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Paint.Align; 2611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Paint.Style; 2711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Rect; 2811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.RectF; 2911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.os.Build; 3011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.os.Bundle; 3111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.support.v4.view.ViewCompat; 3211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 3311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; 3411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.support.v4.widget.ExploreByTouchHelper; 3511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.util.AttributeSet; 3611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.view.MotionEvent; 3711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.view.View; 3811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.view.accessibility.AccessibilityEvent; 3911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport com.example.android.supportv4.R; 4011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 4111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport java.util.ArrayList; 4211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport java.util.List; 4311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 4411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette/** 4511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * This example shows how to use the {@link ExploreByTouchHelper} class in the 4611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Android support library to add accessibility support to a custom view that 4711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * represents multiple logical items. 4811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <p> 4911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * The {@link ExploreByTouchHelper} class wraps 5011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * {@link AccessibilityNodeProviderCompat} and simplifies exposing information 5111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * about a custom view's logical structure to accessibility services. 5211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <p> 5311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * The custom view in this example is responsible for: 5411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <ul> 5511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Creating a helper class that extends {@link ExploreByTouchHelper} 5611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Setting the helper as the accessibility delegate using 5711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * {@link ViewCompat#setAccessibilityDelegate} 5811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Dispatching hover events to the helper in {@link View#dispatchHoverEvent} 5911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * </ul> 6011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <p> 6111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * The helper class implementation in this example is responsible for: 6211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <ul> 6311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Mapping hover event coordinates to logical items 6411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Exposing information about logical items to accessibility services 6511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Handling accessibility actions 6611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <ul> 6711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette */ 6811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverettepublic class ExploreByTouchHelperActivity extends Activity { 6911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 7011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected void onCreate(Bundle savedInstanceState) { 7111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette super.onCreate(savedInstanceState); 7211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 7311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette setContentView(R.layout.explore_by_touch_helper); 7411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 7511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final CustomView customView = (CustomView) findViewById(R.id.custom_view); 7611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 7711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Adds an item at the top-left quarter of the custom view. 7811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette customView.addItem(getString(R.string.sample_item_a), 0, 0, 0.5f, 0.5f); 7911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 8011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Adds an item at the bottom-right quarter of the custom view. 8111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette customView.addItem(getString(R.string.sample_item_b), 0.5f, 0.5f, 1, 1); 8211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 8311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 8411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette /** 8511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Simple custom view that draws rectangular items to the screen. Each item 8611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * has a checked state that may be toggled by tapping on the item. 8711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette */ 8811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette public static class CustomView extends View { 8911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private static final int NO_ITEM = -1; 9011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 9111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private final Paint mPaint = new Paint(); 9211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private final Rect mTempBounds = new Rect(); 9311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private final List<CustomItem> mItems = new ArrayList<CustomItem>(); 9411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private CustomViewTouchHelper mTouchHelper; 9511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 9611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette public CustomView(Context context, AttributeSet attrs) { 9711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette super(context, attrs); 9811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 9911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Set up accessibility helper class. 10011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette mTouchHelper = new CustomViewTouchHelper(this); 10111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette ViewCompat.setAccessibilityDelegate(this, mTouchHelper); 10211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 10311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 10411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 10511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 10611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette public boolean dispatchHoverEvent(MotionEvent event) { 10711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Always attempt to dispatch hover events to accessibility first. 10811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette if (mTouchHelper.dispatchHoverEvent(event)) { 10911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return true; 11011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 11111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 11211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return super.dispatchHoverEvent(event); 11311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 11411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 11511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 11611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette public boolean onTouchEvent(MotionEvent event) { 11711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette switch (event.getAction()) { 11811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette case MotionEvent.ACTION_DOWN: 11911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return true; 12011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette case MotionEvent.ACTION_UP: 12111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final int itemIndex = getItemIndexUnder(event.getX(), event.getY()); 12211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette if (itemIndex >= 0) { 12311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette onItemClicked(itemIndex); 12411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 12511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return true; 12611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 12711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 12811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return super.onTouchEvent(event); 12911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 13011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 13111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette /** 13211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Adds an item to the custom view. The item is positioned relative to 13311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * the custom view bounds and its descriptions is drawn at its center. 13411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * 13511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * @param description The item's description. 13611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * @param top Top coordinate as a fraction of the parent height, range 13711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * is [0,1]. 13811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * @param left Left coordinate as a fraction of the parent width, range 13911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * is [0,1]. 14011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * @param bottom Bottom coordinate as a fraction of the parent height, 14111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * range is [0,1]. 14211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * @param right Right coordinate as a fraction of the parent width, 14311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * range is [0,1]. 14411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette */ 14511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette public void addItem(String description, float top, float left, float bottom, float right) { 14611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final CustomItem item = new CustomItem(); 14711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette item.bounds = new RectF(top, left, bottom, right); 14811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette item.description = description; 14911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette item.checked = false; 15011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette mItems.add(item); 15111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 15211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 15311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 15411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected void onDraw(Canvas canvas) { 15511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette super.onDraw(canvas); 15611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 15711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final Paint paint = mPaint; 15811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final Rect bounds = mTempBounds; 15911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final int height = getHeight(); 16011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final int width = getWidth(); 16111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 16211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette for (CustomItem item : mItems) { 16311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette paint.setColor(item.checked ? Color.RED : Color.BLUE); 16411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette paint.setStyle(Style.FILL); 16511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette scaleRectF(item.bounds, bounds, width, height); 16611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette canvas.drawRect(bounds, paint); 16711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette paint.setColor(Color.WHITE); 16811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette paint.setTextAlign(Align.CENTER); 16911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette canvas.drawText(item.description, bounds.centerX(), bounds.centerY(), paint); 17011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 17111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 17211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 17311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected boolean onItemClicked(int index) { 17411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final CustomItem item = getItem(index); 17511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette if (item == null) { 17611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return false; 17711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 17811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 17911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette item.checked = !item.checked; 18011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette invalidate(); 18111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 18211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Since the item's checked state is exposed to accessibility 18311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // services through its AccessibilityNodeInfo, we need to invalidate 18411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // the item's virtual view. At some point in the future, the 18511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // framework will obtain an updated version of the virtual view. 18611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette mTouchHelper.invalidateVirtualView(index); 18711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 18811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // We also need to let the framework know what type of event 18911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // happened. Accessibility services may use this event to provide 19011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // appropriate feedback to the user. 19111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette mTouchHelper.sendEventForVirtualView(index, AccessibilityEvent.TYPE_VIEW_CLICKED); 19211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 19311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return true; 19411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 19511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 19611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected int getItemIndexUnder(float x, float y) { 19711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final float scaledX = (x / getWidth()); 19811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final float scaledY = (y / getHeight()); 19911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final int n = mItems.size(); 20011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 20111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette for (int i = 0; i < n; i++) { 20211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final CustomItem item = mItems.get(i); 20311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette if (item.bounds.contains(scaledX, scaledY)) { 20411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return i; 20511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 20611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 20711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 20811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return NO_ITEM; 20911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 21011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 21111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected CustomItem getItem(int index) { 21211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette if ((index < 0) || (index >= mItems.size())) { 21311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return null; 21411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 21511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 21611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return mItems.get(index); 21711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 21811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 21911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected static void scaleRectF(RectF in, Rect out, int width, int height) { 22011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette out.top = (int) (in.top * height); 22111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette out.bottom = (int) (in.bottom * height); 22211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette out.left = (int) (in.left * width); 22311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette out.right = (int) (in.right * width); 22411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 22511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 22611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private class CustomViewTouchHelper extends ExploreByTouchHelper { 22711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private final Rect mTempRect = new Rect(); 22811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 22911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette public CustomViewTouchHelper(View forView) { 23011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette super(forView); 23111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 23211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 23311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 23411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected int getVirtualViewAt(float x, float y) { 23511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // We also perform hit detection in onTouchEvent(), and we can 23611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // reuse that logic here. This will ensure consistency whether 23711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // accessibility is on or off. 23811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final int index = getItemIndexUnder(x, y); 23911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette if (index == NO_ITEM) { 24011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return ExploreByTouchHelper.INVALID_ID; 24111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 24211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 24311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return index; 24411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 24511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 24611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 24711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { 24811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Since every item should be visible, and since we're mapping 24911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // directly from item index to virtual view id, we can just add 25011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // every available index in the item list. 25111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final int n = mItems.size(); 25211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette for (int i = 0; i < n; i++) { 25311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette virtualViewIds.add(i); 25411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 25511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 25611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 25711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 25811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected void onPopulateEventForVirtualView( 25911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette int virtualViewId, AccessibilityEvent event) { 26011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final CustomItem item = getItem(virtualViewId); 26111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette if (item == null) { 26211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette throw new IllegalArgumentException("Invalid virtual view id"); 26311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 26411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 26511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // The event must be populated with text, either using 26611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // getText().add() or setContentDescription(). Since the item's 26711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // description is displayed visually, we'll add it to the event 26811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // text. If it was only used for accessibility, we would use 26911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // setContentDescription(). 27011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette event.getText().add(item.description); 27111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 27211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 27311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 27411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected void onPopulateNodeForVirtualView( 27511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette int virtualViewId, AccessibilityNodeInfoCompat node) { 27611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final CustomItem item = getItem(virtualViewId); 27711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette if (item == null) { 27811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette throw new IllegalArgumentException("Invalid virtual view id"); 27911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 28011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 28111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Node and event text and content descriptions are usually 28211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // identical, so we'll use the exact same string as before. 28311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette node.setText(item.description); 28411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 28511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Reported bounds should be consistent with those used to draw 28611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // the item in onDraw(). They should also be consistent with the 28711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // hit detection performed in getVirtualViewAt() and 28811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // onTouchEvent(). 28911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final Rect bounds = mTempRect; 29011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final int height = getHeight(); 29111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette final int width = getWidth(); 29211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette scaleRectF(item.bounds, bounds, width, height); 29311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette node.setBoundsInParent(bounds); 29411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 29511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Since the user can tap an item, add the CLICK action. We'll 29611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // need to handle this later in onPerformActionForVirtualView. 29711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); 29811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 29911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // This item has a checked state. 30011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette node.setCheckable(true); 30111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette node.setChecked(item.checked); 30211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 30311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 30411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette @Override 30511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette protected boolean onPerformActionForVirtualView( 30611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette int virtualViewId, int action, Bundle arguments) { 30711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette switch (action) { 30811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette case AccessibilityNodeInfoCompat.ACTION_CLICK: 30911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // Click handling should be consistent with 31011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // onTouchEvent(). This ensures that the view works the 31111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette // same whether accessibility is turned on or off. 31211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return onItemClicked(virtualViewId); 31311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 31411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 31511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette return false; 31611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 31711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 31811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 31911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette 32011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette public static class CustomItem { 32111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private String description; 32211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private RectF bounds; 32311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette private boolean checked; 32411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 32511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette } 32611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette} 327