зеркало из https://github.com/mozilla/gecko-dev.git
255 строки
8.5 KiB
Java
255 строки
8.5 KiB
Java
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
package org.mozilla.gecko;
|
|
|
|
import android.content.Context;
|
|
import android.support.v4.view.ViewCompat;
|
|
import android.support.v4.widget.ViewDragHelper;
|
|
import android.util.AttributeSet;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.widget.RelativeLayout;
|
|
|
|
/* Outerlayout is the container layout of all the main views. It allows mainlayout to be dragged while targeting
|
|
the toolbar and it's responsible for handling the dragprocess. It relies on ViewDragHelper to ease the drag process.
|
|
*/
|
|
public class OuterLayout extends RelativeLayout {
|
|
private final double AUTO_OPEN_SPEED_LIMIT = 800.0;
|
|
private ViewDragHelper mDragHelper;
|
|
private int mDraggingBorder;
|
|
private int mDragRange;
|
|
private boolean mIsOpen = false;
|
|
private int mDraggingState = ViewDragHelper.STATE_IDLE;
|
|
private DragCallback mDragCallback;
|
|
|
|
public static interface DragCallback {
|
|
public void startDrag(boolean wasOpen);
|
|
public void stopDrag(boolean stoppingToOpen);
|
|
public int getDragRange();
|
|
public int getOrderedChildIndex(int index);
|
|
public boolean canDrag(MotionEvent event);
|
|
public boolean canInterceptEventWhileOpen(MotionEvent event);
|
|
public void onDragProgress(float progress);
|
|
public View getViewToDrag();
|
|
public int getLowerLimit();
|
|
}
|
|
|
|
private class DragHelperCallback extends ViewDragHelper.Callback {
|
|
@Override
|
|
public void onViewDragStateChanged(int newState) {
|
|
if (newState == mDraggingState) { // no change
|
|
return;
|
|
}
|
|
|
|
// if the view stopped moving.
|
|
if ((mDraggingState == ViewDragHelper.STATE_DRAGGING || mDraggingState == ViewDragHelper.STATE_SETTLING) &&
|
|
newState == ViewDragHelper.STATE_IDLE) {
|
|
|
|
final float rangeToCheck = mDragRange;
|
|
final float lowerLimit = mDragCallback.getLowerLimit();
|
|
if (mDraggingBorder == lowerLimit) {
|
|
mIsOpen = false;
|
|
mDragCallback.onDragProgress(0);
|
|
} else if (mDraggingBorder == rangeToCheck) {
|
|
mIsOpen = true;
|
|
mDragCallback.onDragProgress(1);
|
|
}
|
|
mDragCallback.stopDrag(mIsOpen);
|
|
}
|
|
|
|
// The view was previuosly moving.
|
|
if (newState == ViewDragHelper.STATE_DRAGGING && !isMoving()) {
|
|
mDragCallback.startDrag(mIsOpen);
|
|
updateRanges();
|
|
}
|
|
|
|
mDraggingState = newState;
|
|
}
|
|
|
|
@Override
|
|
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
|
mDraggingBorder = top;
|
|
final float progress = Math.min(1, ((float) top) / mDragRange);
|
|
mDragCallback.onDragProgress(progress);
|
|
}
|
|
|
|
@Override
|
|
public int getViewVerticalDragRange(View child) {
|
|
return mDragRange;
|
|
}
|
|
|
|
@Override
|
|
public int getOrderedChildIndex(int index) {
|
|
return mDragCallback.getOrderedChildIndex(index);
|
|
}
|
|
|
|
@Override
|
|
public boolean tryCaptureView(View view, int i) {
|
|
return (view.getId() == mDragCallback.getViewToDrag().getId());
|
|
}
|
|
|
|
@Override
|
|
public int clampViewPositionVertical(View child, int top, int dy) {
|
|
return top;
|
|
}
|
|
|
|
@Override
|
|
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
|
final float rangeToCheck = mDragRange;
|
|
final float speedToCheck = yvel;
|
|
|
|
if (mDraggingBorder == mDragCallback.getLowerLimit()) {
|
|
return;
|
|
}
|
|
|
|
if (mDraggingBorder == rangeToCheck) {
|
|
return;
|
|
}
|
|
|
|
boolean settleToOpen = false;
|
|
// Speed has priority over position.
|
|
if (speedToCheck > AUTO_OPEN_SPEED_LIMIT) {
|
|
settleToOpen = true;
|
|
} else if (speedToCheck < -AUTO_OPEN_SPEED_LIMIT) {
|
|
settleToOpen = false;
|
|
} else if (mDraggingBorder > rangeToCheck / 2) {
|
|
settleToOpen = true;
|
|
} else if (mDraggingBorder < rangeToCheck / 2) {
|
|
settleToOpen = false;
|
|
}
|
|
|
|
final int settleDestX;
|
|
final int settleDestY;
|
|
if (settleToOpen) {
|
|
settleDestX = 0;
|
|
settleDestY = mDragRange;
|
|
} else {
|
|
settleDestX = 0;
|
|
settleDestY = mDragCallback.getLowerLimit();
|
|
}
|
|
|
|
if(mDragHelper.settleCapturedViewAt(settleDestX, settleDestY)) {
|
|
ViewCompat.postInvalidateOnAnimation(OuterLayout.this);
|
|
}
|
|
}
|
|
}
|
|
|
|
public OuterLayout(Context context, AttributeSet attrs) {
|
|
super(context, attrs);
|
|
}
|
|
|
|
private void updateRanges() {
|
|
// Need to wait for the tabs to show in order to fetch the right sizes.
|
|
mDragRange = mDragCallback.getDragRange() + mDragCallback.getLowerLimit();
|
|
}
|
|
|
|
private void updateOrientation() {
|
|
mDragHelper.setEdgeTrackingEnabled(0);
|
|
}
|
|
|
|
@Override
|
|
protected void onFinishInflate() {
|
|
mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
|
|
mIsOpen = false;
|
|
super.onFinishInflate();
|
|
}
|
|
|
|
@Override
|
|
public boolean onInterceptTouchEvent(MotionEvent event) {
|
|
if (mDragCallback.canDrag(event)) {
|
|
if (mDragHelper.shouldInterceptTouchEvent(event)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Because while open the target layout is translated and draghelper does not catch it.
|
|
if (mIsOpen && mDragCallback.canInterceptEventWhileOpen(event)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
// touch events can be passed to the helper if we target the toolbar or we are already dragging.
|
|
if (mDragCallback.canDrag(event) || mDraggingState == ViewDragHelper.STATE_DRAGGING) {
|
|
mDragHelper.processTouchEvent(event);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
// The first time fennec is started, tabs might not have been created while we drag. In that case we need
|
|
// an arbitrary range to start dragging that will be updated as soon as the tabs are created.
|
|
|
|
if (mDragRange == 0) {
|
|
mDragRange = h / 2;
|
|
}
|
|
super.onSizeChanged(w, h, oldw, oldh);
|
|
}
|
|
|
|
@Override
|
|
public void computeScroll() { // needed for automatic settling.
|
|
if (mDragHelper.continueSettling(true)) {
|
|
ViewCompat.postInvalidateOnAnimation(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* To be called when closing the tabs from outside (i.e. when touching the main layout).
|
|
*/
|
|
public void setClosed() {
|
|
mIsOpen = false;
|
|
mDragHelper.abort();
|
|
}
|
|
|
|
/**
|
|
* To be called when opening the tabs from outside (i.e. when clicking on the tabs button).
|
|
*/
|
|
public void setOpen() {
|
|
mIsOpen = true;
|
|
mDragHelper.abort();
|
|
}
|
|
|
|
public void setDraggableCallback(DragCallback dragCallback) {
|
|
mDragCallback = dragCallback;
|
|
updateOrientation();
|
|
}
|
|
|
|
// If a change happens while we are dragging, we abort the dragging and set to open state.
|
|
public void reset() {
|
|
updateOrientation();
|
|
if (isMoving()) {
|
|
mDragHelper.abort();
|
|
if (mDragCallback != null) {
|
|
mDragCallback.stopDrag(false);
|
|
mDragCallback.onDragProgress(0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void updateDragHelperParameters() {
|
|
mDragRange = mDragCallback.getDragRange() + mDragCallback.getLowerLimit();
|
|
updateOrientation();
|
|
}
|
|
|
|
public boolean isMoving() {
|
|
return (mDraggingState == ViewDragHelper.STATE_DRAGGING ||
|
|
mDraggingState == ViewDragHelper.STATE_SETTLING);
|
|
}
|
|
|
|
public boolean isOpen() {
|
|
return mIsOpen;
|
|
}
|
|
|
|
public View findTopChildUnder(MotionEvent event) {
|
|
return mDragHelper.findTopChildUnder((int) event.getX(), (int) event.getY());
|
|
}
|
|
|
|
public void restoreTargetViewPosition() {
|
|
mDragCallback.getViewToDrag().offsetTopAndBottom(mDraggingBorder);
|
|
}
|
|
}
|