mirror of
https://github.com/material-components/material-components-android.git
synced 2026-01-19 19:41:35 +08:00
192 lines
6.8 KiB
Java
192 lines
6.8 KiB
Java
/*
|
|
* Copyright 2018 The Android Open Source Project
|
|
*
|
|
* 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.android.material.internal;
|
|
|
|
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
|
|
|
import android.graphics.Rect;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.annotation.RestrictTo;
|
|
import android.view.TouchDelegate;
|
|
import android.view.View;
|
|
import android.view.ViewParent;
|
|
|
|
/**
|
|
* Utility methods for dealing with touch targets of a view.
|
|
*
|
|
* @hide
|
|
*/
|
|
@RestrictTo(LIBRARY_GROUP)
|
|
public class TouchTargetUtils {
|
|
|
|
private static final Rect HIT_RECT = new Rect();
|
|
|
|
/**
|
|
* Extend the tap target of the given view using a {@link TouchDelegate}.
|
|
*
|
|
* <p>This also adds an OnAttachStateChangeListener to the view to remove the TouchDelegate from
|
|
* its ancestor when it is detached from its parent.
|
|
*
|
|
* <p>What this means for views which are part of a reusable layout is that you should call this
|
|
* method to extend its tap target every time it is attached to a new ancestor.
|
|
*
|
|
* @param view the View whose tap target to extend
|
|
* @param ancestorId the layout id of an ancestor of the given view. This ancestor must have
|
|
* bounds which include the extended tap target
|
|
* @param left the left extension
|
|
* @param top the top extension
|
|
* @param right the right extension
|
|
* @param bottom the bottom extension
|
|
*/
|
|
public static void extendViewTouchTarget(
|
|
final View view,
|
|
final int ancestorId,
|
|
final int left,
|
|
final int top,
|
|
final int right,
|
|
final int bottom) {
|
|
// Post to view's message queue to allow the view to be added to its
|
|
// parent.
|
|
view.post(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
View ancestor = findViewAncestor(view, ancestorId);
|
|
extendViewTouchTarget(view, ancestor, left, top, right, bottom);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Extend the tap target of the given view using a {@link TouchDelegate}.
|
|
*
|
|
* <p>This also adds an OnAttachStateChangeListener to the view to remove the TouchDelegate from
|
|
* its ancestor when it is detached from its parent.
|
|
*
|
|
* <p>What this means for views which are part of a reusable layout is that you should call this
|
|
* method to extend its tap target every time it is attached to a new ancestor.
|
|
*
|
|
* @param view the View whose tap target to extend
|
|
* @param nullableAncestor an ancestor of the given view. This ancestor must have bounds which
|
|
* include the extended tap target
|
|
* @param left the left extension
|
|
* @param top the top extension
|
|
* @param right the right extension
|
|
* @param bottom the bottom extension
|
|
*/
|
|
public static void extendViewTouchTarget(
|
|
final View view,
|
|
@Nullable View nullableAncestor,
|
|
final int left,
|
|
final int top,
|
|
final int right,
|
|
final int bottom) {
|
|
if (nullableAncestor == null) {
|
|
return;
|
|
}
|
|
|
|
@NonNull final View ancestor = nullableAncestor;
|
|
|
|
// Post to ancestor's message queue to allow the ancestor to layout its
|
|
// children.
|
|
ancestor.post(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Find the hit rect of the given view, relative to its parent.
|
|
Rect viewHitRect = new Rect();
|
|
view.getHitRect(HIT_RECT);
|
|
viewHitRect.set(HIT_RECT);
|
|
|
|
// Keep offsetting viewHitRect until it is relative to the ancestor.
|
|
ViewParent parent = view.getParent();
|
|
while (parent != ancestor) {
|
|
if (parent instanceof View) {
|
|
View parentView = (View) parent;
|
|
parentView.getHitRect(HIT_RECT);
|
|
viewHitRect.offset(HIT_RECT.left, HIT_RECT.top);
|
|
parent = parentView.getParent();
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// viewHitRect is now relative to the ancestor.
|
|
viewHitRect.left -= left;
|
|
viewHitRect.top -= top;
|
|
viewHitRect.right += right;
|
|
viewHitRect.bottom += bottom;
|
|
final TouchDelegate touchDelegate = new TouchDelegate(viewHitRect, view);
|
|
|
|
// Add a touch delegate to the ancestor.
|
|
final TouchDelegateGroup touchDelegateGroup = getOrCreateTouchDelegateGroup(ancestor);
|
|
touchDelegateGroup.addTouchDelegate(touchDelegate);
|
|
ancestor.setTouchDelegate(touchDelegateGroup);
|
|
|
|
// Every time a view is recycled, it is removed from its parent.
|
|
// Make sure to remove the touch delegate from the ancestor.
|
|
view.addOnAttachStateChangeListener(
|
|
new View.OnAttachStateChangeListener() {
|
|
@Override
|
|
public void onViewAttachedToWindow(View view) {}
|
|
|
|
@Override
|
|
public void onViewDetachedFromWindow(View view) {
|
|
touchDelegateGroup.removeTouchDelegate(touchDelegate);
|
|
view.removeOnAttachStateChangeListener(this);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ensure that the given view has a TouchDelegateGroup. A new TouchDelegateGroup is created if one
|
|
* does not exist for the view already. An existing TouchDelegate is retained and inserted into
|
|
* the new group.
|
|
*/
|
|
public static TouchDelegateGroup getOrCreateTouchDelegateGroup(View ancestor) {
|
|
TouchDelegateGroup touchDelegateGroup;
|
|
TouchDelegate existingTouchDelegate = ancestor.getTouchDelegate();
|
|
if (existingTouchDelegate != null) {
|
|
if (existingTouchDelegate instanceof TouchDelegateGroup) {
|
|
touchDelegateGroup = (TouchDelegateGroup) existingTouchDelegate;
|
|
} else {
|
|
touchDelegateGroup = new TouchDelegateGroup(ancestor);
|
|
touchDelegateGroup.addTouchDelegate(existingTouchDelegate);
|
|
}
|
|
} else {
|
|
touchDelegateGroup = new TouchDelegateGroup(ancestor);
|
|
}
|
|
return touchDelegateGroup;
|
|
}
|
|
|
|
/** Return the first ancestor of the given View matching the ancestorId. */
|
|
@Nullable
|
|
public static View findViewAncestor(View view, int ancestorId) {
|
|
View parent = view;
|
|
while (parent != null && parent.getId() != ancestorId) {
|
|
if (!(parent.getParent() instanceof View)) {
|
|
return null;
|
|
}
|
|
parent = (View) parent.getParent();
|
|
}
|
|
return parent;
|
|
}
|
|
}
|