react-native/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackStyleInterpolator.js
Adam Comella 54beee2616 Android: Reduce overdraw layers by hiding cards when they are not visible
Summary:
Cards which are not visible because another card is occluding them are still being rendered by Android resulting in overdraw. This results in wasted GPU time because some pixels are drawn multiple times. This change reduces overdraw by changing the opacity of occluded cards to 0.

This bug was found using the tools described in Android's overdraw docs: https://developer.android.com/topic/performance/rendering/overdraw.html

**Test plan (required)**

This change is being used in my team's app.

Adam Comella
Microsoft Corp.
Closes https://github.com/facebook/react-native/pull/10908

Differential Revision: D4175758

Pulled By: ericvicenti

fbshipit-source-id: 4bfac7df16d2a7ea67db977659237a9aa6598f87
2016-11-14 08:59:24 -08:00

177 lines
4.6 KiB
JavaScript

/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
* all intellectual property and other proprietary rights, in and to the React
* Native CustomComponents software (the "Software"). Subject to your
* compliance with these terms, you are hereby granted a non-exclusive,
* worldwide, royalty-free copyright license to (1) use and copy the Software;
* and (2) reproduce and distribute the Software as part of your own software
* ("Your Software"). Facebook reserves all rights not expressly granted to
* you in this license agreement.
*
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @providesModule NavigationCardStackStyleInterpolator
* @flow
*/
'use strict';
const I18nManager = require('I18nManager');
import type {
NavigationSceneRendererProps,
} from 'NavigationTypeDefinition';
/**
* Utility that builds the style for the card in the cards stack.
*
* +------------+
* +-+ |
* +-+ | |
* | | | |
* | | | Focused |
* | | | Card |
* | | | |
* +-+ | |
* +-+ |
* +------------+
*/
/**
* Render the initial style when the initial layout isn't measured yet.
*/
function forInitial(props: NavigationSceneRendererProps): Object {
const {
navigationState,
scene,
} = props;
const focused = navigationState.index === scene.index;
const opacity = focused ? 1 : 0;
// If not focused, move the scene to the far away.
const translate = focused ? 0 : 1000000;
return {
opacity,
transform: [
{ translateX: translate },
{ translateY: translate },
],
};
}
function forHorizontal(props: NavigationSceneRendererProps): Object {
const {
layout,
position,
scene,
} = props;
if (!layout.isMeasured) {
return forInitial(props);
}
const index = scene.index;
const inputRange = [index - 1, index, index + 0.99, index + 1];
const width = layout.initWidth;
const outputRange = I18nManager.isRTL ?
([-width, 0, 10, 10]: Array<number>) :
([width, 0, -10, -10]: Array<number>);
const opacity = position.interpolate({
inputRange,
outputRange: ([1, 1, 0.3, 0]: Array<number>),
});
const scale = position.interpolate({
inputRange,
outputRange: ([1, 1, 0.95, 0.95]: Array<number>),
});
const translateY = 0;
const translateX = position.interpolate({
inputRange,
outputRange,
});
return {
opacity,
transform: [
{ scale },
{ translateX },
{ translateY },
],
};
}
function forVertical(props: NavigationSceneRendererProps): Object {
const {
layout,
position,
scene,
} = props;
if (!layout.isMeasured) {
return forInitial(props);
}
const index = scene.index;
const inputRange = [index - 1, index, index + 0.99, index + 1];
const height = layout.initHeight;
const opacity = position.interpolate({
inputRange,
outputRange: ([1, 1, 0.3, 0]: Array<number>),
});
const scale = position.interpolate({
inputRange,
outputRange: ([1, 1, 0.95, 0.95]: Array<number>),
});
const translateX = 0;
const translateY = position.interpolate({
inputRange,
outputRange: ([height, 0, -10, -10]: Array<number>),
});
return {
opacity,
transform: [
{ scale },
{ translateX },
{ translateY },
],
};
}
function canUseNativeDriver(isVertical: boolean): boolean {
// The native driver can be enabled for this interpolator because the scale,
// translateX, and translateY transforms are supported with the native
// animation driver.
return true;
}
module.exports = {
forHorizontal,
forVertical,
canUseNativeDriver,
};