/**
* Copyright (c) 2017-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.
*/
package com.facebook.litho.utils;
import android.graphics.Rect;
import android.support.v4.util.Pools;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.litho.LithoView;
import com.facebook.litho.config.ComponentsConfiguration;
import static com.facebook.litho.ThreadUtils.assertMainThread;
/**
* Provides methods for enabling incremental mount.
*/
public class IncrementalMountUtils {
/**
* A view that wraps a child view and that provides a wrapped view to be incrementally mounted.
*/
public interface WrapperView {
/**
* @return A child view that will be incrementally mounted.
*/
View getWrappedView();
}
private static final Pools.SynchronizedPool<Rect> sRectPool =
new Pools.SynchronizedPool<>(10);
/**
* Performs incremental mount on the children views of the given ViewGroup.
* @param scrollingViewParent ViewGroup container of views that will be incrementally mounted.
*/
public static void performIncrementalMount(ViewGroup scrollingViewParent) {
performIncrementalMount(scrollingViewParent, /* force */ false);
}
/**
* Performs incremental mount on the children views of the given ViewGroup.
* @param scrollingViewParent ViewGroup container of views that will be incrementally mounted.
* @param force whether the incremental mount should always take place, or only if
* {@link ComponentsConfiguration.isIncrementalMountOnOffsetOrTranslationChangeEnabled} returns
* false.
*/
public static void performIncrementalMount(ViewGroup scrollingViewParent, boolean force) {
assertMainThread();
if (!force && ComponentsConfiguration.isIncrementalMountOnOffsetOrTranslationChangeEnabled) {
return;
}
final int viewGroupWidth = scrollingViewParent.getWidth();
final int viewGroupHeight = scrollingViewParent.getHeight();
for (int i = 0; i < scrollingViewParent.getChildCount(); i++) {
maybePerformIncrementalMountOnView(
viewGroupWidth,
viewGroupHeight,
scrollingViewParent.getChildAt(i));
}
}
/**
* Returns a scroll listener that performs incremental mount. This is needed
* for feeds that do not use a ScrollingViewProxy, but rather directly use a
* RecyclerView.
*/
public static RecyclerView.OnScrollListener createRecyclerViewListener() {
return new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
performIncrementalMount(recyclerView);
}
};
}
private static void maybePerformIncrementalMountOnView(
int scrollingParentWidth,
int scrollingParentHeight,
View view) {
final View underlyingView = view instanceof WrapperView
? ((WrapperView) view).getWrappedView()
: view;
if (!(underlyingView instanceof LithoView)) {
return;
}
final LithoView lithoView = (LithoView) underlyingView;
if (!lithoView.isIncrementalMountEnabled()) {
return;
}
if (view != underlyingView && view.getHeight() != underlyingView.getHeight()) {
throw new IllegalStateException(
"ViewDiagnosticsWrapper must be the same height as the underlying view");
}
final int translationX = (int) view.getTranslationX();
final int translationY = (int) view.getTranslationY();
final int top = view.getTop() + translationY;
final int bottom = view.getBottom() + translationY;
final int left = view.getLeft() + translationX;
final int right = view.getRight() + translationX;
if (left >= 0 &&
top >= 0 &&
right <= scrollingParentWidth &&
bottom <= scrollingParentHeight &&
lithoView.getPreviousMountBounds().width() == lithoView.getWidth() &&
lithoView.getPreviousMountBounds().height() == lithoView.getHeight()) {
// View is fully visible, and has already been completely mounted.
return;
}
final Rect rect = acquireRect();
rect.set(
Math.max(0, -left),
Math.max(0, -top),
Math.min(right, scrollingParentWidth) - left,
Math.min(bottom, scrollingParentHeight) - top);
if (rect.isEmpty()) {
// View is not visible at all, nothing to do.
release(rect);
return;
}
lithoView.performIncrementalMount(rect);
release(rect);
}
private static Rect acquireRect() {
Rect rect = sRectPool.acquire();
if (rect == null) {
rect = new Rect();
}
return rect;
}
private static void release(Rect rect) {
rect.setEmpty();
sRectPool.release(rect);
}
}