/**
* 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.widget;
import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.litho.ComponentContext;
import com.facebook.litho.ComponentTree;
import com.facebook.litho.LithoView;
/**
* Wrapper that encapsulates all the features {@link RecyclerSpec} provides such as
* sticky header and pull-to-refresh
*/
public class RecyclerViewWrapper extends SwipeRefreshLayout {
private final LithoView mStickyHeader;
private final RecyclerView mRecyclerView;
/**
* Indicates whether {@link RecyclerView} has been detached. In such case we need to make sure
* to relayout its children eventually.
*/
private boolean mHasBeenDetachedFromWindow = false;
public RecyclerViewWrapper(Context context, RecyclerView recyclerView) {
super(context);
mRecyclerView = recyclerView;
// We need to draw first visible item on top of other children to support sticky headers
mRecyclerView.setChildDrawingOrderCallback(new RecyclerView.ChildDrawingOrderCallback() {
@Override
public int onGetChildDrawingOrder(int childCount, int i) {
return childCount - 1 - i;
}
});
addView(mRecyclerView);
mStickyHeader = new LithoView(new ComponentContext(getContext()), null, true);
mStickyHeader.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
addView(mStickyHeader);
}
public RecyclerView getRecyclerView() {
return mRecyclerView;
}
public void setStickyComponent(ComponentTree component) {
if (component.getLithoView() != null) {
component.getLithoView().startTemporaryDetach();
}
mStickyHeader.setComponentTree(component);
measureStickyHeader(getWidth());
}
public LithoView getStickyHeader() {
return mStickyHeader;
}
public void setStickyHeaderVerticalOffset(int verticalOffset) {
mStickyHeader.setTranslationY(verticalOffset);
}
public void showStickyHeader() {
mStickyHeader.setVisibility(View.VISIBLE);
}
public void hideStickyHeader() {
mStickyHeader.setVisibility(View.GONE);
}
public boolean isStickyHeaderHidden() {
return mStickyHeader.getVisibility() == View.GONE;
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureStickyHeader(MeasureSpec.getSize(widthMeasureSpec));
}
private void measureStickyHeader(int parentWidth) {
measureChild(
mStickyHeader,
MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY),
MeasureSpec.UNSPECIFIED);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mStickyHeader.getVisibility() == View.GONE) {
return;
}
final int stickyHeaderLeft = getPaddingLeft();
final int stickyHeaderTop = getPaddingTop();
mStickyHeader.layout(
stickyHeaderLeft,
stickyHeaderTop,
stickyHeaderLeft + mStickyHeader.getMeasuredWidth(),
stickyHeaderTop + mStickyHeader.getMeasuredHeight());
}
static RecyclerViewWrapper getParentWrapper(RecyclerView recyclerView) {
if (recyclerView.getParent() instanceof RecyclerViewWrapper) {
return (RecyclerViewWrapper) recyclerView.getParent();
}
return null;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mHasBeenDetachedFromWindow = true;
}
/**
* This is needed to solve a launch-blocker t14789523 and work around a framework bug t14809560.
*/
@Override
public boolean isLayoutRequested() {
if (getParent() != null) {
return getParent().isLayoutRequested() || super.isLayoutRequested();
}
return super.isLayoutRequested();
}
boolean hasBeenDetachedFromWindow() {
return mHasBeenDetachedFromWindow;
}
void setHasBeenDetachedFromWindow(boolean hasBeenDetachedFromWindow) {
mHasBeenDetachedFromWindow = hasBeenDetachedFromWindow;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
// SwipeRefreshLayout can ignore this request if nested scrolling is disabled on the child,
// but it fails to delegate the request up to the parents.
// This fixes a bug that can cause parents to improperly intercept scroll events from
// nested recyclers.
if (getParent() != null && !isNestedScrollingEnabled()) {
getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
@Override
public void setOnTouchListener(OnTouchListener listener) {
// When setting touch handler for RecyclerSpec we want RecyclerView to handle it.
mRecyclerView.setOnTouchListener(listener);
}
}