| Index: ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceInflater.java
|
| diff --git a/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceInflater.java b/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceInflater.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..38888dd17f86d6ad7edfffb3ef59670a0daf5bfa
|
| --- /dev/null
|
| +++ b/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceInflater.java
|
| @@ -0,0 +1,376 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.ui.resources.dynamics;
|
| +
|
| +import android.content.Context;
|
| +import android.view.LayoutInflater;
|
| +import android.view.View;
|
| +import android.view.ViewGroup;
|
| +import android.view.ViewTreeObserver;
|
| +
|
| +/**
|
| + * ViewResourceInflater is a utility class that facilitates using an Android View as a dynamic
|
| + * resource, which can be later used as a compositor layer. This class assumes that the View
|
| + * is defined declaratively, using a XML Layout file, and that the View that is going to be
|
| + * inflated is the single top-level View of the layout (its root).
|
| + *
|
| + * By default, the View is inflated without being attached to the hierarchy, which allows
|
| + * subclasses to read/modify the View "offscreen", via the method {@link #onFinishInflate()}.
|
| + * Only when a new snapshot of the View is required, which happens when the method
|
| + * {@link #invalidate()} is called, and the View is automatically detached from the
|
| + * hierarchy after the snapshot is captured.
|
| + *
|
| + * There's also an option to not attach to the hierarchy at all, by overriding the method
|
| + * {@link #shouldAttachView()} and making it return false (the default is yes). In this case
|
| + * the changes to the View will always be "offscreen". By default, an unspecified value of
|
| + * {@link View.MeasureSpec} will de used to determine the width and height of the View.
|
| + * It's possible to specify custom size constraints by overriding the methods
|
| + * {@link #getWidthMeasureSpec()} and {@link #getHeightMeasureSpec()}.
|
| + */
|
| +public class ViewResourceInflater {
|
| +
|
| + /**
|
| + * The id of the XML Layout that describes the View.
|
| + */
|
| + private int mLayoutId;
|
| +
|
| + /**
|
| + * The id of the View being inflated, which must be the root of the given Layout.
|
| + */
|
| + private int mViewId;
|
| +
|
| + /**
|
| + * The Context used to inflate the View.
|
| + */
|
| + private Context mContext;
|
| +
|
| + /**
|
| + * The ViewGroup container used to inflate the View.
|
| + */
|
| + private ViewGroup mContainer;
|
| +
|
| + /**
|
| + * The DynamicResourceLoader used to manage resources generated dynamically.
|
| + */
|
| + private DynamicResourceLoader mResourceLoader;
|
| +
|
| + /**
|
| + * The ViewResourceAdapter used to capture snapshots of the View.
|
| + */
|
| + private ViewResourceAdapter mResourceAdapter;
|
| +
|
| + /**
|
| + * The inflated View.
|
| + */
|
| + private View mView;
|
| +
|
| + /**
|
| + * Whether the View is invalided.
|
| + */
|
| + private boolean mIsInvalidated;
|
| +
|
| + /**
|
| + * Whether the View is attached to the hierarchy.
|
| + */
|
| + private boolean mIsAttached;
|
| +
|
| + /**
|
| + * The ViewInflaterOnDrawListener used to track changes in the View when attached.
|
| + */
|
| + private ViewInflaterOnDrawListener mOnDrawListener;
|
| +
|
| + /**
|
| + * The invalid ID.
|
| + */
|
| + private static final int INVALID_ID = -1;
|
| +
|
| + /**
|
| + * @param layoutId The XML Layout that declares the View.
|
| + * @param viewId The id of the root View of the Layout.
|
| + * @param context The Android Context used to inflate the View.
|
| + * @param container The container View used to inflate the View.
|
| + * @param resourceLoader The resource loader that will handle the snapshot capturing.
|
| + */
|
| + public ViewResourceInflater(int layoutId,
|
| + int viewId,
|
| + Context context,
|
| + ViewGroup container,
|
| + DynamicResourceLoader resourceLoader) {
|
| + mLayoutId = layoutId;
|
| + mViewId = viewId;
|
| + mContext = context;
|
| + mContainer = container;
|
| + mResourceLoader = resourceLoader;
|
| + }
|
| +
|
| + /**
|
| + * Inflate the layout.
|
| + */
|
| + public void inflate() {
|
| + if (mView != null) return;
|
| +
|
| + // Inflate the View without attaching to hierarchy (attachToRoot param is false).
|
| + mView = LayoutInflater.from(mContext).inflate(mLayoutId, mContainer, false);
|
| +
|
| + // Make sure the View we just inflated is the right one.
|
| + assert mView.getId() == mViewId;
|
| +
|
| + // Allow subclasses to access/modify the View before it's attached
|
| + // to the hierarchy (if allowed) or snapshots are captured.
|
| + onFinishInflate();
|
| +
|
| + registerResource();
|
| + }
|
| +
|
| + /**
|
| + * Invalidate the inflated View, causing a snapshot of the View to be captured.
|
| + */
|
| + public void invalidate() {
|
| + // View must be inflated at this point. If it's not, do it now.
|
| + if (mView == null) {
|
| + inflate();
|
| + }
|
| +
|
| + mIsInvalidated = true;
|
| +
|
| + // If the View is already attached, we don't need to do anything because the
|
| + // snapshot will be captured automatically when the View is drawn.
|
| + if (!mIsAttached) {
|
| + if (shouldAttachView()) {
|
| + // TODO(pedrosimonetti): investigate if complex views can be rendered offline.
|
| + // NOTE(pedrosimonetti): it seems that complex views don't get rendered
|
| + // properly if not attached to the hierarchy. The problem seem to be related
|
| + // to the use of the property "layout_gravity: end", possibly in combination
|
| + // of other things like elastic views (layout_weight: 1) and/or fading edges.
|
| + attachView();
|
| + } else {
|
| + // When the View is not attached, we need to manually layout the View
|
| + // and invalidate the resource in order to capture a new snapshot.
|
| + layout();
|
| + invalidateResource();
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Destroy the instance.
|
| + */
|
| + public void destroy() {
|
| + if (mView == null) return;
|
| +
|
| + unregisterResource();
|
| +
|
| + detachView();
|
| + mView = null;
|
| +
|
| + mLayoutId = INVALID_ID;
|
| + mViewId = INVALID_ID;
|
| +
|
| + mContext = null;
|
| + mContainer = null;
|
| + mResourceLoader = null;
|
| + }
|
| +
|
| + /**
|
| + * @return The measured width of the inflated View.
|
| + */
|
| + public int getMeasuredWidth() {
|
| + // View must be inflated at this point.
|
| + assert mView != null;
|
| +
|
| + return mView.getMeasuredWidth();
|
| + }
|
| +
|
| + /**
|
| + * @return The measured height of the inflated View.
|
| + */
|
| + public int getMeasuredHeight() {
|
| + // View must be inflated at this point.
|
| + assert mView != null;
|
| +
|
| + return mView.getMeasuredHeight();
|
| + }
|
| +
|
| + /**
|
| + * @return The id of View, which is used as an identifier for the resource loader.
|
| + */
|
| + public int getViewId() {
|
| + return mViewId;
|
| + }
|
| +
|
| + /**
|
| + * The callback called after inflating the View, allowing subclasses to access/modify
|
| + * the View before it's attached to the hierarchy (if allowed) or snapshots are captured.
|
| + */
|
| + protected void onFinishInflate() {}
|
| +
|
| + /**
|
| + * NOTE(pedrosimonetti): Complex views don't fully work when not attached to the hierarchy.
|
| + * @return Whether the View should be attached to the hierarchy after being inflated.
|
| + * Subclasses should override this method to change the default behavior.
|
| + */
|
| + protected boolean shouldAttachView() {
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the View should be detached from the hierarchy after being captured.
|
| + * Subclasses should override this method to change the default behavior.
|
| + */
|
| + protected boolean shouldDetachViewAfterCapturing() {
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @return The MeasureSpec used for calculating the width of the offscreen View.
|
| + * Subclasses should override this method to specify measurements.
|
| + * By default, this method returns an unspecified MeasureSpec.
|
| + */
|
| + protected int getWidthMeasureSpec() {
|
| + return getUnspecifiedMeasureSpec();
|
| + }
|
| +
|
| + /**
|
| + * @return The MeasureSpec used for calculating the height of the offscreen View.
|
| + * Subclasses should override this method to specify measurements.
|
| + * By default, this method returns an unspecified MeasureSpec.
|
| + */
|
| + protected int getHeightMeasureSpec() {
|
| + return getUnspecifiedMeasureSpec();
|
| + }
|
| +
|
| + /**
|
| + * @return The View resource.
|
| + */
|
| + protected View getView() {
|
| + return mView;
|
| + }
|
| +
|
| + /**
|
| + * Attach the View to the hierarchy.
|
| + */
|
| + private void attachView() {
|
| + if (!mIsAttached) {
|
| + assert mView.getParent() == null;
|
| + mContainer.addView(mView);
|
| + mIsAttached = true;
|
| +
|
| + if (mOnDrawListener == null) {
|
| + // Add a draw listener. For now on, changes in the View will cause a
|
| + // new snapshot to be captured, if the ViewResourceInflater was invalidated.
|
| + mOnDrawListener = new ViewInflaterOnDrawListener();
|
| + mView.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Detach the View from the hierarchy.
|
| + */
|
| + private void detachView() {
|
| + if (mIsAttached) {
|
| + if (mOnDrawListener != null) {
|
| + mView.getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
|
| + mOnDrawListener = null;
|
| + }
|
| +
|
| + assert mView.getParent() != null;
|
| + mContainer.removeView(mView);
|
| + mIsAttached = false;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Layout the View. This is to be used when the View is not attached to the hierarchy.
|
| + */
|
| + private void layout() {
|
| + // View must be inflated at this point.
|
| + assert mView != null;
|
| +
|
| + mView.measure(getWidthMeasureSpec(), getHeightMeasureSpec());
|
| + mView.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
| + }
|
| +
|
| + /**
|
| + * @return An unspecified MeasureSpec value.
|
| + */
|
| + private int getUnspecifiedMeasureSpec() {
|
| + return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
| + }
|
| +
|
| + /**
|
| + * Register the resource and creates an adapter for it.
|
| + */
|
| + private void registerResource() {
|
| + if (mResourceAdapter == null) {
|
| + mResourceAdapter = new ViewInflaterAdapter(mView.findViewById(mViewId));
|
| + }
|
| +
|
| + if (mResourceLoader != null) {
|
| + mResourceLoader.registerResource(mViewId, mResourceAdapter);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Unregister the resource and destroys the adapter.
|
| + */
|
| + private void unregisterResource() {
|
| + if (mResourceLoader != null) {
|
| + mResourceLoader.unregisterResource(mViewId);
|
| + }
|
| +
|
| + mResourceAdapter = null;
|
| + }
|
| +
|
| + /**
|
| + * Invalidate the resource, which will cause a new snapshot to be captured.
|
| + */
|
| + private void invalidateResource() {
|
| + if (mIsInvalidated && mView != null && mResourceAdapter != null) {
|
| + mIsInvalidated = false;
|
| + mResourceAdapter.invalidate(null);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * A custom {@link ViewResourceAdapter} that calls the method {@link #onCaptureEnd()}.
|
| + */
|
| + private class ViewInflaterAdapter extends ViewResourceAdapter {
|
| + public ViewInflaterAdapter(View view) {
|
| + super(view);
|
| + }
|
| +
|
| + @Override
|
| + protected void onCaptureEnd() {
|
| + ViewResourceInflater.this.onCaptureEnd();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a snapshot is captured.
|
| + */
|
| + private void onCaptureEnd() {
|
| + if (shouldDetachViewAfterCapturing()) {
|
| + detachView();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * A custom {@link ViewTreeObserver.OnDrawListener} that calls the method {@link #onDraw()}.
|
| + */
|
| + private class ViewInflaterOnDrawListener implements ViewTreeObserver.OnDrawListener {
|
| + @Override
|
| + public void onDraw() {
|
| + ViewResourceInflater.this.onDraw();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when the View is drawn,
|
| + */
|
| + private void onDraw() {
|
| + invalidateResource();
|
| + }
|
| +}
|
|
|