content: /**
   * 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 org.apache.weex.ui.component.list.template;
  
  import android.annotation.TargetApi;
  import android.content.Context;
  import android.graphics.Point;
  import android.graphics.PointF;
  import android.os.Build;
  import android.os.Looper;
  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 org.apache.weex.WXEnvironment;
  import org.apache.weex.WXSDKInstance;
  import org.apache.weex.annotation.Component;
  import org.apache.weex.annotation.JSMethod;
  import org.apache.weex.bridge.JSCallback;
  import org.apache.weex.common.Constants;
  import org.apache.weex.common.ICheckBindingScroller;
  import org.apache.weex.common.OnWXScrollListener;
  import org.apache.weex.common.WXErrorCode;
  import org.apache.weex.common.WXThread;
  import org.apache.weex.dom.CSSShorthand;
  import org.apache.weex.dom.WXAttr;
  import org.apache.weex.dom.WXEvent;
  import org.apache.weex.el.parse.ArrayStack;
  import org.apache.weex.ui.action.BasicComponentData;
  import org.apache.weex.ui.component.AppearanceHelper;
  import org.apache.weex.ui.component.Scrollable;
  import org.apache.weex.ui.component.WXBaseRefresh;
  import org.apache.weex.ui.component.WXComponent;
  import org.apache.weex.ui.component.WXComponentProp;
  import org.apache.weex.ui.component.WXLoading;
  import org.apache.weex.ui.component.WXRefresh;
  import org.apache.weex.ui.component.WXVContainer;
  import org.apache.weex.ui.component.binding.Layouts;
  import org.apache.weex.ui.component.binding.Statements;
  import org.apache.weex.ui.component.helper.ScrollStartEndHelper;
  import org.apache.weex.ui.component.list.RecyclerTransform;
  import org.apache.weex.ui.component.list.WXCell;
  import org.apache.weex.ui.view.listview.WXRecyclerView;
  import org.apache.weex.ui.view.listview.adapter.IOnLoadMoreListener;
  import org.apache.weex.ui.view.listview.adapter.IRecyclerAdapterListener;
  import org.apache.weex.ui.view.listview.adapter.RecyclerViewBaseAdapter;
  import org.apache.weex.ui.view.listview.adapter.WXRecyclerViewOnScrollListener;
  import org.apache.weex.ui.view.refresh.wrapper.BounceRecyclerView;
  import org.apache.weex.utils.WXExceptionUtils;
  import org.apache.weex.utils.WXLogUtils;
  import org.apache.weex.utils.WXUtils;
  import org.apache.weex.utils.WXViewUtils;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  import org.apache.weex.common.Constants.Name;
  
  /**
   * 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 {
  
      /**
       * trace log for template list
       * */
      public static final boolean ENABLE_TRACE_LOG = false;
  
      public static final String TAG = "WXRecyclerTemplateList";
  
      private static final String EMPTY_HOLDER_TEMPLATE_KEY  = "";
      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";
  
      // TODO
  //    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 boolean mHasAddScrollEvent = false;
  
  
      private CellDataManager cellDataManager;
      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_CASE;
      private Runnable listUpdateRunnable;
      private ConcurrentHashMap<String, TemplateCache> mTemplatesCache;
      private int templateCacheSize = 2;
  
  
      /**
       * case default cell and key
       * */
      private WXCell defaultTemplateCell;
      private String defaultTemplateKey = "@default_template_cell";
  
      /**
       * scroll start and scroll end event
       * */
      private ScrollStartEndHelper mScrollStartEndHelper;
  
  
      /**
       * 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 CellRenderContext cellRenderContext = new CellRenderContext();
  
      private Runnable mAppearChangeRunnable = null;
      private static final long APPEAR_CHANGE_RUNNABLE_DELAY = 50;
  
      /**
       * has append tree done
       * */
      private boolean hasAppendTreeDone = false;
  
      /**
       * has layout done
       * */
      private boolean hasLayoutDone = false;
  
      public WXRecyclerTemplateList(
          WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) {
          super(instance, parent, basicComponentData);
          initRecyclerTemplateList(instance, basicComponentData, parent);
      }
  
      private void initRecyclerTemplateList(WXSDKInstance instance, BasicComponentData basicComponentData,
                                            WXVContainer parent){
  
          updateRecyclerAttr();
  
          mTemplateViewTypes = new ArrayMap<>();
          mTemplateViewTypes.put(EMPTY_HOLDER_TEMPLATE_KEY, 0); //empty view, when template was not sended
          mTemplateSources = new HashMap<>();
          mTemplatesCache = new ConcurrentHashMap<>();
          mStickyHelper = new TemplateStickyHelper(this);
          orientation =  basicComponentData.getAttrs().getOrientation();
          listDataTemplateKey = WXUtils.getString(getAttrs().get(Constants.Name.Recycler.LIST_DATA_TEMPLATE_SWITCH_KEY), Constants.Name.Recycler.SLOT_TEMPLATE_CASE);
          listDataItemKey = WXUtils.getString(getAttrs().get(Constants.Name.Recycler.LIST_DATA_ITEM), listDataItemKey);
          listDataIndexKey = WXUtils.getString(getAttrs().get(Constants.Name.Recycler.LIST_DATA_ITEM_INDEX), listDataIndexKey);
          cellDataManager = new CellDataManager(this);
          cellDataManager.listData = parseListDataToJSONArray(getAttrs().get(Constants.Name.Recycler.LIST_DATA));
          /**
           * we have separate cell with list, post add cell in dom thread ensure
           * list has layout and can archive better user experience and better load time,
           * which reduce waste cell layout when list layout changes.
           * */
          // TODO
  //        if(mDomObject != null
  //                && mDomObject.getCellList() != null
  //                && mDomObject.getCellList().size() > 0){
  //            Runnable runnable =  new Runnable() {
  //                // @Override
  //                public void run() {
  //                    if(isDestoryed()){
  //                        return;
  //                    }
  //                    long start = System.currentTimeMillis();
  //                    if(mDomObject != null && mDomObject.getCellList() != null){
  //                        for(int i=0; i<mDomObject.getCellList().size(); i++){
  //                            addChild(ComponentUtils.buildTree(mDomObject.getCellList().get(i),  WXRecyclerTemplateList.this));
  //                        }
  //                    }
  //                    if(WXEnvironment.isOpenDebugLog() && ENABLE_TRACE_LOG){
  //                        WXLogUtils.d(TAG, "TemplateList BuildDomTree Used " + (System.currentTimeMillis() - start));
  //                    }
  //                }
  //            };
  //            WXSDKManager.getInstance().getWXDomManager().post(runnable);
  //        }
      }
  
      @Override
      protected BounceRecyclerView initComponentHostView(@NonNull Context context) {
          final BounceRecyclerView bounceRecyclerView = new BounceRecyclerView(context,mLayoutType,mColumnCount,mColumnGap, getOrientation());
          WXAttr attrs = 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(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);
          }
          if(bounceRecyclerView.getSwipeLayout()  != null){
              if(WXUtils.getBoolean(getAttrs().get("nestedScrollingEnabled"), false)) {
                  bounceRecyclerView.getSwipeLayout().setNestedScrollingEnabled(true);
              }
          }
          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);
                  getScrollStartEndHelper().onScrollStateChanged(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){
                      if(mStickyHelper.getStickyTypes().size() > 0){
                          mStickyHelper.getStickyPositions().clear();
                          if(cellDataManager.listData != null){
                              for(int i = 0; i< cellDataManager.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.isOpenDebugLog() && ENABLE_TRACE_LOG){
                      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(width, outHeight);
      }
  
  
      @Override
      public void bindStickStyle(WXComponent component) {
          WXComponent template = findParentType(component, WXCell.class);
          if(template == null){
              return;
          }
          if(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
                  || 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(cellDataManager.listData == null
                  || mAppearHelpers == null
                  || TextUtils.isEmpty(component.getRef())){
              return;
          }
          WXCell cell = findCell(component);
          int type = getCellTemplateItemType(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);
          if(mAppearChangeRunnable == null){
              mAppearChangeRunnable =  new Runnable() {
                  @Override
                  public void run() {
                      if(mAppearChangeRunnable != null) {
                          notifyAppearStateChange(0, 0, 0, 0);
                      }
                  }
              };
          }
          if (getHostView() != null) {
              getHostView().removeCallbacks(mAppearChangeRunnable);
              getHostView().postDelayed(mAppearChangeRunnable, APPEAR_CHANGE_RUNNABLE_DELAY);
          }
      }
  
      @Override
      public void bindDisappearEvent(WXComponent component) {
          setAppearanceWatch(component, AppearanceHelper.DISAPPEAR, true);
          if(mAppearChangeRunnable == null){
              mAppearChangeRunnable =  new Runnable() {
                  @Override
                  public void run() {
                      if(mAppearChangeRunnable != null) {
                          notifyAppearStateChange(0, 0, 0, 0);
                      }
                  }
              };
          }
          if (getHostView() != null) {
              getHostView().removeCallbacks(mAppearChangeRunnable);
              getHostView().postDelayed(mAppearChangeRunnable, APPEAR_CHANGE_RUNNABLE_DELAY);
          }
      }
  
      @Override
      public void unbindAppearEvent(WXComponent component) {
          setAppearanceWatch(component, AppearanceHelper.APPEAR, false);
      }
  
      @Override
      public void unbindDisappearEvent(WXComponent component) {
          setAppearanceWatch(component, AppearanceHelper.DISAPPEAR, false);
      }
  
  
      @JSMethod(uiThread = true)
      public void queryElement(String virtualRef, String selector, JSCallback callback){
          try{
              String[]  segments = virtualRef.split(TemplateDom.SEPARATOR + "");
              String listRef = segments[0];
              int position = Integer.parseInt(segments[1]); // position
              WXComponent component = TemplateDom.findVirtualComponentByVRef(getInstanceId(), virtualRef);
              if(component == null){
                  return;
              }
              if(getHostView() == null || getHostView().getInnerView() == null){
                  return;
              }
              List<WXComponent>  componentList  = new ArrayList<>(4);
              Selector.queryElementAll(component, selector, componentList);
              if(componentList.size() > 0){
                  callback.invoke(TemplateDom.toMap(listRef, position, componentList.get(0)));
              }else{
                  callback.invoke(new HashMap<>(4));
              }
          }catch (Exception e){
              callback.invoke(new HashMap<>(4));
              WXLogUtils.e(TAG, e);
          }
      }
  
      @JSMethod(uiThread = true)
      public void queryElementAll(String virtualRef,  String selector, JSCallback callback){
          List datas = new ArrayList();
          try{
              String[]  segments = virtualRef.split(TemplateDom.SEPARATOR + "");
              String listRef = segments[0];
              int position = Integer.parseInt(segments[1]); // position
              WXComponent component = TemplateDom.findVirtualComponentByVRef(getInstanceId(), virtualRef);
              if(component == null){
                  return;
              }
              if(getHostView() == null || getHostView().getInnerView() == null){
                  return;
              }
              List<WXComponent>  componentList  = new ArrayList<>(4);
              Selector.queryElementAll(component, selector, componentList);
              for(WXComponent child : componentList){
                  datas.add(TemplateDom.toMap(listRef, position, child));
              }
              callback.invoke(datas);
          }catch (Exception e){
              callback.invoke(datas);
              WXLogUtils.e(TAG, e);
          }
      }
  
      @JSMethod(uiThread = true)
      public void closest(String virtualRef,  String selector, JSCallback callback){
          try{
              String[]  segments = virtualRef.split(TemplateDom.SEPARATOR + "");
              String listRef = segments[0];
              int position = Integer.parseInt(segments[1]); // position
              WXComponent component = TemplateDom.findVirtualComponentByVRef(getInstanceId(), virtualRef);
              if(component == null){
                  return;
              }
              if(getHostView() == null || getHostView().getInnerView() == null){
                  return;
              }
              List<WXComponent>  componentList  = new ArrayList<>(4);
              Selector.closest(component, selector, componentList);
              if(componentList.size() > 0){
                  callback.invoke(TemplateDom.toMap(listRef, position, componentList.get(0)));
              }else{
                  callback.invoke(new HashMap<>(4));
              }
          }catch (Exception e){
              callback.invoke(new HashMap<>(4));
              WXLogUtils.e(TAG, e);
          }
      }
  
  
  
      @JSMethod(uiThread = true)
      public void scrollToElement(String virtualRef, Map<String, Object> options){
          scrollTo(virtualRef, options);
      }
  
  
      @JSMethod(uiThread = true)
      public void scrollTo(String virtualRef, Map<String, Object> options){
          int position = -1;
          try{
              if(virtualRef.indexOf(TemplateDom.SEPARATOR) > 0){
                  String[]  segments = virtualRef.split(TemplateDom.SEPARATOR + "");
                  position = Integer.parseInt(segments[0]);
              }else{
                  position = (int) Float.parseFloat(virtualRef);
              }
              if (position >= 0) {
                  boolean smooth = true;
                  float offsetFloat = 0;
                  if(options != null) {
                      smooth = WXUtils.getBoolean(options.get(Constants.Name.ANIMATED), true);
                      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());
                          }
                      }
                  }
                  final int offset = (int) offsetFloat;
  
                  final int pos = position;
                  BounceRecyclerView bounceRecyclerView = getHostView();
                  if (bounceRecyclerView == null) {
                      return;
                  }
                  final WXRecyclerView view = bounceRecyclerView.getInnerView();
                  view.scrollTo(smooth, pos, offset, getOrientation());
              }
          }catch (Exception e){
              WXLogUtils.e(TAG, e);
          }
      }
  
  
      @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(cellDataManager.listData != null && component.getRef() != null){
                  int typePosition = 0;
                  for(int i = 0; i< cellDataManager.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 = cellDataManager.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) {
          /**
           *  dom object in component is not tree, build tree
           * */
          if(!(child instanceof WXCell)) {
              super.addChild(child, index);
          }
          if(child instanceof WXBaseRefresh){
              return;
          }
          if(child instanceof WXCell){
              if(child.getAttrs() != null){
                  Object templateId = child.getAttrs().get(Constants.Name.Recycler.SLOT_TEMPLATE_CASE);
                  String key = WXUtils.getString(templateId, null);
                  if(getAttrs().containsKey(Constants.Name.Recycler.LIST_DATA_TEMPLATE_SWITCH_KEY)){
                      if(defaultTemplateCell == null){
                          defaultTemplateCell = (WXCell) child;
                          if(!TextUtils.isEmpty(key)){
                              defaultTemplateKey = key;
                          }else{
                              key = defaultTemplateKey;
                              child.getAttrs().put(Constants.Name.Recycler.SLOT_TEMPLATE_CASE, key);
                          }
                      }
                  }else{
                      if(defaultTemplateCell == null
                              || child.getAttrs().containsKey(Constants.Name.Recycler.SLOT_TEMPLATE_DEFAULT)){
                          defaultTemplateCell = (WXCell) child;
                          if(!TextUtils.isEmpty(key)){
                              defaultTemplateKey = key;
                          }else{
                              key = defaultTemplateKey;
                              child.getAttrs().put(Constants.Name.Recycler.SLOT_TEMPLATE_CASE, key);
                          }
                      }
                  }
                  if(key != null){
                      // TODO
  //                    if(child.getDomObject() instanceof  WXCellDomObject
  //                            && getDomObject() instanceof  WXRecyclerDomObject){
  //                        WXCellDomObject domObject = (WXCellDomObject) child.getDomObject();
  //                        domObject.setRecyclerDomObject((WXRecyclerDomObject) getDomObject());
  //                    }
                      mTemplateSources.put(key, (WXCell) child);
                      if(mTemplateViewTypes.get(key) == null){
                          mTemplateViewTypes.put(key, mTemplateViewTypes.size());
                      }
                  }
              }
  
              ((WXCell) child).setCellAppendTreeListener(new WXCell.CellAppendTreeListener() {
                  @Override
                  public void onAppendTreeDone() {
                      checkAppendDone(false);
                  }
              });
          }
      }
  
  
      /**
       * check all the cell has append tree done, then show list
       * */
      private void  checkAppendDone(boolean listDone){
          if(mTemplateSources.size() == 0){
              return;
          }
          Set<Map.Entry<String,WXCell>> cells = mTemplateSources.entrySet();
          for(Map.Entry<String,WXCell> entry : cells){
              if(!entry.getValue().isAppendTreeDone()){
                  return;
              }
          }
          hasAppendTreeDone = true;
          if(hasLayoutDone) {
              notifyUpdateList();
          }
      }
  
      @Override
      protected void setHostLayoutParams(BounceRecyclerView host, int width, int height, int left, int right, int top, int bottom) {
          super.setHostLayoutParams(host, width, height, left, right, top, bottom);
          if(!hasLayoutDone){
              hasLayoutDone = true;
              hasAppendTreeDone = true;
              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
       * 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_SWITCH_KEY:
              case Constants.Name.Recycler.SLOT_TEMPLATE_CASE:
                  listDataTemplateKey = WXUtils.getString(param, Constants.Name.Recycler.SLOT_TEMPLATE_CASE);
                  return true;
              case Name.LOADMOREOFFSET:
                  return true;
              case Constants.Name.SCROLLABLE:
                  boolean scrollable = WXUtils.getBoolean(param, true);
                  setScrollable(scrollable);
                  return true;
              case Constants.Name.SCROLL_DIRECTION:
                  if(param != null) {
                      setScrollDirection(param.toString());
                  }
                  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(){
          mLayoutType = getAttrs().getLayoutType();
          mColumnCount = getAttrs().getColumnCount();
          if (mColumnCount <= 0 && mLayoutType != WXRecyclerView.TYPE_LINEAR_LAYOUT) {
              Map<String, String> ext = new ArrayMap<>();
              ext.put("componentType", getComponentType());
              ext.put("attribute", getAttrs().toString());
              ext.put("stackTrace", Arrays.toString(Thread.currentThread().getStackTrace()));
              WXExceptionUtils.commitCriticalExceptionRT(getInstanceId(),
                  WXErrorCode.WX_RENDER_ERR_LIST_INVALID_COLUMN_COUNT, "columnCount",
                  String.format(Locale.ENGLISH,
                      "You are trying to set the list/recycler/vlist/waterfall's column to %d, which is illegal. The column count should be a positive integer",
                      mColumnCount),
                  ext);
              mColumnCount = Constants.Value.COLUMN_COUNT_NORMAL;
          }
          mColumnGap = getAttrs().getColumnGap();
          mColumnWidth = getAttrs().getColumnWidth();
          mPaddingLeft = getPadding().get(CSSShorthand.EDGE.LEFT);
          mPaddingRight = getPadding().get(CSSShorthand.EDGE.RIGHT);
  
      }
  
  
      @WXComponentProp(name = Constants.Name.SCROLL_DIRECTION)
      public void setScrollDirection(String direction){
          if(orientation != getAttrs().getOrientation()) {
              orientation = getAttrs().getOrientation();
              updateRecyclerAttr();
              WXRecyclerView wxRecyclerView = getHostView().getInnerView();
              wxRecyclerView.initView(getContext(), mLayoutType,mColumnCount,mColumnGap, getOrientation());
  
          }
      }
  
      @WXComponentProp(name = Constants.Name.COLUMN_WIDTH)
      public void setColumnWidth(int columnCount)  {
          if(getAttrs().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(getAttrs().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(getAttrs().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){
          param = parseListDataToJSONArray(param);
          boolean update = cellDataManager.listData != param;
          if(param instanceof  JSONArray){
              if(update){
                  cellDataManager.setListData((JSONArray) param);
                  notifyUpdateList();
              }
          }
      }
  
      @JSMethod
      public void  appendData(JSONArray data){
          if(data == null || data.size() == 0){
              return;
          }
          if(cellDataManager.listData == null){
              cellDataManager.listData = new JSONArray();
          }
          int position = cellDataManager.listData.size();
          if(position < 0){
              position = 0;
          }
          if(data instanceof  JSONArray){
              cellDataManager.listData.addAll(data);
          }
          getHostView().getRecyclerViewBaseAdapter().notifyItemRangeInserted(position, data.size());
      }
  
      @JSMethod
      public void  insertData(int index, Object data){
          if(data == null){
              return;
          }
  
          if(cellDataManager.listData == null || index > cellDataManager.listData.size()){
              return;
          }
          boolean renderStateChanged = cellDataManager.insertData(index, data);
          if(renderStateChanged){
              notifyUpdateList();
          }else{
              getHostView().getRecyclerViewBaseAdapter().notifyItemInserted(index);
          }
      }
  
      @JSMethod
      public void  appendRange(int index, JSONArray data){
           insertRange(index, data);
      }
  
  
      /**
       * when update data, list maybe contains sticky, may use position, when position changed should be rendered
       * so use notifyUpdateList is better
       * */
  
      @JSMethod
      public void  insertRange(int index, JSONArray data){
          if(data == null || data.size() == 0){
              return;
          }
          if(cellDataManager.listData == null || index > cellDataManager.listData.size()){
              return;
          }
          boolean renderStateChange = cellDataManager.insertRange(index, data);
          if(renderStateChange){
              notifyUpdateList();
          }else{
              getHostView().getRecyclerViewBaseAdapter().notifyItemRangeInserted(index, data.size());
          }
  
      }
  
      @JSMethod
      public void  updateData(int index, Object data){
          if(data == null){
              return;
          }
          if(cellDataManager.listData == null || index >= cellDataManager.listData.size()){
              return;
          }
          boolean onlyDataChange = cellDataManager.updateData(data, index);
          if(onlyDataChange) {
              getHostView().getRecyclerViewBaseAdapter().notifyItemChanged(index, data);
          }else{
              notifyUpdateList();
          }
      }
  
      @JSMethod
      public void  removeData(int index, int count){
          if(cellDataManager.listData == null
                  || index >= cellDataManager.listData.size()){
              return;
          }
          if(count <= 0){
              count = 1;
          }
          int removeCount = 0;
          while (count > 0 && index < cellDataManager.listData.size()){
              cellDataManager.removeData(index);
              count--;
              removeCount++;
          }
          if(removeCount > 0) {
              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 != getPadding().get(CSSShorthand.EDGE.LEFT)
                      || mPaddingRight != getPadding().get(CSSShorthand.EDGE.RIGHT)) {
                  updateRecyclerAttr();
                  WXRecyclerView wxRecyclerView = getHostView().getInnerView();
                  wxRecyclerView.initView(getContext(), mLayoutType, mColumnCount, mColumnGap, getOrientation());
              }
          }
      }
  
  
  
      @Override
      public void addEvent(String type) {
          super.addEvent(type);
          if (ScrollStartEndHelper.isScrollEvent(type)
                  && getHostView() != null
                  && getHostView().getInnerView() != null
                  && !mHasAddScrollEvent) {
              mHasAddScrollEvent = true;
              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;
                      }
                      getScrollStartEndHelper().onScrolled(offsetX, offsetY);
                      if(!getEvents().contains(Constants.Event.SCROLL)){
                          return;
                      }
                      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) {
          fireEvent(Constants.Event.SCROLL, getScrollEvent(recyclerView, offsetX, offsetY));
      }
  
      public Map<String, Object> getScrollEvent(RecyclerView recyclerView, int offsetX, int offsetY){
          offsetY = -calcContentOffset(recyclerView);
          int contentWidth = recyclerView.getMeasuredWidth() + recyclerView.computeHorizontalScrollRange();
          int contentHeight = calcContentSize();
  
          Map<String, Object> event = new HashMap<>(3);
          Map<String, Object> contentSize = new HashMap<>(3);
          Map<String, Object> contentOffset = new HashMap<>(3);
  
          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);
          event.put(Constants.Name.ISDRAGGING, recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING);
          return 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(WXThread.secure(new Runnable() {
                  @Override
                  public void run() {
                      getHostView().setHeaderView(child);
                  }
              }), 100);
              return true;
          }
  
          if (child instanceof WXLoading && getHostView() != null) {
              getHostView().setOnLoadingListener((WXLoading) child);
              getHostView().postDelayed(WXThread.secure(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;
  
              this.setMarginsSupportRTL(params, left, 0, right, 0);
          }
          return params;
      }
  
  
      @Override
      public void destroy() {
          synchronized (this){
              if(getHostView() != null){
                  if(mAppearChangeRunnable != null) {
                      getHostView().removeCallbacks(mAppearChangeRunnable);
                      mAppearChangeRunnable = null;
                  }
                  getHostView().removeCallbacks(listUpdateRunnable);
                  if(getHostView().getInnerView() != null){
                      getHostView().getInnerView().setAdapter(null);
                  }
              }
              if(cellDataManager.listData != null){
                  cellDataManager.setListData(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;
          }
          if(templateViewHolder.getHolderPosition() >= 0){
              fireEvent(TemplateDom.DETACH_CELL_SLOT, TemplateDom.findAllComponentRefs(getRef(), position, component));
          }
          long start = System.currentTimeMillis();
          templateViewHolder.setHolderPosition(position);
          Object data = cellDataManager.listData.get(position);
          CellRenderState cellRenderState = cellDataManager.getRenderState(position);
          if((component.getRenderData() == data && (cellRenderState == null || !cellRenderState.isDirty()))){
              if(WXEnvironment.isOpenDebugLog() && ENABLE_TRACE_LOG){
                  WXLogUtils.d(TAG,  position + " position "+ getTemplateKey(position) + " onBindViewHolder none data update "
                          + " component " + component.hashCode());
              }
              fireEvent(TemplateDom.ATTACH_CELL_SLOT, TemplateDom.findAllComponentRefs(getRef(), position, component));
              return;  //none update just return
          }else{
              List<WXComponent> updates = doRenderTemplate(component, position);
              Statements.doInitCompontent(updates);
              component.setRenderData(data);
              Layouts.doLayoutAsync(templateViewHolder, true);
              if(WXEnvironment.isOpenDebugLog() && ENABLE_TRACE_LOG){
                  WXLogUtils.d(TAG,  position + " position "+ getTemplateKey(position) + " onBindViewHolder used " + (System.currentTimeMillis() - start)
                    + " component " + component.hashCode());
              }
          }
      }
  
      @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(this, view, viewType);
          }
          WXCell component =  getCellTemplateFromCache(template);
          boolean cacheHit = true;
          if(component == null){
              cacheHit = false;
              if(!source.isSourceUsed()){
                  source.setSourceUsed(true);
                  renderTemplateCellWithData(source);
                  component = source;
                  if(WXEnvironment.isOpenDebugLog() && ENABLE_TRACE_LOG) {
                      WXLogUtils.d(TAG, template + " onCreateViewHolder source");
                  }
              }
          }
          if(component == null) {
              long start = System.currentTimeMillis();
              component = (WXCell) copyComponentFromSourceCell(source);
              if(WXEnvironment.isOpenDebugLog() && ENABLE_TRACE_LOG) {
                  WXLogUtils.d(TAG, template + " onCreateViewHolder copy used " + (System.currentTimeMillis() - start));
              }
          }
          if(component.isLazy() || component.getHostView() == null) {
              doCreateCellViewBindData(component, template, false);
              if(WXEnvironment.isOpenDebugLog() && ENABLE_TRACE_LOG) {
                  WXLogUtils.d(TAG, template + " onCreateViewHolder  cache hit " + cacheHit   + " view not idle init");
              }
          }else{
              if(WXEnvironment.isOpenDebugLog() && ENABLE_TRACE_LOG) {
                  WXLogUtils.d(TAG, template + " onCreateViewHolder  cache hit " + cacheHit + " view idle init " + component.hashCode()
                   + "  " + source.hashCode());
              }
          }
          TemplateViewHolder templateViewHolder = new TemplateViewHolder(this, component, viewType);
          return  templateViewHolder;
      }
  
  
  
      /**
       * @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) {
          String template = getTemplateKey(position);
          int type =  mTemplateViewTypes.indexOfKey(template);
          if(type < 0){
              type = mTemplateViewTypes.indexOfKey(EMPTY_HOLDER_TEMPLATE_KEY);
          }
          return type;
      }
  
  
      /**
       * create code context for render component and do render
       * */
      private List<WXComponent> doRenderTemplate(WXCell cell, int position){
          this.cellRenderContext.clear();
          Object item = cellDataManager.listData.get(position);
          CellRenderState cellRenderState = cellDataManager.getRenderState(position);
          cellRenderContext.renderState = cellRenderState;
          cellRenderContext.templateList = this;
          cellRenderContext.position = position;
  
          ArrayStack stack = cellRenderContext.stack;
          Map map = cellRenderContext.map;
          if(cellDataManager.listData != null){
              stack.push(map);
              map.put(listDataKey, cellDataManager.listData);
              if(!TextUtils.isEmpty(listDataIndexKey)) {
                  map.put(listDataIndexKey, new PositionRef(cellRenderState));
              }
              if(!TextUtils.isEmpty(listDataItemKey)) {
                  map.put(listDataItemKey, item);
              }else{
                  stack.push(item);
              }
          }
          if(cellRenderState.itemId <= 0){
              getItemId(position);
          }
          List<WXComponent> updates = Statements.doRender(cell, this.cellRenderContext);
          if(cellRenderState.isDirty()){
              cellRenderState.resetDirty();
          }
          return  updates;
      }
  
      public ArrayStack copyStack(CellRenderContext context, ArrayStack stack){
          ArrayStack onceStack = new ArrayStack();
          for(int index=0;  index <  stack.size(); index++) {
              Object value = stack.get(index);
              if(value instanceof  Map){
                  value  = new HashMap((Map) value);
              }
              onceStack.push(value);
          }
          return onceStack;
      }
  
  
      /**
       * return tepmlate key for position
       * */
      public String getTemplateKey(int position){
          Object data =  safeGetListData(position);
          return getTemplateKey(data);
      }
  
      /**
       * return template key for position never be null
       * */
      public String getTemplateKey(Object data){
          String template = null;
          if(data instanceof  JSONObject) {
              template = ((JSONObject)data).getString(listDataTemplateKey);
          }
          if(TextUtils.isEmpty(template)){
              if(defaultTemplateCell != null){
                  template  = defaultTemplateKey;
              }else {
                  template = "";
              }
          }
          return  template;
      }
  
      /**
       * get source template
       * */
      public WXCell getSourceTemplate(int position){
          String template = getTemplateKey(position);
          return mTemplateSources.get(template);
      }
  
  
  
      /**
       * get template key from cell; -1 for  cann't find  type
       * */
      private int getCellTemplateItemType(WXCell cell){
          if(cell == null){
              return  -1;
          }
          if(cell.getAttrs() != null){
              Object templateId = cell.getAttrs().get(Constants.Name.Recycler.SLOT_TEMPLATE_CASE);
              String template = WXUtils.getString(templateId, null);
              if(cell == defaultTemplateCell){
                  template = defaultTemplateKey;
              }
              int type =  mTemplateViewTypes.indexOfKey(template);
              if(type < 0){
                  return -1;
              }
              return  type;
          }
          return  0;
      }
  
      @Override
      public int getItemCount() {
          if(!hasLayoutDone){
              return 0;
          }
          if(!hasAppendTreeDone){
              return 0;
          }
          if(cellDataManager.listData == null){
              return  0;
          }
          if(mTemplateViewTypes == null || mTemplateViewTypes.size() <= 1){
              return 0;
          }
          if(mTemplateSources == null || mTemplateSources.size() == 0){
              return  0;
          }
          return cellDataManager.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) {
          CellRenderState renderState = cellDataManager.getRenderState(position);
          if(renderState.itemId <=  0){
              String template = getTemplateKey(position);
              if(TextUtils.isEmpty(template)){
                  return RecyclerView.NO_ID;
              }
              Object data = safeGetListData(position);
              if(data instanceof  JSONObject && ((JSONObject)data).containsKey("keyItemId")){
                  renderState.itemId =  ((JSONObject)data).getLongValue("keyItemId");
              }else{
                  long id = Math.abs(data.hashCode());
                  long itemId = (id<< 24) + position;
                  renderState.itemId = itemId;
              }
          }
          return renderState.itemId;
      }
  
      @Override
      public void onBeforeScroll(int dx, int dy) {
          if(mStickyHelper != null){
              mStickyHelper.onBeforeScroll(dx, dy);
          }
      }
  
      @Override
      public void onLoadMore(int offScreenY) {
          try {
              String offset = getAttrs().getLoadMoreOffset();
  
              if (TextUtils.isEmpty(offset)) {
                  offset = "0";
              }
              float offsetParsed = WXViewUtils.getRealPxByWidth(Integer.parseInt(offset),getInstance().getInstanceViewPortWidth());
  
              if (offScreenY <= offsetParsed && cellDataManager.listData != null) {
                  if (mListCellCount != cellDataManager.listData.size()
                          || mForceLoadmoreNextTime) {
                      fireEvent(Constants.Event.LOADMORE);
                      mListCellCount = cellDataManager.listData.size();
                      mForceLoadmoreNextTime = false;
                  }
              }
          } catch (Exception e) {
              if (WXEnvironment.isApkDebugable()){
                  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;
          }
          if(mAppearChangeRunnable != null) {
              getHostView().removeCallbacks(mAppearChangeRunnable);
              mAppearChangeRunnable = null;
          }
          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.notifyAppearStateChange(Constants.Event.APPEAR, direction);
                              List<Object> eventArgs = null;
                              if(childLisener.getEvents() != null
                                      && childLisener.getEvents().getEventBindingArgsValues() != null
                                      && childLisener.getEvents().getEventBindingArgsValues().get(Constants.Event.DISAPPEAR) != null){
                                  eventArgs = childLisener.getEvents().getEventBindingArgsValues().get(Constants.Event.DISAPPEAR);
                              }
                              componentDisAppearList.put(key, eventArgs);
                          }
                      }else{
                          if(componentDisAppearList.containsKey(key)){
                              childLisener.notifyAppearStateChange(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.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.notifyAppearStateChange(Constants.Event.DISAPPEAR, direction);
                  }
                  eventWatchers.clear();
              }
              mDisAppearWatchList.remove(position);
          }
      }
  
  
      private Object safeGetListData(int position){
          try{
              return cellDataManager.listData.get(position);
          }catch (Exception e){return  JSONObject.parseObject("{}");}
      }
  
      public 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(cellDataManager.listData == null){
              return totalHeight;
          }
          for (int i = 0; i < cellDataManager.listData.size(); i++) {
              WXCell child = getSourceTemplate(i);
              if (child != null) {
                  totalHeight += child.getLayoutHeight();
              }
          }
          return totalHeight;
      }
  
      public 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(type.isAssignableFrom(component.getClass())){
              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;
      }
  
  
  
      /**
       * find child by ref
       * */
      public WXComponent findChildByAttrsRef(WXComponent component, String ref){
          if(component.getAttrs() != null && ref.equals(component.getAttrs().get(TemplateDom.ATTRS_KEY_REF))){
              return component;
          }
          if(component instanceof WXVContainer){
              WXVContainer container = (WXVContainer) component;
              for(int i=0; i<container.getChildCount(); i++){
                  WXComponent child = findChildByAttrsRef(container.getChild(i), ref);
                  if(child != null){
                      return  child;
                  }
              }
          }
          return  null;
      }
  
  
  
      /**
       * @param  template  template name
       * get cell template component from cache, if cell component not load
       * start load cell template
       * */
      private WXCell getCellTemplateFromCache(final String template){
          TemplateCache cache = mTemplatesCache.get(template);
          WXCell component =  null;
          if(cache != null && cache.cells != null && cache.cells.size() > 0){
              component = cache.cells.poll();
          }
          if(cache == null ||  !cache.isLoadIng){
              if(cache == null){
                  cache = new TemplateCache();
                  mTemplatesCache.put(template, cache);
              }
              cache.isLoadIng = true;
              WXCell source = mTemplateSources.get(template);
              if(source != null){
                  boolean allowPreload = WXUtils.getBoolean(source.getAttrs().get("preload"), true);
                  if(allowPreload) {
                      AsyncCellLoadTask asyncCellLoadTask = new AsyncCellLoadTask(template, source, this);
                      asyncCellLoadTask.startTask();
                  }
              }
          }
          return  component;
      }
  
      /**
       * copy cell component from source, init render data, and return source
       * if none data, return null
       * */
      public WXComponent copyComponentFromSourceCell(WXCell cell){
          renderTemplateCellWithData(cell);
          WXCell component = (WXCell) Statements.copyComponentTree(cell);
          return component;
      }
  
      /**
       *  render  init with  cell with one data,
       *  if template has already render with data, done nothing
       *  @param  cell
       * */
      private synchronized void renderTemplateCellWithData(WXCell cell){
          if(cell.getRenderData() == null){
              if(cellDataManager.listData != null && cellDataManager.listData.size() > 0){
                  synchronized (this){
                      if(cell.getRenderData() == null){
                          Statements.parseStatementsToken(cell);
                          for(int i = 0; i< cellDataManager.listData.size(); i++){
                              if(cell == getSourceTemplate(i)){
                                  Object data = cellDataManager.listData.get(i);
                                  doRenderTemplate(cell, i);
                                  Layouts.doLayoutSync(cell, getLayoutWidth(), getLayoutHeight());
                                  cell.setRenderData(data);
                                  break;
                              }
                          }
                      }
                  }
              }
          }
      }
  
  
      /**
       * create view for lazy cell and bind data
       * */
      public static void doCreateCellViewBindData(WXCell component, String template, boolean inPreload){
          if(component.isLazy() || component.getHostView() == null){
              long start = System.currentTimeMillis();
              Statements.initLazyComponent(component, null);
              if(WXEnvironment.isOpenDebugLog() && ENABLE_TRACE_LOG) {
                  WXLogUtils.d(TAG, " doCreateCellViewBindData " + template + " in preload "+ inPreload + " used " + (System.currentTimeMillis() - start));
              }
          }
      }
  
      public ScrollStartEndHelper getScrollStartEndHelper() {
          if(mScrollStartEndHelper == null){
              mScrollStartEndHelper = new ScrollStartEndHelper(this);
          }
          return mScrollStartEndHelper;
      }
  
      public int getTemplateCacheSize() {
          return templateCacheSize;
      }
  
      public ConcurrentHashMap<String, TemplateCache> getTemplatesCache() {
          if(mTemplatesCache == null){
              mTemplatesCache = new ConcurrentHashMap<>();
          }
          return mTemplatesCache;
      }
  
  
      public CellDataManager getCellDataManager() {
          return cellDataManager;
      }
  
  
      private JSONArray parseListDataToJSONArray(Object value){
          try{
              if(value instanceof  JSONArray){
                  return (JSONArray) value;
              }
              if(value instanceof String){
                  JSONArray array = JSONArray.parseArray(getAttrs().get(Constants.Name.Recycler.LIST_DATA).toString());
                  return array;
              }
          }catch (Exception e){
              WXLogUtils.e(TAG, "parseListDataException" + e.getMessage());
          }
          return new JSONArray();
      }
  }
  