| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package com.taobao.weex.ui.component.list.template; |
| |
| import android.annotation.TargetApi; |
| import android.content.Context; |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.os.AsyncTask; |
| import android.os.Build; |
| import android.os.Looper; |
| import android.os.MessageQueue; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.support.v4.util.ArrayMap; |
| import android.support.v7.widget.GridLayoutManager; |
| import android.support.v7.widget.LinearLayoutManager; |
| import android.support.v7.widget.RecyclerView; |
| import android.support.v7.widget.StaggeredGridLayoutManager; |
| import android.text.TextUtils; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewTreeObserver; |
| import android.widget.FrameLayout; |
| import android.widget.LinearLayout; |
| |
| import com.alibaba.fastjson.JSONArray; |
| import com.alibaba.fastjson.JSONObject; |
| import com.taobao.weex.WXEnvironment; |
| import com.taobao.weex.WXSDKInstance; |
| import com.taobao.weex.WXSDKManager; |
| import com.taobao.weex.annotation.Component; |
| import com.taobao.weex.annotation.JSMethod; |
| import com.taobao.weex.bridge.WXBridgeManager; |
| import com.taobao.weex.common.Constants; |
| import com.taobao.weex.common.ICheckBindingScroller; |
| import com.taobao.weex.common.OnWXScrollListener; |
| import com.taobao.weex.dom.WXAttr; |
| import com.taobao.weex.dom.WXCellDomObject; |
| import com.taobao.weex.dom.WXDomHandler; |
| import com.taobao.weex.dom.WXDomObject; |
| import com.taobao.weex.dom.WXEvent; |
| import com.taobao.weex.dom.WXRecyclerDomObject; |
| import com.taobao.weex.dom.flex.CSSLayoutContext; |
| import com.taobao.weex.dom.flex.Spacing; |
| import com.taobao.weex.el.parse.ArrayStack; |
| import com.taobao.weex.ui.component.AppearanceHelper; |
| import com.taobao.weex.ui.component.Scrollable; |
| import com.taobao.weex.ui.component.WXBaseRefresh; |
| import com.taobao.weex.ui.component.WXComponent; |
| import com.taobao.weex.ui.component.WXComponentProp; |
| import com.taobao.weex.ui.component.WXLoading; |
| import com.taobao.weex.ui.component.WXRefresh; |
| import com.taobao.weex.ui.component.WXVContainer; |
| import com.taobao.weex.ui.component.binding.Layouts; |
| import com.taobao.weex.ui.component.binding.Statements; |
| import com.taobao.weex.ui.component.list.RecyclerTransform; |
| import com.taobao.weex.ui.component.list.WXCell; |
| import com.taobao.weex.ui.view.listview.WXRecyclerView; |
| import com.taobao.weex.ui.view.listview.adapter.IOnLoadMoreListener; |
| import com.taobao.weex.ui.view.listview.adapter.IRecyclerAdapterListener; |
| import com.taobao.weex.ui.view.listview.adapter.RecyclerViewBaseAdapter; |
| import com.taobao.weex.ui.view.listview.adapter.WXRecyclerViewOnScrollListener; |
| import com.taobao.weex.ui.view.refresh.wrapper.BounceRecyclerView; |
| import com.taobao.weex.utils.WXLogUtils; |
| import com.taobao.weex.utils.WXUtils; |
| import com.taobao.weex.utils.WXViewUtils; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| |
| import static com.taobao.weex.common.Constants.Name.LOADMOREOFFSET; |
| |
| /** |
| * weex template list supported, high performance recycler-list |
| * https://github.com/Hanks10100/weex-native-directive |
| * Created by jianbai.gbj on 2017/8/17. |
| */ |
| @Component(lazyload = false) |
| public class WXRecyclerTemplateList extends WXVContainer<BounceRecyclerView> implements |
| IRecyclerAdapterListener<TemplateViewHolder>, IOnLoadMoreListener, Scrollable { |
| public static final String TAG = "WXRecyclerTemplateList"; |
| |
| private static final String NAME_HAS_FIXED_SIZE = "hasFixedSize"; |
| private static final String NAME_ITEM_VIEW_CACHE_SIZE = "itemViewCacheSize"; |
| private static final String NAME_TEMPLATE_CACHE_SIZE = "templateCacheSize"; |
| |
| |
| private WXRecyclerDomObject mDomObject; |
| protected int mLayoutType = WXRecyclerView.TYPE_LINEAR_LAYOUT; |
| protected int mColumnCount = 1; |
| protected float mColumnGap = 0; |
| protected float mColumnWidth = 0; |
| private float mPaddingLeft; |
| private float mPaddingRight; |
| |
| private WXRecyclerViewOnScrollListener mViewOnScrollListener = new WXRecyclerViewOnScrollListener(this); |
| private int mListCellCount = 0; |
| private boolean mForceLoadmoreNextTime = false; |
| private RecyclerView.ItemAnimator mItemAnimator; |
| |
| /** |
| * default orientation |
| * */ |
| private int orientation = Constants.Orientation.VERTICAL; |
| |
| /** |
| * offset reported |
| * */ |
| private boolean isScrollable = true; |
| private int mOffsetAccuracy = 10; |
| private Point mLastReport = new Point(-1, -1); |
| |
| private JSONArray listData; |
| private String listDataKey = Constants.Name.Recycler.LIST_DATA; |
| private String listDataItemKey = null; |
| private String listDataIndexKey = null; |
| private ArrayMap<String, Integer> mTemplateViewTypes; |
| |
| |
| |
| private Map<String, WXCell> mTemplateSources; |
| private String listDataTemplateKey = Constants.Name.Recycler.SLOT_TEMPLATE_TYPE; |
| private Runnable listUpdateRunnable; |
| private ConcurrentHashMap<String, TemplateCache> mTemplatesCache; |
| private int templateCacheSize = 2; |
| |
| |
| /** |
| * sticky helper |
| * */ |
| private TemplateStickyHelper mStickyHelper; |
| |
| |
| /** |
| * appear and disappear event managaer |
| * */ |
| private ArrayMap<Integer, List<AppearanceHelper>> mAppearHelpers = new ArrayMap<>(); |
| |
| /** |
| * disappear event will be fire, |
| * fist layer map key position, |
| * send layer map key String ref |
| * three layer map key view hash code, value is event arguments |
| * */ |
| private ArrayMap<Integer, Map<String,Map<Integer, List<Object>>>> mDisAppearWatchList = new ArrayMap<>(); |
| |
| private ArrayStack bindIngStackContext = new ArrayStack(); |
| private Map bindIngMapContext = new HashMap(); |
| |
| public WXRecyclerTemplateList(WXSDKInstance instance, WXDomObject node, WXVContainer parent) { |
| super(instance, node, parent); |
| initRecyclerTemplateList(instance, node, parent); |
| } |
| |
| private void initRecyclerTemplateList(WXSDKInstance instance, WXDomObject node, WXVContainer parent){ |
| if (node != null && node instanceof WXRecyclerDomObject) { |
| mDomObject = (WXRecyclerDomObject) node; |
| mDomObject.preCalculateCellWidth(); |
| mLayoutType = mDomObject.getLayoutType(); |
| updateRecyclerAttr(); |
| } |
| mTemplateViewTypes = new ArrayMap<>(); |
| mTemplateViewTypes.put("", 0); //empty view, when template was not sended |
| mTemplateSources = new HashMap<>(); |
| mTemplatesCache = new ConcurrentHashMap<>(); |
| mStickyHelper = new TemplateStickyHelper(this); |
| orientation = mDomObject.getOrientation(); |
| listDataTemplateKey = WXUtils.getString(getDomObject().getAttrs().get(Constants.Name.Recycler.LIST_DATA_TEMPLATE_KEY), Constants.Name.Recycler.SLOT_TEMPLATE_TYPE); |
| listDataItemKey = WXUtils.getString(getDomObject().getAttrs().get(Constants.Name.Recycler.LIST_DATA_ITEM), listDataItemKey); |
| listDataIndexKey = WXUtils.getString(getDomObject().getAttrs().get(Constants.Name.Recycler.LIST_DATA_ITEM_INDEX), listDataIndexKey); |
| if( getDomObject().getAttrs().get(Constants.Name.Recycler.LIST_DATA) instanceof JSONArray) { |
| JSONArray array = (JSONArray)getDomObject().getAttrs().get(Constants.Name.Recycler.LIST_DATA); |
| if(array.size() > 0) { |
| listData = array; |
| } |
| } |
| long start = System.currentTimeMillis(); |
| if(mDomObject != null && mDomObject.getCellList() != null){ |
| for(int i=0; i<mDomObject.getCellList().size(); i++){ |
| addChild(DomTreeBuilder.buildTree(mDomObject.getCellList().get(i), this)); |
| } |
| } |
| if(WXEnvironment.isApkDebugable()){ |
| WXLogUtils.d(TAG, "TemplateList BuildDomTree Used " + (System.currentTimeMillis() - start)); |
| } |
| } |
| |
| @Override |
| protected BounceRecyclerView initComponentHostView(@NonNull Context context) { |
| final BounceRecyclerView bounceRecyclerView = new BounceRecyclerView(context,mLayoutType,mColumnCount,mColumnGap, getOrientation()); |
| WXAttr attrs = getDomObject().getAttrs(); |
| String transforms = (String) attrs.get(Constants.Name.TRANSFORM); |
| if (transforms != null) { |
| bounceRecyclerView.getInnerView().addItemDecoration(RecyclerTransform.parseTransforms(getOrientation(), transforms)); |
| } |
| mItemAnimator = bounceRecyclerView.getInnerView().getItemAnimator(); |
| |
| if(attrs.get(NAME_TEMPLATE_CACHE_SIZE) != null){ |
| templateCacheSize = WXUtils.getInteger(attrs.get(NAME_TEMPLATE_CACHE_SIZE), templateCacheSize); |
| } |
| |
| boolean hasFixedSize = false; |
| int itemViewCacheSize = 2; |
| if(attrs.get(NAME_ITEM_VIEW_CACHE_SIZE) != null){ |
| itemViewCacheSize = WXUtils.getNumberInt(getDomObject().getAttrs().get(NAME_ITEM_VIEW_CACHE_SIZE), itemViewCacheSize); |
| } |
| if(attrs.get(NAME_HAS_FIXED_SIZE) != null){ |
| hasFixedSize = WXUtils.getBoolean(attrs.get(NAME_HAS_FIXED_SIZE), hasFixedSize); |
| } |
| RecyclerViewBaseAdapter recyclerViewBaseAdapter = new RecyclerViewBaseAdapter<>(this); |
| recyclerViewBaseAdapter.setHasStableIds(true); |
| bounceRecyclerView.getInnerView().setItemAnimator(null); |
| if(itemViewCacheSize != 2) { |
| bounceRecyclerView.getInnerView().setItemViewCacheSize(itemViewCacheSize); |
| } |
| bounceRecyclerView.getInnerView().setHasFixedSize(hasFixedSize); |
| bounceRecyclerView.setRecyclerViewBaseAdapter(recyclerViewBaseAdapter); |
| bounceRecyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER); |
| bounceRecyclerView.getInnerView().clearOnScrollListeners(); |
| bounceRecyclerView.getInnerView().addOnScrollListener(mViewOnScrollListener); |
| bounceRecyclerView.getInnerView().addOnScrollListener(new RecyclerView.OnScrollListener() { |
| @Override |
| public void onScrollStateChanged(RecyclerView recyclerView, int newState) { |
| super.onScrollStateChanged(recyclerView, newState); |
| List<OnWXScrollListener> listeners = getInstance().getWXScrollListeners(); |
| if (listeners != null && listeners.size() > 0) { |
| for (OnWXScrollListener listener : listeners) { |
| if (listener != null) { |
| View topView = recyclerView.getChildAt(0); |
| if (topView != null) { |
| int y = topView.getTop(); |
| listener.onScrollStateChanged(recyclerView, 0, y, newState); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
| super.onScrolled(recyclerView, dx, dy); |
| List<OnWXScrollListener> listeners = getInstance().getWXScrollListeners(); |
| if (listeners != null && listeners.size() > 0) { |
| try { |
| for (OnWXScrollListener listener : listeners) { |
| if (listener != null) { |
| if (listener instanceof ICheckBindingScroller) { |
| if (((ICheckBindingScroller) listener).isNeedScroller(getRef(), null)) { |
| listener.onScrolled(recyclerView, dx, dy); |
| } |
| } else { |
| listener.onScrolled(recyclerView, dx, dy); |
| } |
| } |
| } |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| }); |
| bounceRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { |
| @TargetApi(Build.VERSION_CODES.JELLY_BEAN) |
| @Override |
| public void onGlobalLayout() { |
| BounceRecyclerView view; |
| if ((view = getHostView()) == null) |
| return; |
| mViewOnScrollListener.onScrolled(view.getInnerView(), 0, 0); |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
| view.getViewTreeObserver().removeOnGlobalLayoutListener(this); |
| } else { |
| view.getViewTreeObserver().removeGlobalOnLayoutListener(this); |
| } |
| } |
| }); |
| listUpdateRunnable = new Runnable() { |
| @Override |
| public void run() { |
| /** |
| * compute sticky position |
| * */ |
| if(mStickyHelper != null){ |
| mStickyHelper.getStickyPositions().clear(); |
| if(listData != null){ |
| for(int i=0; i<listData.size(); i++){ |
| WXCell cell = getSourceTemplate(i); |
| if(cell == null){ |
| continue; |
| } |
| if(cell.isSticky()){ |
| mStickyHelper.getStickyPositions().add(i); |
| } |
| } |
| } |
| } |
| if(getHostView() != null && getHostView().getRecyclerViewBaseAdapter() != null){ |
| getHostView().getRecyclerViewBaseAdapter().notifyDataSetChanged(); |
| } |
| if(WXEnvironment.isApkDebugable()){ |
| WXLogUtils.d(TAG, "WXTemplateList notifyDataSetChanged"); |
| } |
| } |
| }; |
| return bounceRecyclerView; |
| } |
| |
| |
| |
| |
| @Override |
| protected void onHostViewInitialized(BounceRecyclerView host) { |
| super.onHostViewInitialized(host); |
| WXRecyclerView recyclerView = host.getInnerView(); |
| if (recyclerView == null || recyclerView.getAdapter() == null) { |
| WXLogUtils.e(TAG, "RecyclerView is not found or Adapter is not bound"); |
| return; |
| } |
| } |
| |
| /** |
| * Measure the size of the recyclerView. |
| * |
| * @param width the expected width |
| * @param height the expected height |
| * @return the result of measurement |
| */ |
| @Override |
| protected MeasureOutput measure(int width, int height) { |
| int screenH = WXViewUtils.getScreenHeight(WXEnvironment.sApplication); |
| int weexH = WXViewUtils.getWeexHeight(getInstanceId()); |
| int outHeight = height > (weexH >= screenH ? screenH : weexH) ? weexH - getAbsoluteY() : height; |
| return super.measure((int)(width+mColumnGap), outHeight); |
| } |
| |
| |
| @Override |
| public void bindStickStyle(WXComponent component) { |
| WXComponent template = findParentType(component, WXCell.class); |
| if(template == null){ |
| return; |
| } |
| if(listData == null || mStickyHelper == null){ |
| return; |
| } |
| if(!mStickyHelper.getStickyTypes().contains(template.getRef())){ |
| mStickyHelper.getStickyTypes().add(template.getRef()); |
| notifyUpdateList(); |
| } |
| } |
| |
| @Override |
| public void unbindStickStyle(WXComponent component) { |
| WXComponent template = findParentType(component, WXCell.class); |
| if(template == null |
| || listData == null |
| || mStickyHelper == null){ |
| return; |
| } |
| if(mStickyHelper.getStickyTypes().contains(template.getRef())){ |
| mStickyHelper.getStickyTypes().remove(template.getRef()); |
| notifyUpdateList(); |
| } |
| } |
| |
| private @Nullable WXCell findCell(WXComponent component) { |
| if(component instanceof WXCell){ |
| return (WXCell) component; |
| } |
| WXComponent parent; |
| if (component == null || (parent = component.getParent()) == null) { |
| return null; |
| } |
| return findCell(parent); |
| } |
| |
| private void setAppearanceWatch(WXComponent component, int event, boolean enable) { |
| if(listData == null |
| || mAppearHelpers == null |
| || TextUtils.isEmpty(component.getRef())){ |
| return; |
| } |
| WXComponent cell = findCell(component); |
| int type = getCellItemType(cell); |
| if(type <= 0){ |
| return; |
| } |
| List<AppearanceHelper> mAppearListeners = mAppearHelpers.get(type); |
| if(mAppearListeners == null){ |
| mAppearListeners = new ArrayList<>(); |
| mAppearHelpers.put(type, mAppearListeners); |
| } |
| AppearanceHelper item = null; |
| for(AppearanceHelper mAppearListener : mAppearListeners){ |
| if(component.getRef().equals(mAppearListener.getAwareChild().getRef())){ |
| item = mAppearListener; |
| break; |
| } |
| } |
| if (item != null) { |
| item.setWatchEvent(event, enable); |
| }else { |
| item = new AppearanceHelper(component, type); |
| item.setWatchEvent(event, enable); |
| mAppearListeners.add(item); |
| } |
| } |
| |
| @Override |
| public void bindAppearEvent(WXComponent component) { |
| setAppearanceWatch(component, AppearanceHelper.APPEAR, true); |
| } |
| |
| @Override |
| public void bindDisappearEvent(WXComponent component) { |
| setAppearanceWatch(component, AppearanceHelper.DISAPPEAR, true); |
| } |
| |
| @Override |
| public void unbindAppearEvent(WXComponent component) { |
| setAppearanceWatch(component, AppearanceHelper.APPEAR, false); |
| } |
| |
| @Override |
| public void unbindDisappearEvent(WXComponent component) { |
| setAppearanceWatch(component, AppearanceHelper.DISAPPEAR, false); |
| } |
| |
| |
| @JSMethod |
| public void scrollTo(int position, Map<String, Object> options){ |
| if (position >= 0) { |
| boolean smooth = true; |
| if(options != null) { |
| smooth = WXUtils.getBoolean(options.get(Constants.Name.ANIMATED), true); |
| } |
| |
| final int pos = position; |
| BounceRecyclerView bounceRecyclerView = getHostView(); |
| if (bounceRecyclerView == null) { |
| return; |
| } |
| final WXRecyclerView view = bounceRecyclerView.getInnerView(); |
| view.scrollTo(smooth, pos, 0, getOrientation()); |
| } |
| } |
| |
| |
| @Override |
| public void scrollTo(WXComponent component, Map<String, Object> options) { |
| float offsetFloat = 0; |
| boolean smooth = true; |
| int position = -1; |
| int typeIndex = -1; |
| if (options != null) { |
| String offsetStr = options.get(Constants.Name.OFFSET) == null ? "0" : options.get(Constants.Name.OFFSET).toString(); |
| smooth = WXUtils.getBoolean(options.get(Constants.Name.ANIMATED), true); |
| if (offsetStr != null) { |
| try { |
| offsetFloat = WXViewUtils.getRealPxByWidth(Float.parseFloat(offsetStr), getInstance().getInstanceViewPortWidth()); |
| }catch (Exception e ){ |
| WXLogUtils.e("Float parseFloat error :"+e.getMessage()); |
| } |
| } |
| position = WXUtils.getNumberInt(options.get(Constants.Name.Recycler.CELL_INDEX), -1); |
| typeIndex = WXUtils.getNumberInt(options.get(Constants.Name.Recycler.TYPE_INDEX), -1); |
| } |
| WXCell cell = findCell(component); |
| if(typeIndex >= 0){ |
| if(listData != null && component.getRef() != null){ |
| int typePosition = 0; |
| for(int i=0; i<listData.size(); i++){ |
| WXCell template = getSourceTemplate(i); |
| if(template == null){ |
| continue; |
| } |
| if(cell.getRef().equals(template.getRef())){ |
| typePosition ++; |
| } |
| if(typePosition > typeIndex){ |
| position = i; |
| break; |
| } |
| } |
| if(position < 0){ |
| position = listData.size() - 1; |
| } |
| } |
| } |
| |
| final int offset = (int) offsetFloat; |
| BounceRecyclerView bounceRecyclerView = getHostView(); |
| if (bounceRecyclerView == null) { |
| return; |
| } |
| if (position >= 0) { |
| final int pos = position; |
| final WXRecyclerView view = bounceRecyclerView.getInnerView(); |
| view.scrollTo(smooth, pos, offset, getOrientation()); |
| } |
| } |
| |
| @Override |
| public int getScrollY() { |
| BounceRecyclerView bounceRecyclerView = getHostView(); |
| return bounceRecyclerView == null ? 0 : bounceRecyclerView.getInnerView().getScrollY(); |
| } |
| |
| @Override |
| public int getScrollX() { |
| BounceRecyclerView bounceRecyclerView = getHostView(); |
| return bounceRecyclerView == null ? 0 : bounceRecyclerView.getInnerView().getScrollX(); |
| } |
| |
| public int getOrientation() { |
| return orientation; |
| } |
| |
| @Override |
| public boolean isScrollable() { |
| return isScrollable; |
| } |
| |
| |
| |
| @Override |
| public void addChild(WXComponent child) { |
| this.addChild(child, -1); |
| } |
| |
| @Override |
| protected int getChildrenLayoutTopOffset() { |
| return 0; |
| } |
| |
| @Override |
| public void addChild(WXComponent child, int index) { |
| if(!(child instanceof WXCell)) { |
| super.addChild(child, index); |
| } |
| if(child instanceof WXBaseRefresh){ |
| return; |
| } |
| if(child instanceof WXCell){ |
| if(child.getDomObject() != null && child.getDomObject().getAttrs() != null){ |
| Object templateId = child.getDomObject().getAttrs().get(Constants.Name.Recycler.SLOT_TEMPLATE_TYPE); |
| String key = WXUtils.getString(templateId, null); |
| if(key != null){ |
| if(child.getDomObject() instanceof WXCellDomObject |
| && getDomObject() instanceof WXRecyclerDomObject){ |
| WXCellDomObject domObject = (WXCellDomObject) child.getDomObject(); |
| domObject.setRecyclerDomObject((WXRecyclerDomObject) getDomObject()); |
| } |
| mTemplateSources.put(key, (WXCell) child); |
| ensureSourceCellRenderWithData((WXCell)child); |
| if(mTemplateViewTypes.get(key) == null){ |
| mTemplateViewTypes.put(key, mTemplateViewTypes.size()); |
| } |
| } |
| } |
| notifyUpdateList(); |
| } |
| } |
| |
| |
| |
| /** |
| * RecyclerView manage its children in a way that different from {@link WXVContainer}. Therefore, |
| * {@link WXVContainer#addSubView(View, int)} is an empty implementation in {@link |
| * com.taobao.weex.ui.view.listview.WXRecyclerView} |
| */ |
| @Override |
| public void addSubView(View child, int index) { |
| |
| } |
| |
| |
| /** |
| * all child is template, none need onCreate child except loading and refresh. |
| * */ |
| @Override |
| public void createChildViewAt(int index) { |
| int indexToCreate = index; |
| if (indexToCreate < 0) { |
| indexToCreate = childCount() - 1; |
| if (indexToCreate < 0) { |
| return; |
| } |
| } |
| final WXComponent child = getChild(indexToCreate); |
| if (child instanceof WXBaseRefresh) { |
| child.createView(); |
| setRefreshOrLoading(child); |
| } |
| } |
| |
| @Override |
| public void remove(WXComponent child, boolean destroy) { |
| removeFooterOrHeader(child); |
| super.remove(child, destroy); |
| } |
| |
| |
| |
| @Override |
| public void computeVisiblePointInViewCoordinate(PointF pointF) { |
| RecyclerView view = getHostView().getInnerView(); |
| pointF.set(view.computeHorizontalScrollOffset(), view.computeVerticalScrollOffset()); |
| } |
| |
| @Override |
| protected boolean setProperty(String key, Object param) { |
| switch (key) { |
| case Constants.Name.Recycler.LIST_DATA:{ |
| setListData(param); |
| } |
| return true; |
| case Constants.Name.Recycler.LIST_DATA_ITEM: |
| listDataItemKey = WXUtils.getString(param, listDataItemKey); |
| return true; |
| case Constants.Name.Recycler.LIST_DATA_ITEM_INDEX: |
| listDataIndexKey = WXUtils.getString(param, listDataIndexKey); |
| return true; |
| case Constants.Name.Recycler.LIST_DATA_TEMPLATE_KEY: |
| case Constants.Name.Recycler.SLOT_TEMPLATE_TYPE: |
| listDataTemplateKey = WXUtils.getString(param, Constants.Name.Recycler.SLOT_TEMPLATE_TYPE); |
| return true; |
| case LOADMOREOFFSET: |
| return true; |
| case Constants.Name.SCROLLABLE: |
| boolean scrollable = WXUtils.getBoolean(param, true); |
| setScrollable(scrollable); |
| return true; |
| case Constants.Name.SHOW_SCROLLBAR: |
| Boolean result = WXUtils.getBoolean(param,null); |
| if (result != null) |
| setShowScrollbar(result); |
| return true; |
| case NAME_ITEM_VIEW_CACHE_SIZE: |
| return true; |
| case NAME_HAS_FIXED_SIZE: |
| return true; |
| case Constants.Name.OFFSET_ACCURACY: |
| int accuracy = WXUtils.getInteger(param, 10); |
| setOffsetAccuracy(accuracy); |
| return true; |
| } |
| return super.setProperty(key, param); |
| } |
| |
| |
| @WXComponentProp(name = Constants.Name.OFFSET_ACCURACY) |
| public void setOffsetAccuracy(int accuracy) { |
| float real = WXViewUtils.getRealPxByWidth(accuracy, getInstance().getInstanceViewPortWidth()); |
| this.mOffsetAccuracy = (int) real; |
| } |
| |
| |
| private void updateRecyclerAttr(){ |
| mColumnCount = mDomObject.getColumnCount(); |
| mColumnGap = mDomObject.getColumnGap(); |
| mColumnWidth = mDomObject.getColumnWidth(); |
| mPaddingLeft =mDomObject.getPadding().get(Spacing.LEFT); |
| mPaddingRight =mDomObject.getPadding().get(Spacing.RIGHT); |
| } |
| |
| @WXComponentProp(name = Constants.Name.COLUMN_WIDTH) |
| public void setColumnWidth(int columnCount) { |
| if(mDomObject.getColumnWidth() != mColumnWidth){ |
| updateRecyclerAttr(); |
| WXRecyclerView wxRecyclerView = getHostView().getInnerView(); |
| wxRecyclerView.initView(getContext(), mLayoutType,mColumnCount,mColumnGap, getOrientation()); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.SHOW_SCROLLBAR) |
| public void setShowScrollbar(boolean show) { |
| if(getHostView() == null || getHostView().getInnerView() == null){ |
| return; |
| } |
| if (getOrientation() == Constants.Orientation.VERTICAL) { |
| getHostView().getInnerView().setVerticalScrollBarEnabled(show); |
| } else { |
| getHostView().getInnerView().setHorizontalScrollBarEnabled(show); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.COLUMN_COUNT) |
| public void setColumnCount(int columnCount){ |
| if(mDomObject.getColumnCount() != mColumnCount){ |
| updateRecyclerAttr(); |
| WXRecyclerView wxRecyclerView = getHostView().getInnerView(); |
| wxRecyclerView.initView(getContext(), mLayoutType,mColumnCount,mColumnGap,getOrientation()); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.COLUMN_GAP) |
| public void setColumnGap(float columnGap) throws InterruptedException { |
| if(mDomObject.getColumnGap() != mColumnGap) { |
| updateRecyclerAttr(); |
| WXRecyclerView wxRecyclerView = getHostView().getInnerView(); |
| wxRecyclerView.initView(getContext(), mLayoutType, mColumnCount, mColumnGap, getOrientation()); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.SCROLLABLE) |
| public void setScrollable(boolean scrollable) { |
| WXRecyclerView inner = getHostView().getInnerView(); |
| inner.setScrollable(scrollable); |
| } |
| |
| @JSMethod |
| public void setListData(Object param){ |
| boolean update = listData != null && listData != param; |
| if(param instanceof JSONArray){ |
| listData = (JSONArray) param; |
| } |
| if(update){ |
| notifyUpdateList(); |
| } |
| } |
| |
| @JSMethod |
| public void appendData(JSONArray arrayObject){ |
| if(listData == null){ |
| listData = new JSONArray(); |
| } |
| if(arrayObject instanceof JSONArray){ |
| listData.addAll(arrayObject); |
| } |
| notifyUpdateList(); |
| } |
| |
| @JSMethod |
| public void insertData(JSONObject data, int index){ |
| if(data == null){ |
| return; |
| } |
| if(listData == null || index >= listData.size()){ |
| return; |
| } |
| listData.add(index, data); |
| notifyUpdateList(); |
| } |
| |
| @JSMethod |
| public void updateData(JSONObject data, int index){ |
| if(data == null){ |
| return; |
| } |
| if(listData == null || index >= listData.size()){ |
| return; |
| } |
| listData.set(index, data); |
| notifyUpdateList(); |
| } |
| |
| @JSMethod |
| public void removeData(List<Integer> array){ |
| if(array == null || array.size() == 0){ |
| return; |
| } |
| int offset = 0; |
| for(Integer index : array){ |
| if(listData == null |
| || index == null){ |
| return; |
| } |
| index -= offset; |
| if(index < listData.size()){ |
| listData.remove((int)index); |
| offset++; |
| } |
| } |
| notifyUpdateList(); |
| } |
| |
| |
| @JSMethod |
| public void resetLoadmore() { |
| mForceLoadmoreNextTime = true; |
| mListCellCount = 0; |
| } |
| |
| |
| @Override |
| public void updateProperties(Map<String, Object> props) { |
| super.updateProperties(props); |
| if(props.containsKey(Constants.Name.PADDING) |
| || props.containsKey(Constants.Name.PADDING_LEFT) |
| || props.containsKey(Constants.Name.PADDING_RIGHT)){ |
| |
| if(mPaddingLeft !=mDomObject.getPadding().get(Spacing.LEFT) |
| || mPaddingRight !=mDomObject.getPadding().get(Spacing.RIGHT)) { |
| updateRecyclerAttr(); |
| WXRecyclerView wxRecyclerView = getHostView().getInnerView(); |
| wxRecyclerView.initView(getContext(), mLayoutType, mColumnCount, mColumnGap, getOrientation()); |
| } |
| } |
| } |
| |
| |
| |
| @Override |
| public void addEvent(String type) { |
| super.addEvent(type); |
| if (Constants.Event.SCROLL.equals(type) && getHostView() != null && getHostView().getInnerView() != null) { |
| WXRecyclerView innerView = getHostView().getInnerView(); |
| innerView.addOnScrollListener(new RecyclerView.OnScrollListener() { |
| private int offsetXCorrection, offsetYCorrection; |
| private boolean mFirstEvent = true; |
| |
| @Override |
| public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
| super.onScrolled(recyclerView, dx, dy); |
| RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); |
| if (!layoutManager.canScrollVertically()) { |
| return; |
| } |
| int offsetX = recyclerView.computeHorizontalScrollOffset(); |
| int offsetY = recyclerView.computeVerticalScrollOffset(); |
| |
| if (dx == 0 && dy == 0) { |
| offsetXCorrection = offsetX; |
| offsetYCorrection = offsetY; |
| offsetX = 0; |
| offsetY = 0; |
| } else { |
| offsetX = offsetX - offsetXCorrection; |
| offsetY = offsetY - offsetYCorrection; |
| } |
| |
| if (mFirstEvent) { |
| //skip first event |
| mFirstEvent = false; |
| return; |
| } |
| |
| if (shouldReport(offsetX, offsetY)) { |
| fireScrollEvent(recyclerView, offsetX, offsetY); |
| } |
| } |
| }); |
| } |
| } |
| |
| private void fireScrollEvent(RecyclerView recyclerView, int offsetX, int offsetY) { |
| offsetY = -calcContentOffset(recyclerView); |
| int contentWidth = recyclerView.getMeasuredWidth() + recyclerView.computeHorizontalScrollRange(); |
| int contentHeight = calcContentSize(); |
| |
| Map<String, Object> event = new HashMap<>(2); |
| Map<String, Object> contentSize = new HashMap<>(2); |
| Map<String, Object> contentOffset = new HashMap<>(2); |
| |
| contentSize.put(Constants.Name.WIDTH, WXViewUtils.getWebPxByWidth(contentWidth, getInstance().getInstanceViewPortWidth())); |
| contentSize.put(Constants.Name.HEIGHT, WXViewUtils.getWebPxByWidth(contentHeight, getInstance().getInstanceViewPortWidth())); |
| |
| contentOffset.put(Constants.Name.X, - WXViewUtils.getWebPxByWidth(offsetX, getInstance().getInstanceViewPortWidth())); |
| contentOffset.put(Constants.Name.Y, - WXViewUtils.getWebPxByWidth(offsetY, getInstance().getInstanceViewPortWidth())); |
| event.put(Constants.Name.CONTENT_SIZE, contentSize); |
| event.put(Constants.Name.CONTENT_OFFSET, contentOffset); |
| |
| fireEvent(Constants.Event.SCROLL, event); |
| } |
| |
| private boolean shouldReport(int offsetX, int offsetY) { |
| if (mLastReport.x == -1 && mLastReport.y == -1) { |
| mLastReport.x = offsetX; |
| mLastReport.y = offsetY; |
| return true; |
| } |
| |
| int gapX = Math.abs(mLastReport.x - offsetX); |
| int gapY = Math.abs(mLastReport.y - offsetY); |
| |
| if (gapX >= mOffsetAccuracy || gapY >= mOffsetAccuracy) { |
| mLastReport.x = offsetX; |
| mLastReport.y = offsetY; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| /** |
| * Setting refresh view and loading view |
| * |
| * @param child the refresh_view or loading_view |
| */ |
| private boolean setRefreshOrLoading(final WXComponent child) { |
| if (child instanceof WXRefresh && getHostView() != null) { |
| getHostView().setOnRefreshListener((WXRefresh) child); |
| getHostView().postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| getHostView().setHeaderView(child); |
| } |
| }, 100); |
| return true; |
| } |
| |
| if (child instanceof WXLoading && getHostView() != null) { |
| getHostView().setOnLoadingListener((WXLoading) child); |
| getHostView().postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| getHostView().setFooterView(child); |
| } |
| }, 100); |
| return true; |
| } |
| return false; |
| } |
| |
| |
| private void removeFooterOrHeader(WXComponent child) { |
| if (child instanceof WXLoading) { |
| getHostView().removeFooterView(child); |
| } else if (child instanceof WXRefresh) { |
| getHostView().removeHeaderView(child); |
| } |
| } |
| |
| @Override |
| public ViewGroup.LayoutParams getChildLayoutParams(WXComponent child, View hostView, int width, int height, int left, int right, int top, int bottom) { |
| ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) hostView.getLayoutParams(); |
| if (child instanceof WXBaseRefresh && params == null) { |
| params = new LinearLayout.LayoutParams(width, height); |
| } else if (params == null) { |
| params = new RecyclerView.LayoutParams(width, height); |
| } else { |
| params.width = width; |
| params.height = height; |
| params.setMargins(left, 0, right, 0); |
| } |
| return params; |
| } |
| |
| |
| @Override |
| public void destroy() { |
| if(getHostView() != null){ |
| getHostView().removeCallbacks(listUpdateRunnable); |
| if(getHostView().getInnerView() != null){ |
| getHostView().getInnerView().setAdapter(null); |
| } |
| } |
| if(listData != null){ |
| listData = null; |
| } |
| if(mStickyHelper != null){ |
| mStickyHelper = null; |
| } |
| if(mTemplateViewTypes != null){ |
| mTemplateViewTypes.clear(); |
| } |
| if(mTemplateSources != null){ |
| mTemplateSources.clear(); |
| } |
| if(mAppearHelpers != null){ |
| mAppearHelpers.clear(); |
| } |
| if(mDisAppearWatchList != null){ |
| mDisAppearWatchList.clear(); |
| } |
| super.destroy(); |
| } |
| |
| |
| |
| @Override |
| public void onViewRecycled(TemplateViewHolder holder) {} |
| |
| @Override |
| public void onBindViewHolder(final TemplateViewHolder templateViewHolder, int position) { |
| if(templateViewHolder == null){ |
| return; |
| } |
| WXCell component = templateViewHolder.getTemplate(); |
| if(component == null){ |
| return; |
| } |
| long start = System.currentTimeMillis(); |
| boolean resuse = templateViewHolder.getHolderPosition() >= 0; |
| templateViewHolder.setHolderPosition(position); |
| Object data = listData.get(position); |
| if(component.getRenderData() == data){ |
| component.setHasLayout(true); |
| }else{ |
| List<WXComponent> updates = Statements.doRender(component, getStackContextForPosition(position, data)); |
| Statements.doInitCompontent(updates); |
| component.setRenderData(data); |
| if(WXEnvironment.isApkDebugable()){ |
| WXLogUtils.d(TAG, position + getTemplateKey(position) + " onBindViewHolder render used " + (System.currentTimeMillis() - start)); |
| } |
| if(component.isHasLayout()){ |
| resuse = true; |
| } |
| Layouts.doLayoutAsync(templateViewHolder, true); |
| component.setHasLayout(true); |
| if(WXEnvironment.isApkDebugable()){ |
| WXLogUtils.d(TAG, position + getTemplateKey(position) + " onBindViewHolder layout used " + (System.currentTimeMillis() - start) + resuse); |
| } |
| } |
| } |
| |
| @Override |
| public TemplateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
| String template = mTemplateViewTypes.keyAt(viewType); |
| WXCell source = mTemplateSources.get(template); |
| if(source == null){ |
| FrameLayout view = new FrameLayout(getContext()); |
| view.setLayoutParams(new FrameLayout.LayoutParams(0, 0)); |
| return new TemplateViewHolder(view, viewType); |
| } |
| TemplateCache cache = mTemplatesCache.get(template); |
| WXCell component = null; |
| boolean cacheHit = true; |
| if(cache != null && cache.cells != null && cache.cells.size() > 0){ |
| component = cache.cells.poll(); |
| } |
| if(cache == null || !cache.isLoadIng){ |
| asyncLoadTemplateCache(template); |
| } |
| if(component == null){ |
| cacheHit = false; |
| if(!source.isSourceUsed()){ |
| source.setSourceUsed(true); |
| ensureSourceCellRenderWithData(source); |
| component = source; |
| if(WXEnvironment.isApkDebugable()) { |
| WXLogUtils.d(TAG, template + " onCreateViewHolder source"); |
| } |
| } |
| } |
| if(component == null) { |
| long start = System.currentTimeMillis(); |
| component = (WXCell) copyCell(source); |
| if(WXEnvironment.isApkDebugable()) { |
| WXLogUtils.d(TAG, template + " onCreateViewHolder copy used " + (System.currentTimeMillis() - start)); |
| } |
| } |
| if(component.isLazy()) { |
| doInitLazyCell(component, template, false); |
| if(WXEnvironment.isApkDebugable()) { |
| WXLogUtils.d(TAG, template + " onCreateViewHolder cache hit " + cacheHit + " idle init false "); |
| } |
| }else{ |
| if(WXEnvironment.isApkDebugable()) { |
| WXLogUtils.d(TAG, template + " onCreateViewHolder cache hit " + cacheHit + " idle init true"); |
| } |
| } |
| TemplateViewHolder templateViewHolder = new TemplateViewHolder(component, viewType); |
| return templateViewHolder; |
| } |
| |
| |
| /** |
| * copy cell component from source, init render data, and return source |
| * if none data, return null |
| * */ |
| private WXComponent copyCell(WXCell cell){ |
| ensureSourceCellRenderWithData(cell); |
| WXCell component = (WXCell) Statements.copyComponentTree(cell); |
| if(component.getDomObject() instanceof WXCellDomObject |
| && getDomObject() instanceof WXRecyclerDomObject){ |
| WXCellDomObject domObject = (WXCellDomObject) component.getDomObject(); |
| domObject.setRecyclerDomObject((WXRecyclerDomObject) getDomObject()); |
| } |
| component.setRenderData(cell.getRenderData()); |
| return component; |
| } |
| |
| private void ensureSourceCellRenderWithData(WXCell cell){ |
| if(cell.getRenderData() == null){ |
| if(listData != null && listData.size() > 0){ |
| synchronized (this){ |
| if(cell.getRenderData() == null){ |
| for(int i=0; i<listData.size(); i++){ |
| if(cell == getSourceTemplate(i)){ |
| Object data = listData.get(i); |
| Statements.doRender(cell, getStackContextForPosition(i, data)); |
| Layouts.doSafeLayout(cell, new CSSLayoutContext()); |
| cell.setRenderData(data); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param position |
| * when template not send, return an invalid id, use empty view holder. |
| * when template has sended, use real template id to refresh view, use real view holder. |
| * */ |
| @Override |
| public int getItemViewType(int position) { |
| JSONObject data = safeGetListData(position); |
| String template = data.getString(listDataTemplateKey); |
| if(TextUtils.isEmpty(template)){ |
| template = ""; |
| } |
| int type = mTemplateViewTypes.indexOfKey(template); |
| if(type < 0){ |
| type = 0; |
| } |
| return type; |
| } |
| |
| |
| /** |
| * return code context for render component |
| * */ |
| private ArrayStack getStackContextForPosition(int position, Object item){ |
| if(!bindIngStackContext.isEmpty()){ |
| bindIngStackContext.getList().clear(); |
| } |
| if(!bindIngMapContext.isEmpty()){ |
| bindIngMapContext.clear(); |
| } |
| ArrayStack stack = bindIngStackContext; |
| Map map = bindIngMapContext; |
| if(listData != null){ |
| stack.push(listData); |
| stack.push(map); |
| map.put(listDataKey, listData); |
| if(!TextUtils.isEmpty(listDataIndexKey)) { |
| map.put(listDataIndexKey, position); |
| } |
| if(!TextUtils.isEmpty(listDataItemKey)) { |
| map.put(listDataItemKey, item); |
| }else{ |
| stack.push(item); |
| } |
| } |
| return stack; |
| } |
| |
| /** |
| * return tepmlate key for position |
| * */ |
| public String getTemplateKey(int position){ |
| JSONObject data = safeGetListData(position); |
| String template = data.getString(listDataTemplateKey); |
| if(TextUtils.isEmpty(template)){ |
| template = ""; |
| } |
| return template; |
| } |
| |
| /** |
| * get source template |
| * */ |
| public WXCell getSourceTemplate(int position){ |
| String template = getTemplateKey(position); |
| return mTemplateSources.get(template); |
| } |
| |
| |
| |
| /** |
| * get template key from cell; 0 for default type |
| * */ |
| private int getCellItemType(WXComponent cell){ |
| if(cell == null){ |
| return -1; |
| } |
| if(cell.getDomObject() != null && cell.getDomObject().getAttrs() != null){ |
| Object templateId = cell.getDomObject().getAttrs().get(Constants.Name.Recycler.SLOT_TEMPLATE_TYPE); |
| String template = WXUtils.getString(templateId, null); |
| if(template == null){ |
| return 0; |
| } |
| int type = mTemplateViewTypes.indexOfKey(template); |
| if(type < 0){ |
| return -1; |
| } |
| return type; |
| } |
| return 0; |
| } |
| |
| @Override |
| public int getItemCount() { |
| if(listData == null){ |
| return 0; |
| } |
| if(mTemplateViewTypes == null || mTemplateViewTypes.size() <= 1){ |
| return 0; |
| } |
| if(mTemplateSources == null || mTemplateSources.size() == 0){ |
| return 0; |
| } |
| return listData.size(); |
| } |
| |
| @Override |
| public boolean onFailedToRecycleView(TemplateViewHolder holder) { |
| return false; |
| } |
| |
| |
| /** |
| * @param position |
| * when template not send by javascript, return an invalid id, force use empty view holder. |
| * when template has sended by javascript, use real template id to refresh view, use real view holder. |
| * */ |
| @Override |
| public long getItemId(int position) { |
| if(getItemViewType(position) <= 0){ |
| return RecyclerView.NO_ID; |
| } |
| JSONObject data = safeGetListData(position); |
| if(data.containsKey(Constants.Name.Recycler.LIST_DATA_ITEM_ID)) { |
| String itemKey = data.getString(Constants.Name.Recycler.LIST_DATA_ITEM_ID); |
| if(TextUtils.isEmpty(itemKey)){ |
| return position; |
| } |
| return itemKey.hashCode(); |
| } |
| return position; |
| } |
| |
| @Override |
| public void onBeforeScroll(int dx, int dy) { |
| if(mStickyHelper != null){ |
| mStickyHelper.onBeforeScroll(dx, dy); |
| } |
| } |
| |
| @Override |
| public void onLoadMore(int offScreenY) { |
| try { |
| String offset = getDomObject().getAttrs().getLoadMoreOffset(); |
| |
| if (TextUtils.isEmpty(offset)) { |
| offset = "0"; |
| } |
| float offsetParsed = WXViewUtils.getRealPxByWidth(Integer.parseInt(offset),getInstance().getInstanceViewPortWidth()); |
| |
| if (offScreenY <= offsetParsed && listData != null) { |
| if (mListCellCount != listData.size() |
| || mForceLoadmoreNextTime) { |
| fireEvent(Constants.Event.LOADMORE); |
| mListCellCount = listData.size(); |
| mForceLoadmoreNextTime = false; |
| } |
| } |
| } catch (Exception e) { |
| WXLogUtils.d(TAG + "onLoadMore :", e); |
| } |
| } |
| |
| /** |
| * |
| * first fire appear event. |
| * */ |
| @Override |
| public void notifyAppearStateChange(int firstVisible, int lastVisible, int directionX, int directionY) { |
| if(mAppearHelpers == null |
| || mAppearHelpers.size() <= 0){ |
| return; |
| } |
| String direction = directionY > 0 ? Constants.Value.DIRECTION_UP : |
| directionY < 0 ? Constants.Value.DIRECTION_DOWN : null; |
| if (getOrientation() == Constants.Orientation.HORIZONTAL && directionX != 0) { |
| direction = directionX > 0 ? Constants.Value.DIRECTION_LEFT : Constants.Value.DIRECTION_RIGHT; |
| } |
| RecyclerView recyclerView = getHostView().getInnerView(); |
| for(int position=firstVisible; position<=lastVisible; position++){ |
| int type = getItemViewType(position); |
| List<AppearanceHelper> helpers = mAppearHelpers.get(type); |
| if(helpers == null){ |
| continue; |
| } |
| for(AppearanceHelper helper : helpers){ |
| if(!helper.isWatch()){ |
| continue; |
| } |
| TemplateViewHolder itemHolder = (TemplateViewHolder) recyclerView.findViewHolderForAdapterPosition(position); |
| if(itemHolder == null || itemHolder.getComponent() == null){ |
| break; |
| } |
| List<WXComponent> childListeners = findChildListByRef(itemHolder.getComponent(), helper.getAwareChild().getRef()); |
| if(childListeners == null || childListeners.size() == 0){ |
| break; |
| } |
| |
| Map<String, Map<Integer, List<Object>>> disAppearList = mDisAppearWatchList.get(position); |
| if(disAppearList == null){ |
| disAppearList = new ArrayMap<>(); |
| mDisAppearWatchList.put(position, disAppearList); |
| } |
| |
| Map<Integer, List<Object>> componentDisAppearList = disAppearList.get(helper.getAwareChild().getRef()); |
| if(componentDisAppearList == null){ |
| componentDisAppearList = new ArrayMap<>(); |
| disAppearList.put(helper.getAwareChild().getRef(), componentDisAppearList); |
| } |
| |
| for(int m=0; m<childListeners.size(); m++){ |
| WXComponent childLisener = childListeners.get(m); |
| if(childLisener.getHostView() == null){ |
| continue; |
| } |
| boolean appear = helper.isViewVisible(childLisener.getHostView()); |
| int key = childLisener.getHostView().hashCode(); |
| if(appear){ |
| if(!componentDisAppearList.containsKey(key)){ |
| childLisener.notifyWatchAppearDisappearEvent(Constants.Event.APPEAR, direction); |
| List<Object> eventArgs = null; |
| if(childLisener.getDomObject().getEvents() != null |
| && childLisener.getDomObject().getEvents().getEventBindingArgsValues() != null |
| && childLisener.getDomObject().getEvents().getEventBindingArgsValues().get(Constants.Event.DISAPPEAR) != null){ |
| eventArgs = childLisener.getDomObject().getEvents().getEventBindingArgsValues().get(Constants.Event.DISAPPEAR); |
| } |
| componentDisAppearList.put(key, eventArgs); |
| } |
| }else{ |
| if(componentDisAppearList.containsKey(key)){ |
| childLisener.notifyWatchAppearDisappearEvent(Constants.Event.DISAPPEAR, direction); |
| componentDisAppearList.remove(key); |
| } |
| } |
| } |
| } |
| } |
| |
| //handle disappear event, out of position |
| int count = getItemCount(); |
| for (int position=0; position<count; position++){ |
| if(position >= firstVisible && position <= lastVisible){ |
| position = lastVisible + 1; |
| continue; |
| } |
| Map<String, Map<Integer, List<Object>>> map = mDisAppearWatchList.get(position); |
| if(map == null){ |
| continue; |
| } |
| WXCell template = mTemplateSources.get(getTemplateKey(position)); |
| if(template == null){ |
| return; |
| } |
| Set<Map.Entry<String, Map<Integer, List<Object>>>> cellWatcherEntries = map.entrySet(); |
| for(Map.Entry<String,Map<Integer, List<Object>>> cellWatcherEntry : cellWatcherEntries){ |
| String ref = cellWatcherEntry.getKey(); |
| WXComponent component = findChildByRef(template, ref); |
| if(component == null){ |
| continue; |
| } |
| Map<Integer, List<Object>> eventWatchers = cellWatcherEntry.getValue(); |
| if(eventWatchers == null || eventWatchers.size() == 0){ |
| continue; |
| } |
| WXEvent events = component.getDomObject().getEvents(); |
| Set<Map.Entry<Integer, List<Object>>> eventWatcherEntries = eventWatchers.entrySet(); |
| for(Map.Entry<Integer, List<Object>> eventWatcherEntry : eventWatcherEntries){ |
| events.putEventBindingArgsValue(Constants.Event.DISAPPEAR, eventWatcherEntry.getValue()); |
| component.notifyWatchAppearDisappearEvent(Constants.Event.DISAPPEAR, direction); |
| } |
| eventWatchers.clear(); |
| } |
| mDisAppearWatchList.remove(position); |
| } |
| } |
| |
| |
| private JSONObject safeGetListData(int position){ |
| try{ |
| return listData.getJSONObject(position); |
| }catch (Exception e){return JSONObject.parseObject("{}");} |
| } |
| |
| private void notifyUpdateList(){ |
| if(getHostView() == null |
| || getHostView().getInnerView() == null |
| || listUpdateRunnable == null){ |
| return; |
| } |
| if(Looper.getMainLooper().getThread().getId() != Thread.currentThread().getId()){ |
| getHostView().removeCallbacks(listUpdateRunnable); |
| getHostView().post(listUpdateRunnable); |
| }else{ |
| listUpdateRunnable.run(); |
| } |
| } |
| |
| private int calcContentSize() { |
| int totalHeight = 0; |
| if(listData == null){ |
| return totalHeight; |
| } |
| for (int i = 0; i < listData.size(); i++) { |
| WXCell child = getSourceTemplate(i); |
| if (child != null) { |
| totalHeight += child.getLayoutHeight(); |
| } |
| } |
| return totalHeight; |
| } |
| |
| private int calcContentOffset(RecyclerView recyclerView) { |
| RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); |
| if (layoutManager instanceof LinearLayoutManager) { |
| int firstVisibleItemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); |
| int offset = 0; |
| for (int i=0;i<firstVisibleItemPosition;i++) { |
| WXCell cell = getSourceTemplate(i); |
| if(cell == null){ |
| continue; |
| } |
| offset -= cell.getLayoutHeight(); |
| } |
| |
| if (layoutManager instanceof GridLayoutManager) { |
| int spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); |
| offset = offset / spanCount; |
| } |
| View firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition); |
| if(firstVisibleView != null) { |
| offset += firstVisibleView.getTop(); |
| } |
| return offset; |
| } else if (layoutManager instanceof StaggeredGridLayoutManager) { |
| int spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount(); |
| int firstVisibleItemPosition = ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null)[0]; |
| int offset = 0; |
| for (int i=0;i<firstVisibleItemPosition;i++) { |
| WXCell cell = getSourceTemplate(i); |
| if(cell == null){ |
| continue; |
| } |
| offset -= cell.getLayoutHeight(); |
| } |
| offset = offset / spanCount; |
| |
| View firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition); |
| if(firstVisibleView != null) { |
| offset += firstVisibleView.getTop(); |
| } |
| return offset; |
| } |
| return -1; |
| } |
| |
| /** |
| * find certain class type parent |
| * */ |
| public WXComponent findParentType(WXComponent component, Class type){ |
| if(component.getClass() == type){ |
| return component; |
| } |
| if(component.getParent() != null) { |
| findTypeParent(component.getParent(), type); |
| } |
| return null; |
| } |
| |
| |
| /** |
| * find child by ref |
| * */ |
| public WXComponent findChildByRef(WXComponent component, String ref){ |
| if(ref.equals(component.getRef())){ |
| return component; |
| } |
| if(component instanceof WXVContainer){ |
| WXVContainer container = (WXVContainer) component; |
| for(int i=0; i<container.getChildCount(); i++){ |
| WXComponent child = findChildByRef(container.getChild(i), ref); |
| if(child != null){ |
| return child; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * find child list, has same ref |
| * */ |
| public List<WXComponent> findChildListByRef(WXComponent component, String ref){ |
| WXComponent child = findChildByRef(component, ref); |
| if(child == null){ |
| return null; |
| } |
| List<WXComponent> componentList = new ArrayList<>(); |
| WXVContainer container = child.getParent(); |
| if(container != null && (!(container instanceof WXRecyclerTemplateList))){ |
| for(int i=0; i<container.getChildCount(); i++){ |
| WXComponent element = container.getChild(i); |
| if(ref.equals(element.getRef())){ |
| componentList.add(element); |
| } |
| } |
| }else{ |
| componentList.add(child); |
| } |
| return componentList; |
| } |
| |
| |
| /** |
| * copy cell async and save to cache |
| * */ |
| private void asyncLoadTemplateCache(final String template) { |
| if(Thread.currentThread() != Looper.getMainLooper().getThread()){ |
| if(listData == null || listData.size() == 0){ |
| return; |
| } |
| boolean firstScreenContains = false; |
| for(int i=0; i<listData.size(); i++){ |
| if(template.equals(getTemplateKey(i))){ |
| firstScreenContains = true; |
| break; |
| } |
| } |
| if(!firstScreenContains){ |
| return; |
| } |
| } |
| final WXCell source = mTemplateSources.get(template); |
| if(source == null){ |
| return; |
| } |
| TemplateCache cellCache = mTemplatesCache.get(template); |
| if(cellCache == null){ |
| cellCache = new TemplateCache(); |
| mTemplatesCache.put(template, cellCache); |
| } |
| if(cellCache.cells.size() > 0){ |
| cellCache.isLoadIng = false; |
| return; |
| } |
| if(cellCache.isLoadIng){ |
| return; |
| } |
| cellCache.isLoadIng = true; |
| AsyncTask<Void,Void, Void> preloadTask = new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... params) { |
| TemplateCache cellCache = mTemplatesCache.get(template); |
| if(cellCache == null || cellCache.cells == null){ |
| return null; |
| } |
| while (cellCache.cells.size() < templateCacheSize){ |
| WXCell component = (WXCell) copyCell(source); |
| if(component == null){ |
| return null; |
| } |
| if(source.getInstance() == null || source.getInstance().isDestroy()){ |
| return null; |
| } |
| cellCache.cells.add(component); |
| } |
| return null; |
| } |
| @Override |
| protected void onPostExecute(Void aVoid) { |
| if(source.getInstance() == null || source.getInstance().isDestroy()){ |
| return; |
| } |
| final TemplateCache cellCache = mTemplatesCache.get(template); |
| if(cellCache == null){ |
| return; |
| } |
| if(cellCache.cells == null |
| || cellCache.cells.size() == 0){ |
| cellCache.isLoadIng = false; |
| return; |
| } |
| Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { |
| @Override |
| public boolean queueIdle() { |
| if(source.getInstance() == null || source.getInstance().isDestroy()){ |
| return false; |
| } |
| ConcurrentLinkedQueue<WXCell> queue = cellCache.cells; |
| Iterator<WXCell> iterator = queue.iterator(); |
| while (iterator.hasNext()){ |
| WXCell component = iterator.next(); |
| if(component.isLazy()){ |
| doInitLazyCell(component, template, true); |
| return iterator.hasNext(); |
| } |
| } |
| return false; |
| } |
| }); |
| cellCache.isLoadIng = false; |
| } |
| }; |
| preloadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| } |
| |
| private static void doInitLazyCell(WXCell component, String template, boolean inPreload){ |
| if(component.isLazy()){ |
| long start = System.currentTimeMillis(); |
| component.lazy(false); |
| component.createView(); |
| if(WXEnvironment.isApkDebugable()) { |
| WXLogUtils.d(TAG, "doInitLazyCell idle" + inPreload + template + " createView used " + (System.currentTimeMillis() - start)); |
| } |
| component.applyLayoutAndEvent(component); |
| if(WXEnvironment.isApkDebugable()) { |
| WXLogUtils.d(TAG, "doInitLazyCell idle" + inPreload + template + " apply layout used " + (System.currentTimeMillis() - start)); |
| } |
| component.bindData(component); |
| if(WXEnvironment.isApkDebugable()) { |
| WXLogUtils.d(TAG, "doInitLazyCell idle" + inPreload + template + " bindData used " + (System.currentTimeMillis() - start)); |
| } |
| } |
| } |
| } |