blob: 93a59740d09bf627010de851293f1506084e9273 [file] [log] [blame]
/*
* Copyright (C) 2016 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.calculator2;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
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.FrameLayout;
import android.widget.RelativeLayout;
public class DragLayout extends RelativeLayout {
private static final double AUTO_OPEN_SPEED_LIMIT = 800.0;
private static final String KEY_IS_OPEN = "IS_OPEN";
private static final String KEY_SUPER_STATE = "SUPER_STATE";
private final Rect mHitRect = new Rect();
private CalculatorDisplay mCalculatorDisplay;
private FrameLayout mHistoryFrame;
private ViewDragHelper mDragHelper;
private OnDragCallback mOnDragCallback;
private int mDraggingState = ViewDragHelper.STATE_IDLE;
private int mDraggingBorder;
private int mVerticalRange;
private boolean mIsOpen;
public DragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
mHistoryFrame = (FrameLayout) findViewById(R.id.history_frame);
mCalculatorDisplay = (CalculatorDisplay) findViewById(R.id.display);
super.onFinishInflate();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
mHistoryFrame.setTranslationY(-(b - t) + mCalculatorDisplay.getBottom());
if (mIsOpen) {
setOpen();
}
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mVerticalRange = h - mCalculatorDisplay.getMeasuredHeight();
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected Parcelable onSaveInstanceState() {
final Bundle bundle = new Bundle();
bundle.putParcelable(KEY_SUPER_STATE, super.onSaveInstanceState());
bundle.putBoolean(KEY_IS_OPEN, mIsOpen);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle bundle = (Bundle) state;
mIsOpen = bundle.getBoolean(KEY_IS_OPEN);
state = bundle.getParcelable(KEY_SUPER_STATE);
}
super.onRestoreInstanceState(state);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return (isDisplayTarget(event) || isHistoryTarget(event))
&& mDragHelper.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isDisplayTarget(event) || isHistoryTarget(event) || isMoving()) {
mDragHelper.processTouchEvent(event);
return true;
} else {
return super.onTouchEvent(event);
}
}
@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
private void onStartDragging() {
mOnDragCallback.onStartDragging();
mHistoryFrame.setVisibility(VISIBLE);
}
private boolean isViewTarget(View view, MotionEvent event) {
view.getHitRect(mHitRect);
offsetDescendantRectToMyCoords(view, mHitRect);
return mHitRect.contains((int) event.getRawX(), (int) event.getRawY());
}
private boolean isDisplayTarget(MotionEvent event) {
return isViewTarget(mCalculatorDisplay, event);
}
private boolean isHistoryTarget(MotionEvent event) {
return isViewTarget(mHistoryFrame, event);
}
public boolean isMoving() {
return mDraggingState == ViewDragHelper.STATE_DRAGGING
|| mDraggingState == ViewDragHelper.STATE_SETTLING;
}
public boolean isOpen() {
return mIsOpen;
}
public void setOpen() {
mDragHelper.smoothSlideViewTo(mHistoryFrame, 0, mVerticalRange);
mIsOpen = true;
mHistoryFrame.setVisibility(VISIBLE);
}
public void setClosed() {
mDragHelper.smoothSlideViewTo(mHistoryFrame, 0, 0);
mIsOpen = false;
mOnDragCallback.onDragToClose();
mHistoryFrame.setVisibility(GONE);
}
public void setOnDragCallback(OnDragCallback callback) {
mOnDragCallback = callback;
}
public interface OnDragCallback {
// Callback when a drag in any direction begins.
void onStartDragging();
// Callback when a drag is used to close.
void onDragToClose();
}
public class DragHelperCallback extends ViewDragHelper.Callback {
@Override
public void onViewDragStateChanged(int state) {
if (state == mDraggingState) {
// No change.
return;
}
if ((mDraggingState == ViewDragHelper.STATE_DRAGGING
|| mDraggingState == ViewDragHelper.STATE_SETTLING)
&& state == ViewDragHelper.STATE_IDLE) {
// The view stopped moving.
if (mDraggingBorder == 0) {
setClosed();
} else if (mDraggingBorder == mVerticalRange) {
setOpen();
}
}
if (state == ViewDragHelper.STATE_DRAGGING) {
onStartDragging();
}
mDraggingState = state;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
mDraggingBorder = top;
}
@Override
public int getViewVerticalDragRange(View child) {
return mVerticalRange;
}
@Override
public boolean tryCaptureView(View view, int i) {
return view.getId() == R.id.history_frame;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topBound = getPaddingTop();
final int bottomBound = mVerticalRange;
return Math.min(Math.max(top, topBound), bottomBound);
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (mDraggingBorder == 0) {
setClosed();
return;
}
if (mDraggingBorder == mVerticalRange) {
setOpen();
return;
}
boolean settleToOpen = false;
final float threshold = mVerticalRange / 2;
if (yvel > AUTO_OPEN_SPEED_LIMIT) {
// Speed has priority over position.
settleToOpen = true;
} else if (yvel < -AUTO_OPEN_SPEED_LIMIT) {
settleToOpen = false;
} else if (mDraggingBorder > threshold) {
settleToOpen = true;
} else if (mDraggingBorder < threshold) {
settleToOpen = false;
}
if (mDragHelper.settleCapturedViewAt(0, settleToOpen ? mVerticalRange : 0)) {
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
}
}
}