2018-03-14 12:30:11 -07:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) 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
|
|
|
|
|
*
|
|
|
|
|
* http://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.android.launcher3.views;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
import android.widget.FrameLayout;
|
|
|
|
|
|
|
|
|
|
import com.android.launcher3.AbstractFloatingView;
|
|
|
|
|
import com.android.launcher3.BaseActivity;
|
|
|
|
|
import com.android.launcher3.BaseDraggingActivity;
|
|
|
|
|
import com.android.launcher3.InsettableFrameLayout;
|
|
|
|
|
import com.android.launcher3.Utilities;
|
|
|
|
|
import com.android.launcher3.util.TouchController;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
2018-04-24 12:21:28 -07:00
|
|
|
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
|
|
|
|
|
|
2018-03-14 12:30:11 -07:00
|
|
|
/**
|
|
|
|
|
* A viewgroup with utility methods for drag-n-drop and touch interception
|
|
|
|
|
*/
|
|
|
|
|
public abstract class BaseDragLayer<T extends BaseDraggingActivity> extends InsettableFrameLayout {
|
|
|
|
|
|
|
|
|
|
protected final int[] mTmpXY = new int[2];
|
|
|
|
|
protected final Rect mHitRect = new Rect();
|
|
|
|
|
|
|
|
|
|
protected final T mActivity;
|
|
|
|
|
|
|
|
|
|
protected TouchController[] mControllers;
|
|
|
|
|
protected TouchController mActiveController;
|
|
|
|
|
private TouchCompleteListener mTouchCompleteListener;
|
|
|
|
|
|
|
|
|
|
public BaseDragLayer(Context context, AttributeSet attrs) {
|
|
|
|
|
super(context, attrs);
|
|
|
|
|
mActivity = (T) BaseActivity.fromContext(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public boolean isEventOverView(View view, MotionEvent ev) {
|
|
|
|
|
getDescendantRectRelativeToSelf(view, mHitRect);
|
|
|
|
|
return mHitRect.contains((int) ev.getX(), (int) ev.getY());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
|
|
|
int action = ev.getAction();
|
|
|
|
|
|
|
|
|
|
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
|
|
|
|
if (mTouchCompleteListener != null) {
|
|
|
|
|
mTouchCompleteListener.onTouchComplete();
|
|
|
|
|
}
|
|
|
|
|
mTouchCompleteListener = null;
|
|
|
|
|
} else if (action == MotionEvent.ACTION_DOWN) {
|
|
|
|
|
mActivity.finishAutoCancelActionMode();
|
|
|
|
|
}
|
|
|
|
|
return findActiveController(ev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected boolean findActiveController(MotionEvent ev) {
|
|
|
|
|
mActiveController = null;
|
|
|
|
|
|
|
|
|
|
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
|
|
|
|
|
if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
|
|
|
|
|
mActiveController = topView;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (TouchController controller : mControllers) {
|
|
|
|
|
if (controller.onControllerInterceptTouchEvent(ev)) {
|
|
|
|
|
mActiveController = controller;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
|
|
|
|
|
// Shortcuts can appear above folder
|
|
|
|
|
View topView = AbstractFloatingView.getTopOpenView(mActivity);
|
|
|
|
|
if (topView != null) {
|
|
|
|
|
if (child == topView) {
|
|
|
|
|
return super.onRequestSendAccessibilityEvent(child, event);
|
|
|
|
|
}
|
|
|
|
|
// Skip propagating onRequestSendAccessibilityEvent for all other children
|
|
|
|
|
// which are not topView
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return super.onRequestSendAccessibilityEvent(child, event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
|
|
|
|
|
View topView = AbstractFloatingView.getTopOpenView(mActivity);
|
|
|
|
|
if (topView != null) {
|
|
|
|
|
// Only add the top view as a child for accessibility when it is open
|
|
|
|
|
childrenForAccessibility.add(topView);
|
|
|
|
|
} else {
|
|
|
|
|
super.addChildrenForAccessibility(childrenForAccessibility);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-24 12:21:28 -07:00
|
|
|
@Override
|
|
|
|
|
public void onViewRemoved(View child) {
|
|
|
|
|
super.onViewRemoved(child);
|
|
|
|
|
if (child instanceof AbstractFloatingView) {
|
|
|
|
|
// Handles the case where the view is removed without being properly closed.
|
|
|
|
|
// This can happen if something goes wrong during a state change/transition.
|
|
|
|
|
postDelayed(() -> {
|
|
|
|
|
AbstractFloatingView floatingView = (AbstractFloatingView) child;
|
|
|
|
|
if (floatingView.isOpen()) {
|
|
|
|
|
floatingView.close(false);
|
|
|
|
|
}
|
|
|
|
|
}, SINGLE_FRAME_MS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 12:30:11 -07:00
|
|
|
@Override
|
|
|
|
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
|
|
|
int action = ev.getAction();
|
|
|
|
|
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
|
|
|
|
if (mTouchCompleteListener != null) {
|
|
|
|
|
mTouchCompleteListener.onTouchComplete();
|
|
|
|
|
}
|
|
|
|
|
mTouchCompleteListener = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mActiveController != null) {
|
|
|
|
|
return mActiveController.onControllerTouchEvent(ev);
|
|
|
|
|
} else {
|
|
|
|
|
// In case no child view handled the touch event, we may not get onIntercept anymore
|
|
|
|
|
return findActiveController(ev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine the rect of the descendant in this DragLayer's coordinates
|
|
|
|
|
*
|
|
|
|
|
* @param descendant The descendant whose coordinates we want to find.
|
|
|
|
|
* @param r The rect into which to place the results.
|
|
|
|
|
* @return The factor by which this descendant is scaled relative to this DragLayer.
|
|
|
|
|
*/
|
|
|
|
|
public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
|
|
|
|
|
mTmpXY[0] = 0;
|
|
|
|
|
mTmpXY[1] = 0;
|
|
|
|
|
float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
|
|
|
|
|
|
|
|
|
|
r.set(mTmpXY[0], mTmpXY[1],
|
|
|
|
|
(int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
|
|
|
|
|
(int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
|
|
|
|
|
return scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public float getLocationInDragLayer(View child, int[] loc) {
|
|
|
|
|
loc[0] = 0;
|
|
|
|
|
loc[1] = 0;
|
|
|
|
|
return getDescendantCoordRelativeToSelf(child, loc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
|
|
|
|
|
return getDescendantCoordRelativeToSelf(descendant, coord, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
|
|
|
|
|
* coordinates.
|
|
|
|
|
*
|
|
|
|
|
* @param descendant The descendant to which the passed coordinate is relative.
|
|
|
|
|
* @param coord The coordinate that we want mapped.
|
|
|
|
|
* @param includeRootScroll Whether or not to account for the scroll of the root descendant:
|
|
|
|
|
* sometimes this is relevant as in a child's coordinates within the root descendant.
|
|
|
|
|
* @return The factor by which this descendant is scaled relative to this DragLayer. Caution
|
|
|
|
|
* this scale factor is assumed to be equal in X and Y, and so if at any point this
|
|
|
|
|
* assumption fails, we will need to return a pair of scale factors.
|
|
|
|
|
*/
|
|
|
|
|
public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
|
|
|
|
|
boolean includeRootScroll) {
|
|
|
|
|
return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
|
|
|
|
|
coord, includeRootScroll);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
|
|
|
|
|
*/
|
|
|
|
|
public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
|
|
|
|
|
Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void getViewRectRelativeToSelf(View v, Rect r) {
|
|
|
|
|
int[] loc = new int[2];
|
|
|
|
|
getLocationInWindow(loc);
|
|
|
|
|
int x = loc[0];
|
|
|
|
|
int y = loc[1];
|
|
|
|
|
|
|
|
|
|
v.getLocationInWindow(loc);
|
|
|
|
|
int vX = loc[0];
|
|
|
|
|
int vY = loc[1];
|
|
|
|
|
|
|
|
|
|
int left = vX - x;
|
|
|
|
|
int top = vY - y;
|
|
|
|
|
r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean dispatchUnhandledMove(View focused, int direction) {
|
|
|
|
|
// Consume the unhandled move if a container is open, to avoid switching pages underneath.
|
|
|
|
|
return AbstractFloatingView.getTopOpenView(mActivity) != null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
|
|
|
|
|
View topView = AbstractFloatingView.getTopOpenView(mActivity);
|
|
|
|
|
if (topView != null) {
|
|
|
|
|
return topView.requestFocus(direction, previouslyFocusedRect);
|
|
|
|
|
} else {
|
|
|
|
|
return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
|
|
|
|
|
View topView = AbstractFloatingView.getTopOpenView(mActivity);
|
|
|
|
|
if (topView != null) {
|
|
|
|
|
topView.addFocusables(views, direction);
|
|
|
|
|
} else {
|
|
|
|
|
super.addFocusables(views, direction, focusableMode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setTouchCompleteListener(TouchCompleteListener listener) {
|
|
|
|
|
mTouchCompleteListener = listener;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public interface TouchCompleteListener {
|
|
|
|
|
void onTouchComplete();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public LayoutParams generateLayoutParams(AttributeSet attrs) {
|
|
|
|
|
return new LayoutParams(getContext(), attrs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected LayoutParams generateDefaultLayoutParams() {
|
|
|
|
|
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Override to allow type-checking of LayoutParams.
|
|
|
|
|
@Override
|
|
|
|
|
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
|
|
|
|
return p instanceof LayoutParams;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
|
|
|
|
|
return new LayoutParams(p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
|
|
|
|
|
public int x, y;
|
|
|
|
|
public boolean customPosition = false;
|
|
|
|
|
|
|
|
|
|
public LayoutParams(Context c, AttributeSet attrs) {
|
|
|
|
|
super(c, attrs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public LayoutParams(int width, int height) {
|
|
|
|
|
super(width, height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public LayoutParams(ViewGroup.LayoutParams lp) {
|
|
|
|
|
super(lp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setWidth(int width) {
|
|
|
|
|
this.width = width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getWidth() {
|
|
|
|
|
return width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setHeight(int height) {
|
|
|
|
|
this.height = height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getHeight() {
|
|
|
|
|
return height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setX(int x) {
|
|
|
|
|
this.x = x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getX() {
|
|
|
|
|
return x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setY(int y) {
|
|
|
|
|
this.y = y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getY() {
|
|
|
|
|
return y;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
|
|
|
super.onLayout(changed, l, t, r, b);
|
|
|
|
|
int count = getChildCount();
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
|
View child = getChildAt(i);
|
|
|
|
|
final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
|
|
|
|
|
if (flp instanceof LayoutParams) {
|
|
|
|
|
final LayoutParams lp = (LayoutParams) flp;
|
|
|
|
|
if (lp.customPosition) {
|
|
|
|
|
child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|