com.tmall.wireless.tangram3.dataparser.concrete.Card.java Source code

Java tutorial

Introduction

Here is the source code for com.tmall.wireless.tangram3.dataparser.concrete.Card.java

Source

/*
 * MIT License
 *
 * Copyright (c) 2018 Alibaba Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.tmall.wireless.tangram3.dataparser.concrete;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
import android.widget.ImageView;

import com.alibaba.android.vlayout.LayoutHelper;
import com.alibaba.android.vlayout.Range;
import com.alibaba.android.vlayout.layout.BaseLayoutHelper;
import com.alibaba.android.vlayout.layout.FixAreaLayoutHelper;
import com.alibaba.android.vlayout.layout.MarginLayoutHelper;
import com.alibaba.fastjson.JSONObject;
import com.tmall.wireless.tangram3.Engine;
import com.tmall.wireless.tangram3.MVHelper;
import com.tmall.wireless.tangram3.TangramBuilder;
import com.tmall.wireless.tangram3.core.service.ServiceManager;
import com.tmall.wireless.tangram3.dataparser.DataParser;
import com.tmall.wireless.tangram3.structure.BaseCell;
import com.tmall.wireless.tangram3.support.CardSupport;
import com.tmall.wireless.tangram3.support.ExposureSupport;
import com.tmall.wireless.tangram3.util.ImageUtils;
import com.tmall.wireless.tangram3.util.Preconditions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Basic Card, which will represent LayoutHelpers
 * <p/>
 */
public abstract class Card extends ComponentLifecycle {

    public static final Card NaN = new NaNCard();

    private static final String TAG = "Card";

    /**
     * card type
     */
    public String stringType;

    @Nullable
    public String id;

    @Nullable
    protected BaseCell mHeader;

    @Nullable
    protected BaseCell mFooter;

    @NonNull
    protected ArrayMap<Range<Integer>, Card> mChildren = new ArrayMap<>();

    @NonNull
    protected List<BaseCell> mCells = new ArrayList<>();

    @NonNull
    protected final List<BaseCell> mPendingCells = new ArrayList<>();

    @NonNull
    protected final List<BaseCell> mInQueueCells = new ArrayList<>();

    @Nullable
    public Style style;

    /**
     * if need load more
     */
    public boolean loadMore = false;

    /**
     * api params of loading more data
     */
    public JSONObject loadParams;

    /**
     * indicate if loading data now
     */
    public boolean loading = false;

    /**
     * page number used at loading more data
     */
    public int page;

    /**
     * api name of loading more data
     */
    public String load;

    /**
     * if data has been loaded
     */
    public boolean loaded = false;

    /**
     * if this card has more data
     */
    public boolean hasMore = false;

    /**
     * Not used
     */
    @Deprecated
    public int rowId;

    /**
     * serviceManager
     */
    @Nullable
    public ServiceManager serviceManager;

    @NonNull
    public DataParser dataParser;

    @Nullable
    private Map<String, Object> mParams;

    public JSONObject extras = new JSONObject();

    public void setParams(@Nullable Map<String, Object> params) {
        mParams = params;
    }

    @Nullable
    public Map<String, Object> getParams() {
        return mParams == null ? Collections.<String, Object>emptyMap() : mParams;
    }

    public Card() {

    }

    public void setStringType(String type) {
        this.stringType = type;
    }

    /**
     * LayoutHelper for this card, it won't get update for now!!!, as Card can be replaced, or data can be changed
     * If you want to reset the LayoutHelper? Think why you want to do it, make the LayoutHelper unmodified is easy
     * Please replace the card instead of LayoutHelper.
     */
    private LayoutHelper mLayoutHelper = null;

    public LayoutHelper getExistLayoutHelper() {
        return mLayoutHelper;
    }

    /**
     * whether retain LayoutHelper once it created,
     */
    protected boolean mRetainLayout = true;

    @Nullable
    public final LayoutHelper getLayoutHelper() {

        LayoutHelper helper = convertLayoutHelper(mLayoutHelper);

        // bind style to helper
        if (style != null && helper != null) {
            helper.setZIndex(style.zIndex);

            if (helper instanceof BaseLayoutHelper) {
                BaseLayoutHelper baseHelper = (BaseLayoutHelper) helper;
                baseHelper.setBgColor(style.bgColor);
                if (!TextUtils.isEmpty(style.bgImgUrl)) {
                    if (serviceManager != null && serviceManager.getService(CardSupport.class) != null) {
                        final CardSupport support = serviceManager.getService(CardSupport.class);
                        baseHelper.setLayoutViewBindListener(new BindListener(style) {
                            @Override
                            public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
                                support.onBindBackgroundView(layoutView, Card.this);
                            }
                        });
                        baseHelper.setLayoutViewUnBindListener(new UnbindListener(style) {
                            @Override
                            public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
                                support.onUnbindBackgroundView(layoutView, Card.this);
                            }
                        });
                    } else {
                        baseHelper.setLayoutViewBindListener(new BindListener(style));
                        baseHelper.setLayoutViewUnBindListener(new UnbindListener(style));
                    }
                } else {
                    baseHelper.setLayoutViewBindListener(null);
                    baseHelper.setLayoutViewUnBindListener(null);
                }

                if (!Float.isNaN(style.aspectRatio)) {
                    // ((BaseLayoutHelper) helper).setAspectRatio(style.aspectRatio);
                }

            }

            if (helper instanceof FixAreaLayoutHelper) {
                FixAreaLayoutHelper fixHelper = (FixAreaLayoutHelper) helper;
                boolean hasCustomAnimatorHelper = false;
                if (serviceManager != null && serviceManager.getService(CardSupport.class) != null) {
                    CardSupport support = serviceManager.getService(CardSupport.class);
                    FixAreaLayoutHelper.FixViewAnimatorHelper viewAnimatorHelper = support
                            .onGetFixViewAppearAnimator(Card.this);
                    if (viewAnimatorHelper != null) {
                        hasCustomAnimatorHelper = true;
                        fixHelper.setFixViewAnimatorHelper(viewAnimatorHelper);
                    }
                }
                if (!hasCustomAnimatorHelper) {
                    final int duration = style.extras != null
                            ? style.extras.getIntValue(Style.KEY_ANIMATION_DURATION)
                            : 0;
                    if (duration > 0) {
                        fixHelper.setFixViewAnimatorHelper(new FixAreaLayoutHelper.FixViewAnimatorHelper() {
                            @Override
                            public ViewPropertyAnimator onGetFixViewAppearAnimator(View fixView) {
                                int height = fixView.getMeasuredHeight();
                                fixView.setTranslationY(-height);
                                return fixView.animate().translationYBy(height).setDuration(duration);
                            }

                            @Override
                            public ViewPropertyAnimator onGetFixViewDisappearAnimator(View fixView) {
                                int height = fixView.getMeasuredHeight();
                                return fixView.animate().translationYBy(-height).setDuration(duration);
                            }
                        });
                    }
                }
            }

            if (helper instanceof MarginLayoutHelper) {
                ((MarginLayoutHelper) helper).setMargin(style.margin[Style.MARGIN_LEFT_INDEX],
                        style.margin[Style.MARGIN_TOP_INDEX], style.margin[Style.MARGIN_RIGHT_INDEX],
                        style.margin[Style.MARGIN_BOTTOM_INDEX]);
                ((MarginLayoutHelper) helper).setPadding(style.padding[Style.MARGIN_LEFT_INDEX],
                        style.padding[Style.MARGIN_TOP_INDEX], style.padding[Style.MARGIN_RIGHT_INDEX],
                        style.padding[Style.MARGIN_BOTTOM_INDEX]);
            }
        }

        if (mRetainLayout) {
            mLayoutHelper = helper;
        }

        return helper;
    }

    public static class BindListener implements BaseLayoutHelper.LayoutViewBindListener {
        private Style mStyle;

        public BindListener(Style style) {
            this.mStyle = style;
        }

        @Override
        public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
            if (mStyle != null && !TextUtils.isEmpty(mStyle.bgImgUrl)) {
                if (layoutView instanceof ImageView) {
                    ImageUtils.doLoadImageUrl((ImageView) layoutView, mStyle.bgImgUrl);
                }
            }
        }
    }

    public static class UnbindListener implements BaseLayoutHelper.LayoutViewUnBindListener {
        private Style mStyle;

        public UnbindListener(Style style) {
            this.mStyle = style;
        }

        @Override
        public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
        }
    }

    @Nullable
    public LayoutHelper convertLayoutHelper(@Nullable LayoutHelper oldHelper) {
        return null;
    }

    private boolean mIsExposed = false;

    /**
     * Don't change card's mCells in binding process!
     * <p/>
     */
    public void onBindCell(int offset, int position, boolean showFromEnd) {
        if (!mIsExposed && serviceManager != null) {
            ExposureSupport exposureSupport = serviceManager.getService(ExposureSupport.class);
            if (exposureSupport != null) {
                mIsExposed = true;
                exposureSupport.onExposure(this, offset, position);
            }
        }
    }

    public BaseCell getCellById(String id) {
        for (int i = 0, size = mCells.size(); i < size; i++) {
            BaseCell target = mCells.get(i);
            if (target.id != null && target.id.equals(id)) {
                return target;
            }
        }
        return null;
    }

    public List<BaseCell> getCells() {
        return Collections.unmodifiableList(mCells);
    }

    @NonNull
    public ArrayMap<Range<Integer>, Card> getChildren() {
        return mChildren;
    }

    private final SparseBooleanArray pendingDeleteMap = new SparseBooleanArray();
    private final SparseArray<BaseCell> oldMap = new SparseArray<>();
    private final SparseArray<BaseCell> newMap = new SparseArray<>();

    public void setCells(@Nullable List<BaseCell> cells) {
        if (mPlaceholderCell != null)
            this.mCells.remove(mPlaceholderCell);

        oldMap.clear();
        pendingDeleteMap.clear();
        for (BaseCell cell : this.mCells) {
            oldMap.put(System.identityHashCode(cell), cell);
        }

        this.mCells.clear();
        if (cells != null) {
            for (BaseCell c : cells) {
                //noinspection unchecked
                this.addCellInternal(c, true);
            }
        }

        adjustPendingCells(true);

        newMap.clear();

        for (BaseCell cell : this.mCells) {
            newMap.put(System.identityHashCode(cell), cell);
        }

        for (int i = 0, size = oldMap.size(); i < size; i++) {
            int key = oldMap.keyAt(i);
            if (newMap.get(key) != null) {
                newMap.remove(key);
                pendingDeleteMap.put(key, true);
            }
        }

        for (int i = 0, size = pendingDeleteMap.size(); i < size; i++) {
            oldMap.remove(pendingDeleteMap.keyAt(i));
        }

        diffCells(newMap, oldMap);

        newMap.clear();
        oldMap.clear();
        pendingDeleteMap.clear();

        if (requirePlaceholderCell()) {
            this.mCells.add(mPlaceholderCell);
        }
    }

    public void addCell(@Nullable BaseCell cell) {
        addCellInternal(cell, false);

        adjustPendingCells(false);

        if (mPlaceholderCell != null && this.mCells.contains(mPlaceholderCell))
            this.mCells.remove(mPlaceholderCell);

        if (requirePlaceholderCell()) {
            this.mCells.add(mPlaceholderCell);
        }
    }

    public void addCells(@Nullable List<BaseCell> cells) {
        if (cells != null) {
            for (BaseCell cell : cells) {
                addCellInternal(cell, false);
            }
        }

        adjustPendingCells(false);

        if (mPlaceholderCell != null && this.mCells.contains(mPlaceholderCell))
            this.mCells.remove(mPlaceholderCell);

        if (requirePlaceholderCell()) {
            this.mCells.add(mPlaceholderCell);
        }
    }

    public void addCells(Card parent, int index, @Nullable List<BaseCell> cells) {
        if (cells != null) {
            int i = 0;
            for (BaseCell cell : cells) {
                addCellInternal(parent, index + i, cell, false);
                i++;
            }
        }

        adjustPendingCells(false);

        if (mPlaceholderCell != null && this.mCells.contains(mPlaceholderCell))
            this.mCells.remove(mPlaceholderCell);

        if (requirePlaceholderCell()) {
            this.mCells.add(mPlaceholderCell);
        }
    }

    public void removeAllCells() {
        for (int i = 0, size = mCells.size(); i < size; i++) {
            mCells.get(i).onRemoved();
        }
        mCells.clear();
    }

    public boolean removeCell(@Nullable BaseCell cell) {
        if (cell == null) {
            return false;
        }
        boolean removed = mCells.remove(cell);
        if (removed) {
            cell.onRemoved();
        }

        notifyDataChange();

        return removed;
    }

    public boolean removeCellSilently(@Nullable BaseCell cell) {
        if (cell == null) {
            return false;
        }
        boolean removed = mCells.remove(cell);
        if (removed) {
            cell.onRemoved();
        }
        return removed;
    }

    public boolean replaceCell(@Nullable BaseCell oldCell, @Nullable BaseCell newCell) {
        if (oldCell == null || newCell == null) {
            return false;
        }
        int index = mCells.indexOf(oldCell);
        if (index >= 0) {
            mCells.set(index, newCell);
            newCell.onAdded();
            oldCell.onRemoved();
            return true;
        } else {
            return false;
        }
    }

    public boolean addCellInternal(@Nullable BaseCell cell, boolean silent) {
        if (cell != null) {
            cell.parentId = id;
            cell.parent = this;
            cell.serviceManager = serviceManager;
            MVHelper mvHelper = getMVHelper();
            if (mvHelper != null) {
                if (mvHelper.isValid(cell, serviceManager)) {
                    if (cell.position >= 0 && !TextUtils.isEmpty(load)) {
                        cell.pos = cell.position;
                        mPendingCells.add(cell);
                        return true;
                    } else {
                        cell.pos = mHeader != null ? this.mCells.size() + 1 : this.mCells.size();
                    }

                    if (!silent && mIsActivated) {
                        // do cell added
                        cell.added();
                    }

                    this.mCells.add(cell);
                    if (mFooter != null) {
                        mFooter.pos = cell.pos + 1;
                    }
                    return true;
                }
            }
        }

        return false;
    }

    public boolean addCellInternal(Card parent, int index, @Nullable BaseCell cell, boolean silent) {
        if (cell != null) {
            cell.parentId = parent.id;
            cell.parent = parent;
            cell.serviceManager = serviceManager;
            MVHelper mvHelper = getMVHelper();
            if (mvHelper != null) {
                if (mvHelper.isValid(cell, serviceManager)) {
                    if (cell.position >= 0 && !TextUtils.isEmpty(load)) {
                        cell.pos = cell.position;
                        mPendingCells.add(cell);
                        return true;
                    } else {
                        //FixMe pos not correct when insert cell
                        cell.pos = mHeader != null ? this.mCells.size() + 1 : this.mCells.size();
                    }

                    if (!silent && mIsActivated) {
                        // do cell added
                        cell.added();
                    }

                    this.mCells.add(index, cell);
                    if (mFooter != null) {
                        mFooter.pos = cell.pos + 1;
                    }
                    if (mHeader != null) {
                        mHeader.pos = 0;
                    }
                    return true;
                }
            }
        }

        return false;
    }

    private void adjustPendingCells(boolean silent) {
        if (mPendingCells.size() > 0) {
            Collections.sort(mPendingCells, CellPositionComparator.COMPARATOR);

            for (Iterator<BaseCell> iter = mPendingCells.iterator(); iter.hasNext();) {
                BaseCell next = iter.next();
                if (next.position < 0)
                    continue;
                if (next.position < mCells.size()) {
                    mCells.add(next.position, next);
                    mInQueueCells.add(next);
                    iter.remove();

                    if (!silent)
                        next.added();
                } else {
                    break;
                }
            }
        }

        if (mInQueueCells.size() > 0) {
            // when do clean, the cell is already removed
            Collections.sort(mInQueueCells, CellPositionComparator.REVERSE_COMPARATOR);

            for (Iterator<BaseCell> iter = mInQueueCells.iterator(); iter.hasNext();) {
                BaseCell next = iter.next();
                if (next.position < 0)
                    continue;
                if (next.position > mCells.size()) {
                    mPendingCells.add(next);
                    iter.remove();
                } else {
                    break;
                }
            }

        }

        if (TangramBuilder.isPrintLog()) {
            if (mPendingCells.size() > 0 && mInQueueCells.size() > 0) {
                Preconditions.checkState(
                        mPendingCells.get(0).position >= mInQueueCells.get(mInQueueCells.size() - 1).position,
                        "Items in pendingQueue must have large position than Items in queue");
            }
        }
    }

    public void addChildCard(Card card) {

    }

    public void offsetChildCard(Card anchorCard, int offset) {

    }

    public void clearChildMap() {

    }

    public boolean isValid() {
        return !TextUtils.isEmpty(stringType) && serviceManager != null;
    }

    public final void notifyDataChange() {
        if (serviceManager instanceof Engine) {
            ((Engine) serviceManager).refresh();
        }
    }

    private void diffCells(@NonNull SparseArray<BaseCell> added, @NonNull SparseArray<BaseCell> removed) {
        if (!mIsActivated)
            return;

        for (int i = 0, size = added.size(); i < size; i++) {
            int key = added.keyAt(i);
            BaseCell cell = added.get(key);
            if (cell != null) {
                cell.added();
            }
        }

        for (int i = 0, size = removed.size(); i < size; i++) {
            int key = removed.keyAt(i);
            BaseCell cell = removed.get(key);
            if (cell != null) {
                cell.removed();
            }
        }
    }

    /*==========================================
     * Manage card lifecycle
     *==========================================*/
    protected void onAdded() {
        for (BaseCell cell : mCells) {
            cell.added();
        }
    }

    protected void onRemoved() {
        for (BaseCell cell : mCells) {
            cell.removed();
        }
    }

    /*==========================================
     * Place Holder
     *==========================================*/
    private BaseCell mPlaceholderCell;
    private float mTmpAspectRatio = Float.NaN;

    public void storeAspectRatio() {
        if (style != null && !Float.isNaN(style.aspectRatio)) {
            mTmpAspectRatio = style.aspectRatio;
            style.aspectRatio = Float.NaN;
        }
    }

    public void restoreAspectRatio() {
        if (style != null && !Float.isNaN(mTmpAspectRatio)) {
            style.aspectRatio = mTmpAspectRatio;
        }
    }

    public void enablePlaceholderView(View placeholderView, int placeholderHeight) {
        if (!TextUtils.isEmpty(load) && placeholderView != null) {
            storeAspectRatio();
            this.mPlaceholderCell = new PlaceholderCell(placeholderHeight, placeholderView);
            if (this.mCells.size() == 0) {
                mCells.add(mPlaceholderCell);
            }
        } else {
            this.mCells.remove(mPlaceholderCell);
            this.mPlaceholderCell = null;
        }
    }

    private boolean mPlaceholderRequired = true;

    public void showPlaceholderView(boolean shown) {
        this.mPlaceholderRequired = shown;
        if (!shown) {
            restoreAspectRatio();
        } else {
            storeAspectRatio();
        }
        if (!this.mCells.contains(mPlaceholderCell)) {
            if (requirePlaceholderCell()) {
                this.mCells.add(mPlaceholderCell);
                notifyDataChange();
            }
        } else {
            if (!requirePlaceholderCell() && this.mCells.remove(mPlaceholderCell))
                notifyDataChange();
        }

    }

    public BaseCell getPlaceholderCell() {
        return mPlaceholderCell;
    }

    public boolean requirePlaceholderCell() {
        return mPlaceholderRequired && mPlaceholderCell != null && !TextUtils.isEmpty(load)
                && (mCells.size() == 0 || (mCells.size() == 1 && mCells.contains(mPlaceholderCell)));
    }

    private MVHelper getMVHelper() {
        if (serviceManager != null) {
            return serviceManager.getService(MVHelper.class);
        }
        return null;
    }

    public Card findChildCardById(String id) {
        if (!mChildren.isEmpty()) {
            for (int i = 0, size = mChildren.size(); i < size; i++) {
                Card card = mChildren.valueAt(i);
                if (card != null && card.id.equals(id)) {
                    return card;
                }
            }
        }
        return null;
    }

    public ArrayMap<Range<Integer>, Card> getChildrenCards() {
        return mChildren;
    }

    public static final class PlaceholderCell extends BaseCell {

        private int mHeight = 0;

        private View mPlaceholderView;

        private int mBgColor;

        public PlaceholderCell(int height, int bgColor) {
            this(height, null, bgColor);
        }

        public PlaceholderCell(int height, View placeholderView) {
            this(height, placeholderView, 0x0);
        }

        public PlaceholderCell(int height, View loadingView, int bgColor) {
            this.mHeight = height;
            this.mPlaceholderView = loadingView;
            this.mBgColor = bgColor;
            this.style = new Style();
            this.style.height = mHeight;
            this.style.bgColor = mBgColor;
            this.style.extras = new JSONObject();
            this.gridDisplayType = GridDisplayType.block;
            this.stringType = TangramBuilder.TYPE_EXTENDED_VIEW_COMPACT;
        }

        public void bindView(@NonNull View view) {
            if (mPlaceholderView != null && view instanceof FrameLayout) {
                if (mPlaceholderView.getParent() instanceof FrameLayout) {
                    ((FrameLayout) mPlaceholderView.getParent()).removeView(mPlaceholderView);
                }

                ((FrameLayout) view).addView(mPlaceholderView);
            }
        }
    }

    private static class CellPositionComparator implements Comparator<BaseCell> {

        public static final CellPositionComparator COMPARATOR = new CellPositionComparator(false);
        public static final CellPositionComparator REVERSE_COMPARATOR = new CellPositionComparator(true);

        private int mLarge;
        private int mSmall;

        CellPositionComparator(boolean reverse) {
            mLarge = reverse ? -1 : 1;
            mSmall = -mLarge;
        }

        @Override
        public int compare(BaseCell lhs, BaseCell rhs) {

            if (lhs == null && rhs == null)
                return 0;
            if (lhs == null)
                return mSmall;
            if (rhs == null)
                return mLarge;

            return lhs.position < rhs.position ? mSmall : (lhs.position == rhs.position ? 0 : mLarge);

        }
    }

    public static final class NaNCard extends Card {

        @Override
        public boolean isValid() {
            return false;
        }
    }

}