Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A collection of utilities for handling Gesture Navigation before using compileSdkVersion=Q
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.os.BuildCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.samples.apps.iosched.util.ViewGestureUtils;
import com.google.samples.apps.iosched.util.WindowInsetsUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Extension of {@link DrawerLayout} which sets gesture exclusion zones, to work with Android Q's
* gesture navigation.
*
* @deprecated this should be removed once you can depend on
* androidx.drawerlayout:drawerlayout:1.1.0 (currently in alpha)
*/
@Deprecated
public class GestureExcludingDrawerLayout extends DrawerLayout {
public GestureExcludingDrawerLayout(@NonNull Context context) {
this(context, null);
}
public GestureExcludingDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public GestureExcludingDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Add a DrawerListener which calls updateGestureExclusion() when any drawer state changes
addDrawerListener(new DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
updateGestureExclusion();
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
updateGestureExclusion();
}
@Override
public void onDrawerStateChanged(int newState) {
updateGestureExclusion();
}
});
}
@Override
public void setDrawerLockMode(int lockMode, int edgeGravity) {
super.setDrawerLockMode(lockMode, edgeGravity);
// Drawer lock mode has changed. Update any gesture exclusion zones
updateGestureExclusion();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
updateGestureExclusion();
}
@SuppressLint("RtlHardcoded") // We need to use physical dimensions
private void updateGestureExclusion() {
// If we're not running on Q, we can skip this method entirely
if (!BuildCompat.isAtLeastQ()) {
return;
}
List<Rect> rects = null;
final WindowInsets rootInsets = getRootWindowInsets();
if (rootInsets == null) {
return;
}
final Rect systemGestureInsets = WindowInsetsUtils.getSystemGestureInsets(rootInsets);
if (getDrawerLockMode(Gravity.LEFT) != DrawerLayout.LOCK_MODE_LOCKED_CLOSED &&
hasClosedDrawer(Gravity.LEFT)) {
// We have a non-locked closed drawer on the left, add an exclusion rect so
// that the user can swipe it open
rects = new ArrayList<>();
rects.add(new Rect(0, 0, systemGestureInsets.left, getHeight()));
}
if (getDrawerLockMode(Gravity.RIGHT) != DrawerLayout.LOCK_MODE_LOCKED_CLOSED &&
hasClosedDrawer(Gravity.RIGHT)) {
// We have a non-locked closed drawer on the right, add an exclusion rect so
// that the user can swipe it open
if (rects == null) {
rects = new ArrayList<>();
}
rects.add(new Rect(getWidth() - systemGestureInsets.right, 0, getWidth(), getHeight()));
}
ViewGestureUtils.setSystemGestureExclusionRects(this,
rects != null ? rects : Collections.emptyList());
}
private boolean hasClosedDrawer(final int gravity) {
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int absGravity = Gravity.getAbsoluteGravity(lp.gravity, getLayoutDirection());
if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == gravity) {
return !isDrawerOpen(child);
}
}
return false;
}
}
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.util;
import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.os.BuildCompat;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
/**
* Utils for accessing new {@link View} APIs added in Android Q for system gestures
*
* @deprecated should be removed once you can use ViewCompat in
* androidx.drawerlayout:drawerlayout:1.1.0 (currently in alpha) or newer
*/
@SuppressLint("PrivateApi")
@Deprecated
public class ViewGestureUtils {
private static final String LOG_TAG = "ViewGestureUtils";
private ViewGestureUtils() {}
@SuppressWarnings("unchecked")
@NonNull
public static List<Rect> getSystemGestureExclusionRects(@NonNull final View view) {
if (BuildCompat.isAtLeastQ()) {
try {
Method method = view.getClass().getMethod("getSystemGestureExclusionRects");
return (List<Rect>) method.invoke(view);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Method does not exist before API 29
Log.e(LOG_TAG,"Error while retrieving getSystemGestureExclusionRects"
+ " method via reflection", e);
}
}
// If we're not running on Q, or we hit an error, just return an empty list
return Collections.emptyList();
}
@SuppressWarnings("unchecked")
public static void setSystemGestureExclusionRects(
@NonNull final View view, @NonNull final List<Rect> rects) {
if (BuildCompat.isAtLeastQ()) {
try {
Method method = view.getClass().getMethod(
"setSystemGestureExclusionRects", List.class);
method.invoke(view, rects);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Method does not exist before API 29
Log.e(LOG_TAG, "Error while retrieving setSystemGestureExclusionRects"
+ " method via reflection", e);
}
}
}
@SuppressLint("NewApi") // Suppressed because lint doesn't recognize isAtLeastQ()
public static boolean shouldCloseDrawerFromBackPress(@NonNull final View view) {
if (BuildCompat.isAtLeastQ()) {
// If we're running on Q, and this call to closeDrawers if from a key event
// (for back handling), we should only honor it IF the device is not currently
// in gesture mode. We approximate that by checking the system gesture insets
final Rect systemGestureInsets = WindowInsetsUtils.getSystemGestureInsets(
view.getRootWindowInsets());
return systemGestureInsets.left == 0 && systemGestureInsets.right == 0;
}
// On P and earlier, always close the drawer
return true;
}
}
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.util;
import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.util.Log;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.core.os.BuildCompat;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Utils for accessing new {@link WindowInsets} APIs added in Android Q
*
* @deprecated this should be removed once you can depend on
* androidx.drawerlayout:drawerlayout:1.1.0 (currently in alpha)
*/
@SuppressLint("PrivateApi")
@Deprecated
public class WindowInsetsUtils {
private static final String LOG_TAG = "WindowInsetsUtils";
private static Method methodGetMandatorySystemGestureInsets;
private static boolean methodGetMandatorySystemGestureInsetsFetched;
private static Method methodGetSystemGestureInsets;
private static boolean methodGetSystemGestureInsetsFetched;
private static boolean insetsFetched;
private static Class classInsets;
private static Field fieldInsetsLeft;
private static Field fieldInsetsTop;
private static Field fieldInsetsRight;
private static Field fieldInsetsBottom;
private WindowInsetsUtils() {}
/**
* Returns the system window insets for the given {@link WindowInsets} as a Rect
*/
private static Rect getSystemWindowInsets(@NonNull final WindowInsets insets) {
return new Rect(
insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
}
/**
* Returns the system gesture insets. On Q we use reflection to access the API, before that
* we just use the system window insets
*/
public static Rect getSystemGestureInsets(@NonNull final WindowInsets insets) {
if (BuildCompat.isAtLeastQ()) {
fetchInsetsClazz();
fetchGetSystemGestureInsetsMethod();
return invokeInsetsMethod(methodGetSystemGestureInsets, insets);
}
// For devices running P and earlier, just return the system window insets
return getSystemWindowInsets(insets);
}
/**
* Returns the mandatory system gesture insets. On Q we use reflection to access the API,
* before that we just use the system window insets
*/
public static Rect getMandatorySystemGestureInsets(@NonNull final WindowInsets insets) {
if (BuildCompat.isAtLeastQ()) {
fetchInsetsClazz();
fetchGetMandatorySystemGestureInsetsMethod();
return invokeInsetsMethod(methodGetMandatorySystemGestureInsets, insets);
}
// For devices running P and earlier, just return the system window insets
return getSystemWindowInsets(insets);
}
private static Rect invokeInsetsMethod(Method method, @NonNull final WindowInsets insets) {
if (method != null) {
try {
return insetsToRect(method.invoke(insets));
} catch (IllegalAccessException | InvocationTargetException e) {
Log.e(LOG_TAG, "Error while invoking " + method.getName() + " via reflection", e);
}
}
// If we get here, something has gone wrong. We will just return the system window insets
return getSystemWindowInsets(insets);
}
private static void fetchInsetsClazz() {
if (!insetsFetched) {
try {
classInsets = Class.forName("android.graphics.Insets");
fieldInsetsLeft = classInsets.getField("left");
fieldInsetsTop = classInsets.getField("top");
fieldInsetsRight = classInsets.getField("right");
fieldInsetsBottom = classInsets.getField("bottom");
} catch (ClassNotFoundException | NoSuchFieldException e) {
Log.e(LOG_TAG, "Error while retrieving Insets class via reflection", e);
}
insetsFetched = true;
}
}
private static void fetchGetSystemGestureInsetsMethod() {
if (!methodGetSystemGestureInsetsFetched) {
try {
methodGetSystemGestureInsets =
WindowInsets.class.getMethod("getSystemGestureInsets");
} catch (NoSuchMethodException e) {
// Method does not exist before API 29
Log.e(LOG_TAG, "Error while retrieving getSystemGestureInsets"
+ " method via reflection", e);
}
methodGetSystemGestureInsetsFetched = true;
}
}
private static void fetchGetMandatorySystemGestureInsetsMethod() {
if (!methodGetMandatorySystemGestureInsetsFetched) {
try {
methodGetMandatorySystemGestureInsets =
WindowInsets.class.getMethod("getMandatorySystemGestureInsets");
} catch (NoSuchMethodException e) {
// Method does not exist before API 29
Log.e(LOG_TAG, "Error while retrieving getMandatorySystemGestureInsets"
+ " method via reflection", e);
}
methodGetMandatorySystemGestureInsetsFetched = true;
}
}
@NonNull
private static Rect insetsToRect(@NonNull final Object insets) throws IllegalAccessException {
return new Rect(fieldInsetsLeft.getInt(insets), fieldInsetsTop.getInt(insets),
fieldInsetsRight.getInt(insets), fieldInsetsBottom.getInt(insets));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.