Java tutorial
/* * 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; } } }