android accessibility 模拟返回_Android无障碍宝典
Android江湖上一直流傳著一部秘籍——Android無障礙寶典。傳聞練成這部寶典,可在Android無障礙模式下,飛檐走壁,能人所不能。寶典分為三篇,分別是入門、進階和高級,由淺入深,全面展示無障礙的基本方法及擴展應用。
Android應用無障礙化,目的是為視覺障礙或其他有障礙的用戶提供更好的服務。在無障礙模式下,用戶的操作方式與平常不同,比如:
- 選擇(Hover)一個元素:單擊
- 點擊(Click)一個元素:雙擊
- 滾動:雙指往上、下、左、右
- 選擇上或下一個項目:單指往上、下、左、右
- 快速回到主畫面:單指上滑+左滑
- 返回鍵:單指下滑+左滑
- 最近畫面鍵:單指左滑+上滑
- 通知欄:單指右滑+下滑
此外,還需要理解在無障礙模式下“無障礙焦點”這個概念。如圖1所示,界面上以綠色方框來表示目前獲得無障礙焦點的View。擁有無障礙焦點的View,會被TalkBack服務識別,TalkBack會從View中取出相關的無障礙內容,然后提示給用戶。
圖1 無障礙焦點
有了對無障礙模式初步的了解,就可以正式開始學習如何為應用無障礙化。
入門篇
為View添加ContentDescription
UI上的可操作元素都應該添加上ContentDescription, 當此元素獲得無障礙焦點時,TalkBack服務就取出View的提示語(contentDescription),并朗讀出來。
添加ContentDescription有兩種方法,第一種是通過在XML布局中設置android:contentDescripton屬性,如:
但是很多情況下,View的內容描述會根據不同情景需要而改變,比如CheckBox按鈕是否被選中,以及ListView中item的內容描述等。這種則需要在代碼中使用setContentDescription方法,如:
String contentDescription = "已選中 " + strValues[position];
label.setContentDescription(contentDescription);
設置無障礙焦點
UI上的元素,有的默認帶有無障礙焦點,如Button、CheckBox等標準控件,有的如果不設置contentDescription是默認沒有無障礙焦點。在開發應用過程中,還會遇到一些UI元素,是不希望它獲取無障礙焦點的。以下方法可以改變元素的無障礙焦點:
public void setAccessibilityFocusable(View view, boolean focused){
if(android.os.Build.VERSION.SDK_INT >= 16){
if(focused){
ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}else{
ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
}
}
IMPORTANT_FOR_ACCESSIBILITY_YES表示這個元素應該有無障礙焦點,會被TalkBack服務讀出描述內容;IMPORTANT_FOR_ACCESSIBILITY_NO表示屏蔽元素的無障礙焦點,手指滑動遍歷及觸摸此元素,都不會獲得無障礙焦點,TalkBack服務也不會讀出其描述內容。
發出無障礙事件
IMPORTANT_FOR_ACCESSIBILITY_YES表示這個元素應該有無障礙焦點,會被TalkBack服務讀出描述內容;IMPORTANT_FOR_ACCESSIBILITY_NO表示屏蔽元素的無障礙焦點,手指滑動遍歷及觸摸此元素,都不會獲得無障礙焦點,TalkBack服務也不會讀出其描述內容。
view.postDelayed(new Runnable() {
@Override
public void run() {
if(android.os.Build.VERSION.SDK_INT >= 14){
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
}
}
},100);
這個方法是讓View來自動發出被單擊選中的無障礙事件,發出后,UI上的無障礙焦點則會馬上賦給這個View,從而達到搶無障礙焦點的效果。再比如:
if(android.os.Build.VERSION.SDK_INT >= 16){
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.setPackageName(view.getContext().getPackageName());
event.setClassName(view.getClass().getName());
event.setSource(view);
event.getText().add(desc);
view.getParent().requestSendAccessibilityEvent(view, event);
}
AccessibilityEvent.TYPE_ANNOUNCEMENT是代表元素需要TalkBack服務來讀出描述內容。其中desc是描述內容,將它放到event的getText()中,然后請求View的父類來發出事件。
進階篇
介紹AccessibilityDelegate
Android中View含有AccessibilityDelegate這個子類,它可被注冊進View中,主要作用是為了增強對無障礙化的支持。
查看View的源碼可發現,注冊Accessibility Delegate方法很簡單:
Public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
mAccessibilityDelegate = delegate;
}
注冊后,View對無障礙的處理,則會交給AccessibilityDelegate,如:
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(this, info);
} else {
onInitializeAccessibilityNodeInfoInternal(info);
}
}
onInitializeAccessibilityNodeInfo是View源碼中初始化無障礙節點信息的方法,從上面代碼看出,當mAccessibilityDelegate是開發注冊的AccessiblityDelegate時,則會執行AccessiblityDelegate中的onInitializeAccessibilityNodeInfo方法。再看看AccessibilityDelegate類中的onInitializeAccessibilityNodeInfo:
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
host.onInitializeAccessibilityNodeInfoInternal(info);
}
Host是被注冊AccessibilityDelegate的View,onInitializeAccessibilityNodeInfoInternal是View中真正初始化無障礙節點信息的方法。即是說,注冊了AccessibilityDelegate并沒有改變View原來對無障礙的操作,而是在這個操作之后增加了處理。
AccessibilityDelegate的應用
下面介紹下AccessibilityDelegate可以提供哪些無障礙應用,注冊AccessibilityDelegate是在API 14以上才開放的接口,API 14以下如需使用,可以接入support v4包中的AccessibilityDelegateCompt。注冊方法如下:
if (Build.VERSION.SDK_INT >= 14) {
View view = findViewById(R.id.view_id);
view.setAccessibilityDelegate(new AccessibilityDelegate() {
public void onInitializeAccessibilityNodeInfo(View host,
AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
// 對info做出擴展性支持
});
}
要對info做出擴展支持,還得先了解AccessibilityNodeInfo這個類。Android開發都知道,UI上的元素是通過View來實現,而AccessibilityNodeInfo則是存儲View的無障礙信息(如contentDescription)及無障礙狀態(如focusable、visiable、clickable等),同時它還肩負著TalkBack服務和View之間通訊的橋梁作用。如果要修改View的無障礙提示,比如修改View的類型提示,可以這樣做:
if(android.os.Build.VERSION.SDK_INT >= 14){
view.setAccessibilityDelegate(new AccessibilityDelegate(){
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
if(contentDesc != null) {
info.setContentDescription(contentDesc);
}
info.setClassName(className);
}
});
}
className是類型名稱,這里如果是Button.class.getName(),則TalkBack會對這個View讀“XXX 按鈕”,“XXX”是contentDescription,而“按鈕”則是TalkBack服務添加的(如果是英文環境,則是“XXX button”)。使用上面的方法,可以為非按鈕控件加上“按鈕”的提示,方便無障礙用戶識別UI上元素的作用,同時又不必把“按鈕”提示強加入contentDescription中。除了修改AccessibilityNodeInfo外,使用AccessibilityDelegate還可以影響無障礙事件,如:
if (android.os.Build.VERSION.SDK_INT >= 14) {
view.setAccessibilityDelegate(new AccessibilityDelegate() {
@Override
public void sendAccessibilityEvent(View host, int eventType) {
// 彈出Popup后,不自動讀各項內容
if (eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
super.sendAccessibilityEvent(host, eventType);
}
}
});
}
無障礙模式下,彈出Dialog,則會把Dialog中的所有元素都讀一遍。這個方法可以把彈起窗口的無障礙事件攔截,Dialog彈起就不會再自動讀各項內容。
高級篇
有了AccessibilityDelegate這把利器之后,開發可以輕松對應Android中大部分的無障礙化,但是如果想要做到游刃有余,還得深造更高級的功夫——自定義View無障礙化。
應用開發過程中,總會需要自定義View來實現特殊的UI效果,當一個自定義View中包含多種UI元素時,無障礙模式下并不能區分包含的多種UI元素,而只為自定義View添加一個大無障礙焦點。如圖2所示。
圖2 自定義View只有一個大無障礙焦點
圖中只有一個大無障礙焦點,因為這是一個View,里面的文字及藍色的矩形都是繪制出來的。
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
if (mTitle != null) {
drawTitle(c);
}
for (int i = 0; i < mSize; i++) {
drawBarAtIndex(c, i);
}
drawAxisY(c);
}
代碼中繪制出來的元素不可被TalkBack識別出來,所以開發需要多做一步,對自定義View無障礙化。這里將介紹如何通過官方提供的ExploreByTouchHelper來實現。
圖3 自定義View內元素獲得無障礙焦點
圖3是使用ExploreByTouchHelper實現的最終效果,每一個小矩形都能獲取到無障礙焦點,且可以進行選中高亮。實現ExploreByTouchHelper需要五步。
第一步,委托處理無障礙。
public class BarGraphView extends View {
private final BarGraphAccessHelper mBarGraphAccessHelper;
public BarGraphView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
...
mBarGraphAccessHelper = new BarGraphAccessHelper(this);
ViewCompat.setAccessibilityDelegate(this, mBarGraphAccessHelper);
}
@Override
public boolean dispatchHoverEvent(MotionEvent event) {
if ((mBarGraphAccessHelper != null)
&& mBarGraphAccessHelper.dispatchHoverEvent(event)) {
return true;
}
return super.dispatchHoverEvent(event);
}
}
mBarGraphAccessHelper繼承ExploreBy TouchHelper,可通過注冊AccessibilityDelegate的方式來注冊給自定義BarGraphView,同時讓mBarGraphAccessHelper來處理Hover事件(無障礙模式下的點擊)的分發。
第二步,標記無障礙虛擬節點ID。
private class BarGraphAccessHelper extends ExploreByTouchHelper {
private final Rect mTempParentBounds = new Rect();
public BarGraphAccessHelper(View parentView) {
super(parentView);
}
@Override
protected int getVirtualViewIdAt(float x, float y) {
final int index = getBarIndexAt(x, y);
if (index >= 0) {
return index;
}
return ExploreByTouchHelper.INVALID_ID;
}
@Override
protected void getVisibleVirtualViewIds(List virtualViewIds) {
final int count = getBarCount();
for (int index = 0; index < count; index++) {
virtualViewIds.add(index);
}
}
}
getVirtualViewIdAt和getVisibleVirtualViewIds都是ExploreByTouchHelper類需要實現的方法,分別代表獲取虛擬無障礙節點的id以及設置虛擬無障礙節點的id。由于自定義View里的元素非繼承于View,如要在無障礙模式下被識別,則需要構造一個虛擬無障礙節點。構造方法已封裝到ExploreByTouchHelper里,開發只需要告訴ExploreByTouchHelper有哪些虛擬無障礙節點的id即可。無障礙節點id需要滿足以下條件:id是一個接一個的,穩定且為非負整數。設置好無障礙虛擬節點id后,根據用戶操作UI上的xy坐標,取得對應的無障礙虛擬節點id,通過getVirtualViewIdAt方法告訴ExploreByTouchHelper類。
第三步,填充無障礙節點的屬性。
private class BarGraphAccessHelper extends ExploreByTouchHelper {
...
private CharSequence getDescriptionForIndex(int index) {
final int value = getBarValue(index);
final int templateRes = ((mHighlightedIndex == index) ?
R.string.bar_desc_highlight : R.string.bar_desc);
return getContext().getString(templateRes, index, value);
}
@Override
protected void populateEventForVirtualViewId(int virtualViewId, AccessibilityEvent event) {
final CharSequence desc = getDescriptionForIndex(virtualViewId);
event.setContentDescription(desc);
}
@Override
protected void populateNodeForVirtualViewId(
int virtualViewId, AccessibilityNodeInfoCompat node) {
final CharSequence desc = getDescriptionForIndex(virtualViewId);
node.setContentDescription(desc);
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
final Rect bounds = getBoundsForIndex(virtualViewId, mTempParentBounds);
node.setBoundsInParent(bounds);
}
}
構造了虛擬無障礙節點后,便可以往節點里塞無障礙信息。populateEventForVirtualViewId是將無障礙信息填入無障礙事件中。populateNodeForVirtualViewId是初始化每個虛擬無障礙節點,設置contentDescription,注冊所需要處理的Action,以及設置無障礙焦點的邊框。setBoundsInParent一定要設置有效的邊框,否則會導致虛擬無障礙節點無法獲取無障礙焦點。
第四步,提供用戶無障礙交互支持。
private class BarGraphAccessHelper extends ExploreByTouchHelper {
...
@Override
protected boolean performActionForVirtualViewId(
int virtualViewId, int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_CLICK:
onBarClicked(virtualViewId);
return true;
}
return false;
}
}
private void onBarClicked(int index) {
setSelection(index);
if (mBarGraphAccessHelper != null) {
mBarGraphAccessHelper.sendEventForVirtualViewId(
index, AccessibilityEvent.TYPE_VIEW_CLICKED);
}
}
小矩形點擊后是會被選中且高亮的,在performActionForVirtualViewId中實現對應的點擊事件處理。經過這四步,自定義View就可以完美支持無障礙化了!
可以看出,ExploreByTouchHelper簡化了虛擬節點層次結構的構造,封裝AccessibilityNodeProvider的實現,更完善的控制Hover事件、無障礙事件。有了它,Android無障礙化再也不是難題。
Android無障礙化寶典的內容就介紹到此,在實際開發中,遇到的無障礙化問題都比較細小和瑣碎,希望以上介紹能提供一點幫助。很多Android開發以為無障礙化就是為控件加上ContentDescription,其實還有空描述、混亂焦點、焦點順序、描述準確性等地方需要注意和優化。只有用心、持續地改進和優化,才能做出真正無障礙的產品。
總結
以上是生活随笔為你收集整理的android accessibility 模拟返回_Android无障碍宝典的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python源码只有编译成二进制_Pyt
- 下一篇: 在线ocr文字识别_PandaOCR:最