diff options
Diffstat (limited to 'packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java')
-rw-r--r-- | packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java new file mode 100644 index 0000000..b4a78e6 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java @@ -0,0 +1,408 @@ +/* + * 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 PrintContentView extends ViewGroup implements View.OnClickListener { + private static final int FIRST_POINTER_ID = 0; + + private final ViewDragHelper mDragger; + + private final int mScrimColor; + + private View mStaticContent; + private ViewGroup mSummaryContent; + private View mDynamicContent; + + private View mDraggableContent; + private View mPrintButton; + private ViewGroup mMoreOptionsContainer; + private ViewGroup mOptionsContainer; + + private View mEmbeddedContentContainer; + private View mEmbeddedContentScrim; + + private View mExpandCollapseHandle; + private View mExpandCollapseIcon; + + private int mClosedOptionsOffsetY; + private int mCurrentOptionsOffsetY = Integer.MIN_VALUE; + + private OptionsStateChangeListener mOptionsStateChangeListener; + + private OptionsStateController mOptionsStateController; + + private int mOldDraggableHeight; + + private float mDragProgress; + + public interface OptionsStateChangeListener { + public void onOptionsOpened(); + public void onOptionsClosed(); + } + + public interface OptionsStateController { + public boolean canOpenOptions(); + public boolean canCloseOptions(); + } + + public PrintContentView(Context context, AttributeSet attrs) { + super(context, attrs); + mDragger = ViewDragHelper.create(this, new DragCallbacks()); + + mScrimColor = context.getResources().getColor(R.color.print_preview_scrim_color); + + // 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; + } + + public void setOpenOptionsController(OptionsStateController controller) { + mOptionsStateController = controller; + } + + public boolean isOptionsOpened() { + return mCurrentOptionsOffsetY == 0; + } + + private boolean isOptionsClosed() { + return mCurrentOptionsOffsetY == mClosedOptionsOffsetY; + } + + public void openOptions() { + if (isOptionsOpened()) { + return; + } + mDragger.smoothSlideViewTo(mDynamicContent, mDynamicContent.getLeft(), + getOpenedOptionsY()); + invalidate(); + } + + public 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); + mPrintButton = findViewById(R.id.print_button); + mMoreOptionsContainer = (ViewGroup) findViewById(R.id.more_options_container); + mOptionsContainer = (ViewGroup) findViewById(R.id.options_container); + mEmbeddedContentContainer = findViewById(R.id.embedded_content_container); + mEmbeddedContentScrim = findViewById(R.id.embedded_content_scrim); + mExpandCollapseHandle = findViewById(R.id.expand_collapse_handle); + mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon); + + mExpandCollapseHandle.setOnClickListener(this); + + // Make sure we start in a closed options state. + onDragProgress(1.0f); + } + + @Override + public void onClick(View view) { + if (view == mExpandCollapseHandle) { + if (isOptionsClosed() && mOptionsStateController.canOpenOptions()) { + openOptions(); + } else if (isOptionsOpened() && mOptionsStateController.canCloseOptions()) { + closeOptions(); + } // else in open/close progress do nothing. + } else if (view == mEmbeddedContentScrim) { + if (isOptionsOpened() && mOptionsStateController.canCloseOptions()) { + closeOptions(); + } + } + } + + @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 computeScrimColor() { + final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; + final int adjustedAlpha = (int) (baseAlpha * (1 - mDragProgress)); + return adjustedAlpha << 24 | (mScrimColor & 0xffffff); + } + + private int getOpenedOptionsY() { + return mStaticContent.getBottom(); + } + + private int getClosedOptionsY() { + return getOpenedOptionsY() + mClosedOptionsOffsetY; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final boolean wasOpened = isOptionsOpened(); + + measureChild(mStaticContent, widthMeasureSpec, heightMeasureSpec); + + if (mSummaryContent.getVisibility() != View.GONE) { + measureChild(mSummaryContent, widthMeasureSpec, heightMeasureSpec); + } + + measureChild(mDynamicContent, widthMeasureSpec, heightMeasureSpec); + + measureChild(mPrintButton, widthMeasureSpec, heightMeasureSpec); + + // 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(); + + if (mCurrentOptionsOffsetY == Integer.MIN_VALUE) { + mCurrentOptionsOffsetY = mClosedOptionsOffsetY; + } + + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + // The content host must be maximally large size that fits entirely + // on the screen when the options are collapsed. + ViewGroup.LayoutParams params = mEmbeddedContentContainer.getLayoutParams(); + params.height = heightSize - mStaticContent.getMeasuredHeight() + - mSummaryContent.getMeasuredHeight() - mDynamicContent.getMeasuredHeight() + + mDraggableContent.getMeasuredHeight(); + + // 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 correct position. + if (mOldDraggableHeight != mDraggableContent.getMeasuredHeight()) { + if (mOldDraggableHeight != 0) { + mCurrentOptionsOffsetY = wasOpened ? 0 : mClosedOptionsOffsetY; + } + mOldDraggableHeight = mDraggableContent.getMeasuredHeight(); + } + + // 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); + + MarginLayoutParams params = (MarginLayoutParams) mPrintButton.getLayoutParams(); + final int rightMargin = params.rightMargin; + final int printButtonLeft = right - mPrintButton.getMeasuredWidth() - rightMargin; + final int printButtonTop = dynContentBottom - mPrintButton.getMeasuredHeight() / 2; + final int printButtonRight = printButtonLeft + mPrintButton.getMeasuredWidth(); + final int printButtonBottom = printButtonTop + mPrintButton.getMeasuredHeight(); + + mPrintButton.layout(printButtonLeft, printButtonTop, printButtonRight, printButtonBottom); + + final int embContentTop = mStaticContent.getMeasuredHeight() + mClosedOptionsOffsetY + + mDynamicContent.getMeasuredHeight(); + final int embContentBottom = embContentTop + mEmbeddedContentContainer.getMeasuredHeight(); + + mEmbeddedContentContainer.layout(left, embContentTop, right, embContentBottom); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new ViewGroup.MarginLayoutParams(getContext(), attrs); + } + + private void onDragProgress(float progress) { + if (Float.compare(mDragProgress, progress) == 0) { + return; + } + + if ((mDragProgress == 0 && progress > 0) + || (mDragProgress == 1.0f && progress < 1.0f)) { + mSummaryContent.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mDraggableContent.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mMoreOptionsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + if ((mDragProgress > 0 && progress == 0) + || (mDragProgress < 1.0f && progress == 1.0f)) { + mSummaryContent.setLayerType(View.LAYER_TYPE_NONE, null); + mDraggableContent.setLayerType(View.LAYER_TYPE_NONE, null); + mMoreOptionsContainer.setLayerType(View.LAYER_TYPE_NONE, null); + } + + mDragProgress = progress; + + mSummaryContent.setAlpha(progress); + + final float inverseAlpha = 1.0f - progress; + mOptionsContainer.setAlpha(inverseAlpha); + mMoreOptionsContainer.setAlpha(inverseAlpha); + + mEmbeddedContentScrim.setBackgroundColor(computeScrimColor()); + + if (progress == 0) { + if (mOptionsStateChangeListener != null) { + mOptionsStateChangeListener.onOptionsOpened(); + } + mSummaryContent.setVisibility(View.GONE); + mEmbeddedContentScrim.setOnClickListener(this); + mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_less); + } else { + mSummaryContent.setVisibility(View.VISIBLE); + } + + if (progress == 1.0f) { + if (mOptionsStateChangeListener != null) { + mOptionsStateChangeListener.onOptionsClosed(); + } + if (mMoreOptionsContainer.getVisibility() != View.GONE) { + mMoreOptionsContainer.setVisibility(View.INVISIBLE); + } + mDraggableContent.setVisibility(View.INVISIBLE); + // If we change the scrim visibility the dimming is lagging + // and is janky. Now it is there but transparent, doing nothing. + mEmbeddedContentScrim.setOnClickListener(null); + mEmbeddedContentScrim.setClickable(false); + 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) { + if (isOptionsOpened() && !mOptionsStateController.canCloseOptions() + || isOptionsClosed() && !mOptionsStateController.canOpenOptions()) { + return false; + } + return child == mDynamicContent && pointerId == FIRST_POINTER_ID; + } + + @Override + public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + if ((isOptionsClosed() || isOptionsClosed()) && dy <= 0) { + return; + } + + mCurrentOptionsOffsetY += dy; + final float progress = ((float) top - getOpenedOptionsY()) + / (getClosedOptionsY() - getOpenedOptionsY()); + + mPrintButton.offsetTopAndBottom(dy); + + 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 getOrderedChildIndex(int index) { + return getChildCount() - index - 1; + } + + 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()); + } + } +} |