com.facebook.litho.widget.HorizontalScrollSpec.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.litho.widget.HorizontalScrollSpec.java

Source

/**
 * 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.content.res.TypedArray;
import android.support.v4.util.Pools.SynchronizedPool;
import android.widget.HorizontalScrollView;

import com.facebook.litho.R;
import com.facebook.litho.ComponentContext;
import com.facebook.litho.ComponentTree;
import com.facebook.litho.Component;
import com.facebook.litho.ComponentLayout;
import com.facebook.litho.LithoView;
import com.facebook.litho.Output;
import com.facebook.litho.SizeSpec;
import com.facebook.litho.annotations.OnCreateMountContent;
import com.facebook.litho.annotations.OnLoadStyle;
import com.facebook.litho.annotations.PropDefault;
import com.facebook.litho.annotations.FromBoundsDefined;
import com.facebook.litho.annotations.Prop;
import com.facebook.litho.annotations.FromMeasure;
import com.facebook.litho.annotations.FromPrepare;
import com.facebook.litho.annotations.MountSpec;
import com.facebook.litho.annotations.OnBoundsDefined;
import com.facebook.litho.annotations.OnMeasure;
import com.facebook.litho.annotations.OnMount;
import com.facebook.litho.annotations.OnPrepare;
import com.facebook.litho.annotations.OnUnmount;
import com.facebook.litho.annotations.ResType;
import com.facebook.litho.Size;

import static com.facebook.litho.SizeSpec.EXACTLY;
import static com.facebook.litho.SizeSpec.UNSPECIFIED;

/**
 * A component that wraps another component and allow it to be horizontally scrollable. It's
 * analogous to a {@link android.widget.HorizontalScrollView}.
 */
@MountSpec(canMountIncrementally = true)
class HorizontalScrollSpec {

    @PropDefault
    static final boolean scrollbarEnabled = true;

    private static final SynchronizedPool<Size> sSizePool = new SynchronizedPool<>(2);

    @OnLoadStyle
    static void onLoadStyle(ComponentContext c, Output<Boolean> scrollbarEnabled) {

        final TypedArray a = c.obtainStyledAttributes(R.styleable.HorizontalScroll, 0);

        for (int i = 0, size = a.getIndexCount(); i < size; i++) {
            final int attr = a.getIndex(i);

            if (attr == R.styleable.HorizontalScroll_android_scrollbars) {
                scrollbarEnabled.set(a.getInt(attr, 0) != 0);
            }
        }

        a.recycle();
    }

    @OnPrepare
    static void onPrepare(ComponentContext context, @Prop Component<?> contentProps,
            Output<ComponentTree> contentComponent) {
        contentComponent.set(ComponentTree.create(context, contentProps).build());
    }

    @OnMeasure
    static void onMeasure(ComponentContext context, ComponentLayout layout, int widthSpec, int heightSpec,
            Size size, @FromPrepare ComponentTree contentComponent, Output<Integer> measuredComponentWidth,
            Output<Integer> measuredComponentHeight) {

        final int measuredWidth;
        final int measuredHeight;

        Size contentSize = acquireSize();

        // Measure the component with undefined width spec, as the contents of the
        // hscroll have unlimited horizontal space.
        contentComponent.setSizeSpec(SizeSpec.makeSizeSpec(0, UNSPECIFIED), heightSpec, contentSize);

        measuredWidth = contentSize.width;
        measuredHeight = contentSize.height;

        releaseSize(contentSize);
        contentSize = null;

        measuredComponentWidth.set(measuredWidth);
        measuredComponentHeight.set(measuredHeight);

        // If size constraints were not explicitly defined, just fallback to the
        // component dimensions instead.
        size.width = SizeSpec.getMode(widthSpec) == UNSPECIFIED ? measuredWidth : SizeSpec.getSize(widthSpec);
        size.height = measuredHeight;
    }

    @OnBoundsDefined
    static void onBoundsDefined(ComponentContext context, ComponentLayout layout,
            @FromPrepare ComponentTree contentComponent, @FromMeasure Integer measuredComponentWidth,
            @FromMeasure Integer measuredComponentHeight, Output<Integer> componentWidth,
            Output<Integer> componentHeight) {

        // If onMeasure() has been called, this means the content component already
        // has a defined size, no need to calculate it again.
        if (measuredComponentWidth != null && measuredComponentHeight != null) {
            componentWidth.set(measuredComponentWidth);
            componentHeight.set(measuredComponentHeight);
        } else {
            final int measuredWidth;
            final int measuredHeight;

            Size contentSize = acquireSize();
            contentComponent.setSizeSpec(SizeSpec.makeSizeSpec(0, UNSPECIFIED),
                    SizeSpec.makeSizeSpec(layout.getHeight(), EXACTLY), contentSize);

            measuredWidth = contentSize.width;
            measuredHeight = contentSize.height;

            releaseSize(contentSize);
            contentSize = null;

            componentWidth.set(measuredWidth);
            componentHeight.set(measuredHeight);
        }
    }

    @OnCreateMountContent
    static HorizontalScrollLithoView onCreateMountContent(ComponentContext c) {
        return new HorizontalScrollLithoView(c);
    }

    @OnMount
    static void onMount(ComponentContext context, HorizontalScrollLithoView horizontalScrollLithoView,
            @Prop(optional = true, resType = ResType.BOOL) boolean scrollbarEnabled,
            @FromPrepare ComponentTree contentComponent, @FromBoundsDefined int componentWidth,
            @FromBoundsDefined int componentHeight) {

        horizontalScrollLithoView.setHorizontalScrollBarEnabled(scrollbarEnabled);
        horizontalScrollLithoView.mount(contentComponent, componentWidth, componentHeight);
    }

    @OnUnmount
    static void onUnmount(ComponentContext context, HorizontalScrollLithoView mountedView) {
        mountedView.unmount();
    }

    static class HorizontalScrollLithoView extends HorizontalScrollView {
        private final LithoView mLithoView;

        private int mComponentWidth;
        private int mComponentHeight;

        public HorizontalScrollLithoView(Context context) {
            super(context);
            mLithoView = new LithoView(context);
            addView(mLithoView);
        }

        @Override
        protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
            super.onScrollChanged(left, top, oldLeft, oldTop);

            // Visible area changed, perform incremental mount.
            incrementalMount();
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // The hosting component view always matches the component size. This will
            // ensure that there will never be a size-mismatch between the view and the
            // component-based content, which would trigger a layout pass in the
            // UI thread.
            mLithoView.measure(MeasureSpec.makeMeasureSpec(mComponentWidth, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(mComponentHeight, MeasureSpec.EXACTLY));

            // The mounted view always gets exact dimensions from the framework.
            setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
        }

        void mount(ComponentTree component, int width, int height) {
            mLithoView.setComponentTree(component);
            mComponentWidth = width;
            mComponentHeight = height;
        }

        void incrementalMount() {
            mLithoView.performIncrementalMount();
        }

        void unmount() {
            // Clear all component-related state from the view.
            mLithoView.setComponentTree(null);
            mComponentWidth = 0;
            mComponentHeight = 0;
        }
    }

    private static Size acquireSize() {
        Size size = sSizePool.acquire();
        if (size == null) {
            size = new Size();
        }

        return size;
    }

    private static void releaseSize(Size size) {
        sSizePool.release(size);
    }
}