Skip to content

Instantly share code, notes, and snippets.

@npombourcq
Created May 23, 2013 13:27
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save npombourcq/5636071 to your computer and use it in GitHub Desktop.
Save npombourcq/5636071 to your computer and use it in GitHub Desktop.
Fixes for some issues with the new DrawerLayout.

The new DrawerLayout has a few accessibility related issues. Thankfully they can be fixed by simple subclassing and adapting some code from the ViewPager widget.

  1. focus can still go through the content view when navigating with the dpad or explore by touch. I have had to subclass and override addFocusables and onRequestFocusInDescendants to scope focus only to opened folders, as well as make sure to transfer the focus from the content to the drawer when opening, and back to the content when closing.

  2. override dispatchPopulateAccessibilityEvent so that it ignorse the content view when a drawer is opened.

  3. if a drawer has an area that does not capture clicks (for example a header that is just a plain textview), the click goes through to the content view behind the drawer. This adds a OnTouchListener to the drawer that captures all onTouch events and prevents them from reaching the content view.

import java.util.ArrayList;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
* Fixes focus issues in the base DrawerLayout. This uses code inspired from
* ViewPager to ensure that focus goes to the right place depending on drawer
* state, namely first to any opened drawer, and if no drawer is opened to the
* main content.
*/
public final class DrawerLayout extends android.support.v4.widget.DrawerLayout {
private DrawerListener m_wrappedListener;
private final DrawerListener m_drawerListener = new DrawerListener() {
@Override
public void onDrawerOpened(View drawerView) {
// if the content has focus, transfer focus to the drawer
if (getContentView().hasFocus())
drawerView.requestFocus(View.FOCUS_FORWARD);
if (m_wrappedListener != null)
m_wrappedListener.onDrawerOpened(drawerView);
}
@Override
public void onDrawerClosed(View drawerView) {
if (drawerView.hasFocus())
getContentView().requestFocus(View.FOCUS_FORWARD);
if (m_wrappedListener != null)
m_wrappedListener.onDrawerClosed(drawerView);
}
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
if (m_wrappedListener != null)
m_wrappedListener.onDrawerSlide(drawerView, slideOffset);
}
@Override
public void onDrawerStateChanged(int newState) {
if (m_wrappedListener != null)
m_wrappedListener.onDrawerStateChanged(newState);
}
};
private static final OnTouchListener s_drawerTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// prevent touch events from going through the drawer
return true;
}
};
private final OnHierarchyChangeListener m_hierarchyChangeListener = new OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
if (DrawerLayout.this == parent && child != getContentView())
child.setOnTouchListener(s_drawerTouchListener);
}
@Override
public void onChildViewRemoved(View parent, View child) {
}
};
public DrawerLayout(Context context) {
super(context);
init();
}
public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public DrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
super.setDrawerListener(m_drawerListener);
setOnHierarchyChangeListener(m_hierarchyChangeListener);
}
private View getContentView() {
return getChildAt(0);
}
@Override
public void setDrawerListener(DrawerListener listener) {
m_wrappedListener = listener;
}
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
// logic derived from ViewPager
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
boolean opened = false;
for (int i = 1; i < getChildCount(); i++) {
final View drawerView = getChildAt(i);
if (isDrawerOpen(drawerView)) {
opened = true;
drawerView.addFocusables(views, direction, focusableMode);
}
}
if (!opened) {
getContentView().addFocusables(views, direction, focusableMode);
}
}
// we add ourselves (if focusable) in all cases except for when we are
// FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.
// this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
if (descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
// No focusable descendants
(focusableCount == views.size())) {
// Note that we can't call the superclass here, because it will
// add all views in. So we need to do the same thing View does.
if (!isFocusable()) {
return;
}
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && isInTouchMode()
&& !isFocusableInTouchMode()) {
return;
}
if (views != null) {
views.add(this);
}
}
}
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
boolean opened = false;
for (int i = 1; i < getChildCount(); i++) {
final View drawerView = getChildAt(i);
if (isDrawerOpen(drawerView)) {
opened = true;
if (drawerView.requestFocus(direction, previouslyFocusedRect))
return true;
}
}
if (!opened) {
return getContentView().requestFocus(direction, previouslyFocusedRect);
}
return false;
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean opened = false;
for (int i = 1; i < getChildCount(); i++) {
final View drawerView = getChildAt(i);
if (isDrawerOpen(drawerView)) {
opened = true;
if (drawerView.dispatchPopulateAccessibilityEvent(event))
return true;
}
}
if (!opened)
return getContentView().dispatchPopulateAccessibilityEvent(event);
return false;
}
}
@ksc91u
Copy link

ksc91u commented Aug 19, 2014

Hi, I use this Layout in my app, works perfectly on TV.
But if on phone or tablet, MenuI tem under drawer is not clickable.
To solve this I add isInTouchMode() to addFocusables, onRequestFocusInDescendants, dispatchPopulateAccessibilityEvent.

Gist here: https://gist.github.com/ksc91u/4c6bcbeff74357540a92

Thank you for your great work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment