blob: 77ca541a40a3042827f61dc962805c1cd61aab60 [file] [log] [blame]
/*
* Copyright (C) 2014 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.printspooler.widget;
import android.content.Context;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.android.printspooler.R;
/**
* This class is a layout manager for the print screen. It has a sliding
* area that contains the print options. If the sliding area is open the
* print options are visible and if it is closed a summary of the print
* job is shown. Under the sliding area there is a place for putting
* arbitrary content such as preview, error message, progress indicator,
* etc. The sliding area is covering the content holder under it when
* the former is opened.
*/
@SuppressWarnings("unused")
public final class ContentView extends ViewGroup implements View.OnClickListener {
private static final int FIRST_POINTER_ID = 0;
private final ViewDragHelper mDragger;
private View mStaticContent;
private ViewGroup mSummaryContent;
private View mDynamicContent;
private View mDraggableContent;
private ViewGroup mMoreOptionsContainer;
private ViewGroup mOptionsContainer;
private View mEmbeddedContentContainer;
private View mExpandCollapseHandle;
private View mExpandCollapseIcon;
private int mClosedOptionsOffsetY;
private int mCurrentOptionsOffsetY;
private OptionsStateChangeListener mOptionsStateChangeListener;
private int mOldDraggableHeight;
public interface OptionsStateChangeListener {
public void onOptionsOpened();
public void onOptionsClosed();
}
public ContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mDragger = ViewDragHelper.create(this, new DragCallbacks());
// The options view is sliding under the static header but appears
// after it in the layout, so we will draw in opposite order.
setChildrenDrawingOrderEnabled(true);
}
public void setOptionsStateChangeListener(OptionsStateChangeListener listener) {
mOptionsStateChangeListener = listener;
}
private boolean isOptionsOpened() {
return mCurrentOptionsOffsetY == 0;
}
private boolean isOptionsClosed() {
return mCurrentOptionsOffsetY == mClosedOptionsOffsetY;
}
private void openOptions() {
if (isOptionsOpened()) {
return;
}
mDragger.smoothSlideViewTo(mDynamicContent, mDynamicContent.getLeft(),
getOpenedOptionsY());
invalidate();
}
private void closeOptions() {
if (isOptionsClosed()) {
return;
}
mDragger.smoothSlideViewTo(mDynamicContent, mDynamicContent.getLeft(),
getClosedOptionsY());
invalidate();
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
return childCount - i - 1;
}
@Override
protected void onFinishInflate() {
mStaticContent = findViewById(R.id.static_content);
mSummaryContent = (ViewGroup) findViewById(R.id.summary_content);
mDynamicContent = findViewById(R.id.dynamic_content);
mDraggableContent = findViewById(R.id.draggable_content);
mMoreOptionsContainer = (ViewGroup) findViewById(R.id.more_options_container);
mOptionsContainer = (ViewGroup) findViewById(R.id.options_container);
mEmbeddedContentContainer = findViewById(R.id.embedded_content_container);
mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon);
mExpandCollapseHandle = findViewById(R.id.expand_collapse_handle);
mExpandCollapseIcon.setOnClickListener(this);
mExpandCollapseHandle.setOnClickListener(this);
// Make sure we start in a closed options state.
onDragProgress(1.0f);
}
@Override
public void onClick(View view) {
if (view == mExpandCollapseHandle || view == mExpandCollapseIcon) {
if (isOptionsClosed()) {
openOptions();
} else if (isOptionsOpened()) {
closeOptions();
} // else in open/close progress do nothing.
}
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
/* do nothing */
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragger.processTouchEvent(event);
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mDragger.shouldInterceptTouchEvent(event)
|| super.onInterceptTouchEvent(event);
}
@Override
public void computeScroll() {
if (mDragger.continueSettling(true)) {
postInvalidateOnAnimation();
}
}
private int getOpenedOptionsY() {
return mStaticContent.getBottom();
}
private int getClosedOptionsY() {
return getOpenedOptionsY() + mClosedOptionsOffsetY;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChild(mStaticContent, widthMeasureSpec, heightMeasureSpec);
if (mSummaryContent.getVisibility() != View.GONE) {
measureChild(mSummaryContent, widthMeasureSpec, heightMeasureSpec);
}
measureChild(mDynamicContent, widthMeasureSpec, heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// // The height of the draggable content may change and if that happens
// // we have to adjust the current offset to ensure the sliding area is
// // at the same position.
// mCurrentOptionsOffsetY -= mDraggableContent.getMeasuredHeight()
// - oldDraggableHeight;
if (mOldDraggableHeight != mDraggableContent.getMeasuredHeight()) {
mCurrentOptionsOffsetY -= mDraggableContent.getMeasuredHeight()
- mOldDraggableHeight;
mOldDraggableHeight = mDraggableContent.getMeasuredHeight();
}
// The height of the draggable content may change and if that happens
// we have to adjust the sliding area closed state offset.
mClosedOptionsOffsetY = mSummaryContent.getMeasuredHeight()
- mDraggableContent.getMeasuredHeight();
// The content host must be maximally large size that fits entirely
// on the screen when the options are collapsed.
ViewGroup.LayoutParams params = mEmbeddedContentContainer.getLayoutParams();
if (params.height == 0) {
params.height = heightSize - mStaticContent.getMeasuredHeight()
- mSummaryContent.getMeasuredHeight() - mDynamicContent.getMeasuredHeight()
+ mDraggableContent.getMeasuredHeight();
mCurrentOptionsOffsetY = mClosedOptionsOffsetY;
}
// The content host can grow vertically as much as needed - we will be covering it.
final int hostHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
measureChild(mEmbeddedContentContainer, widthMeasureSpec, hostHeightMeasureSpec);
setMeasuredDimension(resolveSize(MeasureSpec.getSize(widthMeasureSpec), widthMeasureSpec),
resolveSize(heightSize, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mStaticContent.layout(left, top, right, mStaticContent.getMeasuredHeight());
if (mSummaryContent.getVisibility() != View.GONE) {
mSummaryContent.layout(left, mStaticContent.getMeasuredHeight(), right,
mStaticContent.getMeasuredHeight() + mSummaryContent.getMeasuredHeight());
}
final int dynContentTop = mStaticContent.getMeasuredHeight() + mCurrentOptionsOffsetY;
final int dynContentBottom = dynContentTop + mDynamicContent.getMeasuredHeight();
mDynamicContent.layout(left, dynContentTop, right, dynContentBottom);
final int embContentTop = mStaticContent.getMeasuredHeight() + mClosedOptionsOffsetY
+ mDynamicContent.getMeasuredHeight();
final int embContentBottom = embContentTop + mEmbeddedContentContainer.getMeasuredHeight();
mEmbeddedContentContainer.layout(left, embContentTop, right, embContentBottom);
}
private void onDragProgress(float progress) {
final int summaryCount = mSummaryContent.getChildCount();
for (int i = 0; i < summaryCount; i++) {
View child = mSummaryContent.getChildAt(i);
child.setAlpha(progress);
}
if (progress == 0) {
if (mOptionsStateChangeListener != null) {
mOptionsStateChangeListener.onOptionsOpened();
}
mSummaryContent.setVisibility(View.GONE);
mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_less);
} else {
mSummaryContent.setVisibility(View.VISIBLE);
}
final float inverseAlpha = 1.0f - progress;
final int optionCount = mOptionsContainer.getChildCount();
for (int i = 0; i < optionCount; i++) {
View child = mOptionsContainer.getChildAt(i);
child.setAlpha(inverseAlpha);
}
if (mMoreOptionsContainer.getVisibility() != View.GONE) {
final int moreOptionCount = mMoreOptionsContainer.getChildCount();
for (int i = 0; i < moreOptionCount; i++) {
View child = mMoreOptionsContainer.getChildAt(i);
child.setAlpha(inverseAlpha);
}
}
if (inverseAlpha == 0) {
if (mOptionsStateChangeListener != null) {
mOptionsStateChangeListener.onOptionsClosed();
}
if (mMoreOptionsContainer.getVisibility() != View.GONE) {
mMoreOptionsContainer.setVisibility(View.INVISIBLE);
}
mDraggableContent.setVisibility(View.INVISIBLE);
mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_more);
} else {
if (mMoreOptionsContainer.getVisibility() != View.GONE) {
mMoreOptionsContainer.setVisibility(View.VISIBLE);
}
mDraggableContent.setVisibility(View.VISIBLE);
}
}
private final class DragCallbacks extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mDynamicContent && pointerId == FIRST_POINTER_ID;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
mCurrentOptionsOffsetY += dy;
final float progress = ((float) top - getOpenedOptionsY())
/ (getClosedOptionsY() - getOpenedOptionsY());
mDraggableContent.notifySubtreeAccessibilityStateChangedIfNeeded();
onDragProgress(progress);
}
public void onViewReleased(View child, float velocityX, float velocityY) {
final int childTop = child.getTop();
final int openedOptionsY = getOpenedOptionsY();
final int closedOptionsY = getClosedOptionsY();
if (childTop == openedOptionsY || childTop == closedOptionsY) {
return;
}
final int halfRange = closedOptionsY + (openedOptionsY - closedOptionsY) / 2;
if (childTop < halfRange) {
mDragger.smoothSlideViewTo(child, child.getLeft(), closedOptionsY);
} else {
mDragger.smoothSlideViewTo(child, child.getLeft(), openedOptionsY);
}
invalidate();
}
public int getViewVerticalDragRange(View child) {
return mDraggableContent.getHeight();
}
public int clampViewPositionVertical(View child, int top, int dy) {
final int staticOptionBottom = mStaticContent.getBottom();
return Math.max(Math.min(top, getOpenedOptionsY()), getClosedOptionsY());
}
}
}