/** * 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; import java.util.HashMap; import java.util.Map; import android.support.v4.util.Pools; import android.view.View; import android.view.ViewGroup; import com.facebook.litho.viewcompatcreator.ViewCompatCreator; /** * A component that can wrap a view using a {@link ViewBinder} class to bind the view * and a {@link ViewCompatCreator} to create the mount contents. * This component will have a different recycle pool per {@link ViewCompatCreator}. */ public class ViewCompatComponent<V extends View> extends ComponentLifecycle { /** * Binds data to a view. * @param <V> the type of View. */ public interface ViewBinder<V extends View> { /** * Prepares the binder to be bound to a view. * * Use this method to perform calculations ahead of time and save them. */ void prepare(); /** * Binds data to the given view so it can be rendered on screen. This will always be called * after prepare so that you can use stored output from prepare here if needed. * * @param view the view to bind. */ void bind(V view); /** * Cleans up a view that goes off screen after it has already been bound. * * @param view the view to unbind. */ void unbind(V view); } private static final Pools.SynchronizedPool<Builder> sBuilderPool = new Pools.SynchronizedPool<>(2); private static final Map<ViewCompatCreator, ViewCompatComponent> sInstances = new HashMap<>(); private final ViewCompatCreator mViewCompatCreator; private final String mComponentName; public static <V extends View> ViewCompatComponent<V> get( ViewCompatCreator<V> viewCreator, String componentName) { ViewCompatComponent<V> componentLifecycle; synchronized (sInstances) { componentLifecycle = sInstances.get(viewCreator); if (componentLifecycle == null) { componentLifecycle = new ViewCompatComponent<>(viewCreator, componentName); sInstances.put(viewCreator, componentLifecycle); } } return componentLifecycle; } public Builder<V> create(ComponentContext componentContext) { ViewCompatComponentImpl impl = new ViewCompatComponentImpl(this); Builder<V> builder = sBuilderPool.acquire(); if (builder == null) { builder = new Builder<>(); } builder.init(componentContext, impl); return builder; } private ViewCompatComponent(ViewCompatCreator viewCreator, String componentName) { super(); mViewCompatCreator = viewCreator; mComponentName = "ViewCompatComponent_" + componentName; } @Override protected boolean canMeasure() { return true; } @Override protected void onMeasure( ComponentContext c, ComponentLayout layout, int widthSpec, int heightSpec, Size size, Component<?> component) { final ViewCompatComponentImpl impl = (ViewCompatComponentImpl) component; final ViewBinder viewBinder = impl.mViewBinder; View toMeasure = (View) ComponentsPools.acquireMountContent(c, getId()); if (toMeasure == null) { toMeasure = mViewCompatCreator.createView(c); } ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(size.width, size.height); toMeasure.setLayoutParams(layoutParams); viewBinder.bind(toMeasure); if (toMeasure.getVisibility() == View.GONE) { // No need to measure the view if binding it caused its visibility to become GONE. size.width = 0; size.height = 0; } else { toMeasure.measure(widthSpec, heightSpec); size.width = toMeasure.getMeasuredWidth(); size.height = toMeasure.getMeasuredHeight(); } viewBinder.unbind(toMeasure); ComponentsPools.release(c, this, toMeasure); } @Override protected void onPrepare(ComponentContext c, Component<?> component) { ViewCompatComponentImpl impl = ((ViewCompatComponentImpl) component); impl.mViewBinder.prepare(); } @Override void bind(ComponentContext c, Object mountedContent, Component<?> component) { ViewCompatComponentImpl impl = ((ViewCompatComponentImpl) component); impl.mViewBinder.bind((View) mountedContent); } @Override void unbind( ComponentContext c, Object mountedContent, Component<?> component) { ViewCompatComponentImpl impl = ((ViewCompatComponentImpl) component); impl.mViewBinder.unbind((View) mountedContent); } @Override public MountType getMountType() { return MountType.VIEW; } private static final class ViewCompatComponentImpl<V extends View> extends Component<ViewCompatComponent<V>> implements Cloneable { private ViewBinder<V> mViewBinder; protected ViewCompatComponentImpl(ViewCompatComponent lifecycle) { super(lifecycle); } @Override public String getSimpleName() { return getLifecycle().getSimpleName(); } } private String getSimpleName() { return mComponentName; } @Override V createMountContent(ComponentContext c) { return (V) mViewCompatCreator.createView(c); } public static final class Builder<V extends View> extends Component.Builder<ViewCompatComponent<V>> { private ViewCompatComponentImpl mImpl; private void init(ComponentContext context, ViewCompatComponentImpl impl) { super.init(context, 0, 0, impl); mImpl = impl; } public Builder<V> viewBinder(ViewBinder<V> viewBinder) { mImpl.mViewBinder = viewBinder; return this; } @Override public Builder<V> key(String key) { super.setKey(key); return this; } @Override public Component<ViewCompatComponent<V>> build() { if (mImpl.mViewBinder == null) { throw new IllegalStateException( "To create a ViewCompatComponent you must provide a ViewBinder."); } ViewCompatComponentImpl impl = mImpl; release(); return impl; } @Override protected void release() { super.release(); mImpl = null; sBuilderPool.release(this); } } }