视图添加字段_使用ExploreByTouchHelper辅助类为自定义视图添加虚拟视图
在安卓開發(fā)過程中,為了視覺和功能的需要開發(fā)者經(jīng)常會使用自定義視圖
大多數(shù)的自定義視圖是組合現(xiàn)有的控件來完成特定的功能
但是,有一種自定義視圖是通過畫筆在畫布上畫出自定義的子視圖的,例如日期控件,顏色選擇面板等
由于自定義視圖的子視圖是用畫筆繪制的,所以這些子視圖無法被無障礙服務訪問
為了解決此種問題,Android系統(tǒng)在API16引入虛擬視圖概念
開發(fā)人員可以通過虛擬視圖模擬出視圖結構,從而讓無障礙服務能夠訪問這些繪制的子視圖
今天就來講講使用支持庫中的ExploreByTouchHelper工具類實現(xiàn)虛擬視圖的方法:
1. 為自定義視圖添加無障礙代理
在自定義視圖初始化時,調用setAccessibilityDelegate()方法設置無障礙代理
參數(shù)是實現(xiàn)了ExploreByTouchHelper工具類的對象
如果想支持API更早的版本可以調用ViewCompat的setAccessibilityDelegate()方法,如下所示:
mTouchHelper = new CustomViewTouchHelper(this);ViewCompat.setAccessibilityDelegate(this, mTouchHelper);2. 實現(xiàn)無障礙代理
通過繼承ExploreByTouchHelper工具類實現(xiàn)無障礙代理,如下所示:
private class CustomViewTouchHelper extends ExploreByTouchHelper { public CustomViewTouchHelper(VirtualSubview view) { super(view); }...}3. 為虛擬視圖添加子視圖節(jié)點
通過重寫getVisibleVirtualViews()方法確定虛擬視圖中有多少子節(jié)點
添加的子節(jié)點就是無障礙服務訪問時能訪問到的無障礙焦點
代碼樣例如下:
@Override protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { final List<VirtualView> childs = mChildren; final int count = childs.size(); for (int i = 0; i < count; i++) { VirtualView child = childs.get(i); virtualViewIds.add(child.mId); }}4. 為虛擬視圖節(jié)點添加ID
當用戶開啟無障礙服務訪問虛擬節(jié)點時,會調用getVirtualViewAt()方法確定用戶觸摸的區(qū)域屬于哪一個子視圖
我們需要在此方法中通過x和y坐標計算出當前操作的虛擬節(jié)點的id
代碼如下所示:
@Override protected int getVirtualViewAt(float x, float y) { VirtualView view = findVirtualViewByBoords(x, y); if (view == null) return INVALID_ID; //返回無效的節(jié)點return view.mId; }5. 為虛擬視圖填充必要的無障礙屬性
為了讓無障礙服務正確地反饋虛擬視圖的相關信息,我們需要為虛擬視圖填充必要的無障礙屬性信息
下面是填充信息的方法:
//在此方法中設置虛擬視圖的無障礙事件信息 @Override protected void onPopulateEventForVirtualView( int virtualViewId,AccessibilityEvent event) { //調用此方法給無障礙事件填充text字段,text字段會被TalkBack朗讀出來VirtualView item = findVirtualViewById(virtualViewId); if (item != null)event.getText().add(item.mText); }//調用此方法填充子虛擬視圖的無障礙nodeinfo的屬性 @Override protected void onPopulateNodeForVirtualView(int virtualViewId,AccessibilityNodeInfoCompat node) { //調用此方法在NodeInfo中設置子虛擬視圖的text字段,此字段會被talkback朗讀出來VirtualView item = findVirtualViewById(virtualViewId); if (item == null) return;node.setText(item.mText); Rect bounds = item.mBounds; //調用此方法設置子虛擬視圖的焦點大小,焦點大小與實際畫的視圖一致大小。此方法必須調用。node.setBoundsInParent(bounds);//調用此方法設置nodeinfo都能處理哪些無障礙事件。調用此方法只能說明nodeinfo能處理這些action,不是觸發(fā)action,也不是具體處理action。這里可以設置多個action。node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); //調用此方法代表此nodeinfo節(jié)點是可以被選中。當設置為true代表可以被選中,如復選框就需要設置為true。node.setCheckable(true); //設置無障礙屬性的選中狀態(tài) node.setChecked(item.mAlpha == VirtualView.ALPHA_SELECTED); }6. 響應無障礙事件
添加了虛擬視圖的自定義控件要響應無障礙服務的相關事件,如點擊
需要做下面兩個步驟:
第一步:
重寫dispatchHoverEvent()方法,并且把事件轉發(fā)給實現(xiàn)了ExploreByTouchHelper的對象處理,如下所示:
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)@Overridepublic boolean dispatchHoverEvent(MotionEvent event) { if (mTouchHelper.dispatchHoverEvent(event)) { return true; }第二步:
在輔助類中重寫onPerformActionForVirtualView()方法,在此方法中處理相關的無障礙事件
事件處理完成返回true,未處理返回false,從而讓系統(tǒng)自動處理
下面的代碼中處理了點擊事件:
@Override protected boolean onPerformActionForVirtualView( int virtualViewId, int action, Bundle arguments) { switch (action) { case AccessibilityNodeInfoCompat.ACTION_CLICK: //處理點擊事件VirtualView view = findVirtualViewById(virtualViewId);if (view == null)return false;setVirtualViewSelected(view, !(view.mAlpha == VirtualView.ALPHA_SELECTED) );invalidate(); mTouchHelper.invalidateVirtualView(virtualViewId);return true; }return false; }注意:當虛擬節(jié)點中的無障礙屬性更改后,需要調用invalidateVirtualView()更新指定的無障礙節(jié)點
如果不更新無障礙服務獲取的信息會出現(xiàn)問題,如焦點顯示錯誤、文本提示錯誤、狀態(tài)朗讀錯誤等
以上就是自定義視圖實現(xiàn)虛擬節(jié)點的方法
借助ExploreByTouchHelper工具類實現(xiàn)虛擬節(jié)點我們只需要重寫對應的方法、添加無障礙代理、轉發(fā)事件就能輕松的完成
比使用AccessibilityNodeProvider類簡單很多
下面貼出完整的選擇顏色的自定義視圖代碼以供參考:
public class VirtualSubview extends View {private final Paint mPaint = new Paint(); private final Rect mTempRect = new Rect(); private final List<VirtualView> mChildren = new ArrayList<VirtualView>(); private VirtualSubview mLastHoveredChild; private CustomViewTouchHelper mTouchHelper; private Context context;public VirtualSubview(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; createVirtualChildren(); mTouchHelper = new CustomViewTouchHelper(this); ViewCompat.setAccessibilityDelegate(this, mTouchHelper); }@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public boolean dispatchHoverEvent(MotionEvent event) { if (mTouchHelper.dispatchHoverEvent(event)) { return true; }return super.dispatchHoverEvent(event); }@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int offsetX = 0; List<VirtualView> children = mChildren; final int childCount = children.size(); for (int i = 0; i < childCount; i++) { VirtualView child = children.get(i); Rect childBounds = child.mBounds; childBounds.set(offsetX, 0, offsetX + childBounds.width(), childBounds.height()); offsetX += childBounds.width(); } }@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 0; int height = 0; List<VirtualView> children = mChildren; final int childCount = children.size(); for (int i = 0; i < childCount; i++) { VirtualView child = children.get(i); width += child.mBounds.width(); height = Math.max(height, child.mBounds.height()); } setMeasuredDimension(width, height); }@Override protected void onDraw(Canvas canvas) { Rect drawingRect = mTempRect; List<VirtualView> children = mChildren; final int childCount = children.size(); for (int i = 0; i < childCount; i++) { VirtualView child = children.get(i); drawingRect.set(child.mBounds); mPaint.setColor(child.mColor); mPaint.setAlpha(child.mAlpha); canvas.drawRect(drawingRect, mPaint); } }@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: VirtualView view = findVirtualViewByBoords(event.getX(), event.getY()); if (view == null) return true;setVirtualViewSelected(view, !(view.mAlpha == VirtualView.ALPHA_SELECTED) ); sendAccessibilityEventForVirtualView(view, AccessibilityEvent.TYPE_VIEW_CLICKED); invalidate(); mTouchHelper.invalidateVirtualView(view.mId); return true; } return super.onTouchEvent(event); }private void createVirtualChildren() { VirtualView firstChild = new VirtualView(0, new Rect(0, 0, 150, 150), Color.RED, "Virtual view 1"); mChildren.add(firstChild); VirtualView secondChild = new VirtualView(1, new Rect(0, 0, 150, 150), Color.GREEN, "Virtual view 2"); mChildren.add(secondChild); VirtualView thirdChild = new VirtualView(2, new Rect(0, 0, 150, 150), Color.BLUE, "Virtual view 3"); mChildren.add(thirdChild); }private void setVirtualViewSelected(VirtualView virtualView, boolean selected) { virtualView.mAlpha = selected ? VirtualView.ALPHA_SELECTED : VirtualView.ALPHA_NOT_SELECTED; }private void sendAccessibilityEventForVirtualView(VirtualView virtualView, int eventType) { if (mAccessibilityManager.isTouchExplorationEnabled()) { AccessibilityEvent event = AccessibilityEvent.obtain(eventType); event.setPackageName(getContext().getPackageName()); event.setClassName(virtualView.getClass().getName()); event.setSource(VirtualSubview.this, virtualView.mId); event.getText().add(virtualView.mText); getParent().requestSendAccessibilityEvent(VirtualSubview.this, event); } }private VirtualView findVirtualViewById(int id) { List<VirtualView> children = mChildren; final int childCount = children.size(); for (int i = 0; i < childCount; i++) { VirtualView child = children.get(i); if (child.mId == id) { return child; } } return null; }private class VirtualView { public static final int ALPHA_SELECTED = 255; public static final int ALPHA_NOT_SELECTED = 127; public final int mId; public final int mColor; public final Rect mBounds; public final String mText; public int mAlpha;public VirtualView(int id, Rect bounds, int color, String text) { mId = id; mColor = color; mBounds = bounds; mText = text; mAlpha = ALPHA_NOT_SELECTED; } }private class CustomViewTouchHelper extends ExploreByTouchHelper { public CustomViewTouchHelper( VirtualSubview view) { super(view); }//通過此方法的x和y參數(shù)來確定旭虛擬視圖的哪一個虛擬子視圖的虛擬id。在此方法中調用自己實現(xiàn)的通過x、y坐標獲取id的方法,id通常是0、1、2…… @Override protected int getVirtualViewAt(float x, float y) { VirtualViewview = findVirtualViewByBoords(x, y); if (view == null) return INVALID_ID;return view.mId; }//調用此方法來確定虛擬視圖中的哪些子視圖有無障礙焦點。加入列表中的虛擬id的子虛擬視圖都有無障礙焦點 @Override protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { final List<VirtualView> childs = mChildren; final int count = childs.size(); for (int i = 0; i < count; i++) { VirtualView child = childs.get(i); virtualViewIds.add(child.mId); }}//在此方法中填充子虛擬視圖的無障礙事件中的屬性 @Override protected void onPopulateEventForVirtualView( int virtualViewId, AccessibilityEvent event) { //調用此方法給無障礙事件填充text字段,text字段會被TalkBack朗讀出來 VirtualView item = findVirtualViewById(virtualViewId); if (item != null) event.getText().add(item.mText); }//調用此方法填充子虛擬視圖的無障礙nodeinfo的屬性 @Override protected void onPopulateNodeForVirtualView( int virtualViewId, AccessibilityNodeInfoCompat node) { //調用此方法在NodeInfo中設置子虛擬視圖的text字段,此字段會被talkback朗讀出來 VirtualView item = findVirtualViewById(virtualViewId); if (item == null) return;node.setText(item.mText); Rect bounds = item.mBounds; //調用此方法設置子虛擬視圖的焦點大小,焦點大小與實際畫的視圖一致大小。此方法必須調用。 node.setBoundsInParent(bounds);//調用此方法設置nodeinfo都能處理哪些無障礙事件。調用此方法只能說明nodeinfo能處理這些action,不是觸發(fā)action,也不是具體處理action。這里可以設置多個action。 node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);//調用此方法代表此nodeinfo節(jié)點是可以被選中。當設置為true代表可以被選中,如復選框就需要設置為true。 node.setCheckable(true); node.setChecked(item.mAlpha == VirtualView.ALPHA_SELECTED); }//調用此方法具體處理無障礙事件的action。在此方法中需要根據(jù)不同的action進行處理,當發(fā)生click后調用普通的點擊處理方法,在此案例中調用的是onitemclick()方法。 @Override protected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) { switch (action) { case AccessibilityNodeInfoCompat.ACTION_CLICK: VirtualView view = findVirtualViewById(virtualViewId); if (view == null) return false;setVirtualViewSelected(view, !(view.mAlpha == VirtualView.ALPHA_SELECTED) ); invalidate(); mTouchHelper.invalidateVirtualView(virtualViewId); return true;}return false; }}public VirtualView findVirtualViewByBoords(float x, float y) { final List<VirtualView> childs = mChildren; final int count = childs.size(); for (int i = 0; i < count; i++) { VirtualView child = childs.get(i); if (child.mBounds.contains( (int)x, (int)y)) return child; } return null; } }更多精彩干貨:歡迎關注“無障礙實驗室”公眾號
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結
以上是生活随笔為你收集整理的视图添加字段_使用ExploreByTouchHelper辅助类为自定义视图添加虚拟视图的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洛谷 P1469 找筷子
- 下一篇: 如何在右键新建中添加新建xmind文件