Java tutorial
/** * * Apache License * Version 2.0, January 2004 * http://www.apache.org/licenses/ * * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION * * 1. Definitions. * * "License" shall mean the terms and conditions for use, reproduction, * and distribution as defined by Sections 1 through 9 of this document. * * "Licensor" shall mean the copyright owner or entity authorized by * the copyright owner that is granting the License. * * "Legal Entity" shall mean the union of the acting entity and all * other entities that control, are controlled by, or are under common * control with that entity. For the purposes of this definition, * "control" means (i) the power, direct or indirect, to cause the * direction or management of such entity, whether by contract or * otherwise, or (ii) ownership of fifty percent (50%) or more of the * outstanding shares, or (iii) beneficial ownership of such entity. * * "You" (or "Your") shall mean an individual or Legal Entity * exercising permissions granted by this License. * * "Source" form shall mean the preferred form for making modifications, * including but not limited to software source code, documentation * source, and configuration files. * * "Object" form shall mean any form resulting from mechanical * transformation or translation of a Source form, including but * not limited to compiled object code, generated documentation, * and conversions to other media types. * * "Work" shall mean the work of authorship, whether in Source or * Object form, made available under the License, as indicated by a * copyright notice that is included in or attached to the work * (an example is provided in the Appendix below). * * "Derivative Works" shall mean any work, whether in Source or Object * form, that is based on (or derived from) the Work and for which the * editorial revisions, annotations, elaborations, or other modifications * represent, as a whole, an original work of authorship. For the purposes * of this License, Derivative Works shall not include works that remain * separable from, or merely link (or bind by name) to the interfaces of, * the Work and Derivative Works thereof. * * "Contribution" shall mean any work of authorship, including * the original version of the Work and any modifications or additions * to that Work or Derivative Works thereof, that is intentionally * submitted to Licensor for inclusion in the Work by the copyright owner * or by an individual or Legal Entity authorized to submit on behalf of * the copyright owner. For the purposes of this definition, "submitted" * means any form of electronic, verbal, or written communication sent * to the Licensor or its representatives, including but not limited to * communication on electronic mailing lists, source code control systems, * and issue tracking systems that are managed by, or on behalf of, the * Licensor for the purpose of discussing and improving the Work, but * excluding communication that is conspicuously marked or otherwise * designated in writing by the copyright owner as "Not a Contribution." * * "Contributor" shall mean Licensor and any individual or Legal Entity * on behalf of whom a Contribution has been received by Licensor and * subsequently incorporated within the Work. * * 2. Grant of Copyright License. Subject to the terms and conditions of * this License, each Contributor hereby grants to You a perpetual, * worldwide, non-exclusive, no-charge, royalty-free, irrevocable * copyright license to reproduce, prepare Derivative Works of, * publicly display, publicly perform, sublicense, and distribute the * Work and such Derivative Works in Source or Object form. * * 3. Grant of Patent License. Subject to the terms and conditions of * this License, each Contributor hereby grants to You a perpetual, * worldwide, non-exclusive, no-charge, royalty-free, irrevocable * (except as stated in this section) patent license to make, have made, * use, offer to sell, sell, import, and otherwise transfer the Work, * where such license applies only to those patent claims licensable * by such Contributor that are necessarily infringed by their * Contribution(s) alone or by combination of their Contribution(s) * with the Work to which such Contribution(s) was submitted. If You * institute patent litigation against any entity (including a * cross-claim or counterclaim in a lawsuit) alleging that the Work * or a Contribution incorporated within the Work constitutes direct * or contributory patent infringement, then any patent licenses * granted to You under this License for that Work shall terminate * as of the date such litigation is filed. * * 4. Redistribution. You may reproduce and distribute copies of the * Work or Derivative Works thereof in any medium, with or without * modifications, and in Source or Object form, provided that You * meet the following conditions: * * (a) You must give any other recipients of the Work or * Derivative Works a copy of this License; and * * (b) You must cause any modified files to carry prominent notices * stating that You changed the files; and * * (c) You must retain, in the Source form of any Derivative Works * that You distribute, all copyright, patent, trademark, and * attribution notices from the Source form of the Work, * excluding those notices that do not pertain to any part of * the Derivative Works; and * * (d) If the Work includes a "NOTICE" text file as part of its * distribution, then any Derivative Works that You distribute must * include a readable copy of the attribution notices contained * within such NOTICE file, excluding those notices that do not * pertain to any part of the Derivative Works, in at least one * of the following places: within a NOTICE text file distributed * as part of the Derivative Works; within the Source form or * documentation, if provided along with the Derivative Works; or, * within a display generated by the Derivative Works, if and * wherever such third-party notices normally appear. The contents * of the NOTICE file are for informational purposes only and * do not modify the License. You may add Your own attribution * notices within Derivative Works that You distribute, alongside * or as an addendum to the NOTICE text from the Work, provided * that such additional attribution notices cannot be construed * as modifying the License. * * You may add Your own copyright statement to Your modifications and * may provide additional or different license terms and conditions * for use, reproduction, or distribution of Your modifications, or * for any such Derivative Works as a whole, provided Your use, * reproduction, and distribution of the Work otherwise complies with * the conditions stated in this License. * * 5. Submission of Contributions. Unless You explicitly state otherwise, * any Contribution intentionally submitted for inclusion in the Work * by You to the Licensor shall be under the terms and conditions of * this License, without any additional terms or conditions. * Notwithstanding the above, nothing herein shall supersede or modify * the terms of any separate license agreement you may have executed * with Licensor regarding such Contributions. * * 6. Trademarks. This License does not grant permission to use the trade * names, trademarks, service marks, or product names of the Licensor, * except as required for reasonable and customary use in describing the * origin of the Work and reproducing the content of the NOTICE file. * * 7. Disclaimer of Warranty. Unless required by applicable law or * agreed to in writing, Licensor provides the Work (and each * Contributor provides its Contributions) on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied, including, without limitation, any warranties or conditions * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A * PARTICULAR PURPOSE. You are solely responsible for determining the * appropriateness of using or redistributing the Work and assume any * risks associated with Your exercise of permissions under this License. * * 8. Limitation of Liability. In no event and under no legal theory, * whether in tort (including negligence), contract, or otherwise, * unless required by applicable law (such as deliberate and grossly * negligent acts) or agreed to in writing, shall any Contributor be * liable to You for damages, including any direct, indirect, special, * incidental, or consequential damages of any character arising as a * result of this License or out of the use or inability to use the * Work (including but not limited to damages for loss of goodwill, * work stoppage, computer failure or malfunction, or any and all * other commercial damages or losses), even if such Contributor * has been advised of the possibility of such damages. * * 9. Accepting Warranty or Additional Liability. While redistributing * the Work or Derivative Works thereof, You may choose to offer, * and charge a fee for, acceptance of support, warranty, indemnity, * or other liability obligations and/or rights consistent with this * License. However, in accepting such obligations, You may act only * on Your own behalf and on Your sole responsibility, not on behalf * of any other Contributor, and only if You agree to indemnify, * defend, and hold each Contributor harmless for any liability * incurred by, or claims asserted against, such Contributor by reason * of your accepting any such warranty or additional liability. * * END OF TERMS AND CONDITIONS * * APPENDIX: How to apply the Apache License to your work. * * To apply the Apache License to your work, attach the following * boilerplate notice, with the fields enclosed by brackets "[]" * replaced with your own identifying information. (Don't include * the brackets!) The text should be enclosed in the appropriate * comment syntax for the file format. We also recommend that a * file or class name and description of purpose be included on the * same "printed page" as the copyright notice for easier * identification within third-party archives. * * Copyright 2016 Alibaba Group * * Licensed 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; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Color; import android.graphics.Point; import android.graphics.PointF; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.ArrayMap; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.text.TextUtils; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import com.taobao.weex.WXEnvironment; import com.taobao.weex.WXSDKInstance; import com.taobao.weex.annotation.JSMethod; import com.taobao.weex.common.Constants; import com.taobao.weex.common.OnWXScrollListener; import com.taobao.weex.common.WXRuntimeException; import com.taobao.weex.dom.WXDomObject; 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.helper.WXStickyHelper; 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.ListBaseViewHolder; import com.taobao.weex.ui.view.listview.adapter.RecyclerViewBaseAdapter; import com.taobao.weex.ui.view.listview.adapter.TransformItemDecoration; import com.taobao.weex.ui.view.listview.adapter.WXRecyclerViewOnScrollListener; 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.regex.Matcher; import java.util.regex.Pattern; /** * Created by sospartan on 13/12/2016. */ public abstract class BasicListComponent<T extends ViewGroup & ListComponentView> extends WXVContainer<T> implements IRecyclerAdapterListener<ListBaseViewHolder>, IOnLoadMoreListener, Scrollable { public static final String TRANSFORM = "transform"; public static final String LOADMOREOFFSET = "loadmoreoffset"; private String TAG = "BasicListComponent"; private int mListCellCount = 0; private String mLoadMoreRetry = ""; private ArrayList<ListBaseViewHolder> recycleViewList = new ArrayList<>(); private static final Pattern transformPattern = Pattern.compile("([a-z]+)\\(([0-9\\.]+),?([0-9\\.]+)?\\)"); private Map<String, AppearanceHelper> mAppearComponents = new HashMap<>(); private boolean isScrollable = true; private ArrayMap<String, Long> mRefToViewType; private SparseArray<ArrayList<WXComponent>> mViewTypes; private WXRecyclerViewOnScrollListener mViewOnScrollListener = new WXRecyclerViewOnScrollListener(this); private static final int MAX_VIEWTYPE_ALLOW_CACHE = 9; private static boolean mAllowCacheViewHolder = true; private static boolean mDownForBidCacheViewHolder = false; private int mOffsetAccuracy = 10; private Point mLastReport = new Point(-1, -1); /** * Map for storing component that is sticky. **/ private Map<String, HashMap<String, WXComponent>> mStickyMap = new HashMap<>(); private WXStickyHelper stickyHelper; public BasicListComponent(WXSDKInstance instance, WXDomObject node, WXVContainer parent) { super(instance, node, parent); stickyHelper = new WXStickyHelper(this); } /** * 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); } public int getOrientation() { return Constants.Orientation.VERTICAL; } @Override public void destroy() { super.destroy(); if (mStickyMap != null) mStickyMap.clear(); if (mViewTypes != null) mViewTypes.clear(); if (mRefToViewType != null) mRefToViewType.clear(); } @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; } /** * These transform functions are supported: * - `scale(x,y)`: scale item, x and y should be a positive float number. * - `translate(x,y)`: translate item, `x` and `y` shoule be integer numbers. * - `opacity(n)`: change the transparency of item, `n` must in `[0,1.0]`. * - `rotate(n)`: rotate item, n is integer number. * * @param raw * @return */ private RecyclerView.ItemDecoration parseTransforms(String raw) { if (raw == null) { return null; } float scaleX = 0f, scaleY = 0f; int translateX = 0, translateY = 0; float opacity = 0f; int rotate = 0; //public TransformItemDecoration(boolean isVertical,float alpha,int translateX,int translateY,int rotation,float scale) Matcher matcher = transformPattern.matcher(raw); while (matcher.find()) { String match = matcher.group(); String name = matcher.group(1); try { switch (name) { case "scale": scaleX = Float.parseFloat(matcher.group(2)); scaleY = Float.parseFloat(matcher.group(3)); break; case "translate": translateX = Integer.parseInt(matcher.group(2)); translateY = Integer.parseInt(matcher.group(3)); break; case "opacity": opacity = Float.parseFloat(matcher.group(2)); break; case "rotate": rotate = Integer.parseInt(matcher.group(2)); break; default: WXLogUtils.e(TAG, "Invaild transform expression:" + match); break; } } catch (NumberFormatException e) { WXLogUtils.e("", e); WXLogUtils.e(TAG, "Invaild transform expression:" + match); } } return new TransformItemDecoration(getOrientation() == Constants.Orientation.VERTICAL, opacity, translateX, translateY, rotate, scaleX, scaleY); } abstract T generateListView(Context context, int orientation); @Override protected T initComponentHostView(@NonNull Context context) { T bounceRecyclerView = generateListView(context, getOrientation()); String transforms = (String) getDomObject().getAttrs().get(TRANSFORM); if (transforms != null) { bounceRecyclerView.getInnerView().addItemDecoration(parseTransforms(transforms)); } RecyclerViewBaseAdapter recyclerViewBaseAdapter = new RecyclerViewBaseAdapter<>(this); recyclerViewBaseAdapter.setHasStableIds(true); 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); if (newState == RecyclerView.SCROLL_STATE_IDLE) { for (ListBaseViewHolder holder : recycleViewList) { if (holder != null && holder.getComponent() != null && !holder.getComponent().isUsing()) { recycleImage(holder.getView()); } } recycleViewList.clear(); } 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) { for (OnWXScrollListener listener : listeners) { if (listener != null) { listener.onScrolled(recyclerView, dx, dy); } } } } }); bounceRecyclerView.getViewTreeObserver() .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onGlobalLayout() { T 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); } } }); return bounceRecyclerView; } @Override public void bindStickStyle(WXComponent component) { stickyHelper.bindStickStyle(component, mStickyMap); } @Override public void unbindStickStyle(WXComponent component) { stickyHelper.unbindStickStyle(component, mStickyMap); } private @Nullable WXComponent findDirectListChild(WXComponent comp) { WXComponent parent; if (comp == null || (parent = comp.getParent()) == null) { return null; } if (parent instanceof WXListComponent) { return comp; } return findDirectListChild(parent); } @Override protected boolean setProperty(String key, Object param) { switch (key) { case LOADMOREOFFSET: return true; case Constants.Name.SCROLLABLE: boolean scrollable = WXUtils.getBoolean(param, true); setScrollable(scrollable); 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.SCROLLABLE) public void setScrollable(boolean scrollable) { this.isScrollable = scrollable; WXRecyclerView inner = getHostView().getInnerView(); if (inner != null) { inner.setScrollable(scrollable); } } @WXComponentProp(name = Constants.Name.OFFSET_ACCURACY) public void setOffsetAccuracy(int accuracy) { float real = WXViewUtils.getRealPxByWidth(accuracy, getInstance().getViewPortWidth()); this.mOffsetAccuracy = (int) real; } @Override public boolean isScrollable() { return isScrollable; } private void setAppearanceWatch(WXComponent component, int event, boolean enable) { AppearanceHelper item = mAppearComponents.get(component.getRef()); if (item != null) { item.setWatchEvent(event, enable); } else if (!enable) { //Do nothing if disable target not exist. } else { WXComponent dChild = findDirectListChild(component); int index = mChildren.indexOf(dChild); if (index != -1) { item = new AppearanceHelper(component, index); item.setWatchEvent(event, true); mAppearComponents.put(component.getRef(), 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); } @Override public void scrollTo(WXComponent component, Map<String, Object> options) { float offsetFloat = 0; boolean smooth = true; 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().getViewPortWidth()); } catch (Exception e) { WXLogUtils.e("Float parseFloat error :" + e.getMessage()); } } } final int offset = (int) offsetFloat; T bounceRecyclerView = getHostView(); if (bounceRecyclerView == null) { return; } WXComponent parent = component; WXCell cell = null; while (parent != null) { if (parent instanceof WXCell) { cell = (WXCell) parent; break; } parent = parent.getParent(); } if (cell != null) { final int pos = mChildren.indexOf(cell); final WXRecyclerView view = bounceRecyclerView.getInnerView(); if (!smooth) { RecyclerView.LayoutManager layoutManager = view.getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) { //GridLayoutManager is also instance of LinearLayoutManager ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(pos, -offset); } else if (layoutManager instanceof StaggeredGridLayoutManager) { ((StaggeredGridLayoutManager) layoutManager).scrollToPositionWithOffset(pos, -offset); } //Any else? } else { view.smoothScrollToPosition(pos); if (offset != 0) { view.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (getOrientation() == Constants.Orientation.VERTICAL) { recyclerView.smoothScrollBy(0, offset); } else { recyclerView.smoothScrollBy(offset, 0); } recyclerView.removeOnScrollListener(this); } } }); } } } } @Override public void onBeforeScroll(int dx, int dy) { T bounceRecyclerView = getHostView(); if (mStickyMap == null || bounceRecyclerView == null) { return; } HashMap<String, WXComponent> stickyMap = mStickyMap.get(getRef()); if (stickyMap == null) { return; } Iterator<Map.Entry<String, WXComponent>> iterator = stickyMap.entrySet().iterator(); Map.Entry<String, WXComponent> entry; WXComponent stickyComponent; while (iterator.hasNext()) { entry = iterator.next(); stickyComponent = entry.getValue(); if (stickyComponent != null && stickyComponent.getDomObject() != null && stickyComponent instanceof WXCell) { WXCell cell = (WXCell) stickyComponent; if (cell.getHostView() == null) { return; } if (stickyComponent != null && stickyComponent.getDomObject() != null && stickyComponent instanceof WXCell) { if (stickyComponent.getHostView() == null) { return; } RecyclerView.LayoutManager layoutManager; boolean beforeFirstVisibleItem = false; if ((layoutManager = getHostView().getInnerView() .getLayoutManager()) instanceof LinearLayoutManager) { int fVisible = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); int pos = mChildren.indexOf(cell); if (pos <= fVisible) { beforeFirstVisibleItem = true; } } int[] location = new int[2]; stickyComponent.getHostView().getLocationOnScreen(location); int[] parentLocation = new int[2]; stickyComponent.getParentScroller().getView().getLocationOnScreen(parentLocation); int top = location[1] - parentLocation[1]; boolean showSticky = beforeFirstVisibleItem && cell.getLocationFromStart() >= 0 && top <= 0 && dy >= 0; boolean removeSticky = cell.getLocationFromStart() <= 0 && top > 0 && dy <= 0; if (showSticky) { bounceRecyclerView.notifyStickyShow(cell); } else if (removeSticky) { bounceRecyclerView.notifyStickyRemove(cell); } cell.setLocationFromStart(top); } } } } @Override public int getScrollY() { T bounceRecyclerView = getHostView(); return bounceRecyclerView == null ? 0 : bounceRecyclerView.getInnerView().getScrollY(); } @Override public int getScrollX() { T bounceRecyclerView = getHostView(); return bounceRecyclerView == null ? 0 : bounceRecyclerView.getInnerView().getScrollX(); } /** * Append a child component to the end of WXListComponent. This will not refresh the underlying * view immediately. The message of index of the inserted child is given to the adapter, and the * adapter will determine when to refresh. The default implementation of adapter will push the * message into a message and refresh the view in a period of time. * * @param child the inserted child */ @Override public void addChild(WXComponent child) { addChild(child, -1); } @Override protected int getChildrenLayoutTopOffset() { return 0; } /** * @param child the inserted child * @param index the index of the child to be inserted. * @see #addChild(WXComponent) */ @Override public void addChild(WXComponent child, int index) { super.addChild(child, index); if (child == null || index < -1) { return; } int count = mChildren.size(); index = index >= count ? -1 : index; bindViewType(child); int adapterPosition = index == -1 ? mChildren.size() - 1 : index; T view = getHostView(); if (view != null) { view.getRecyclerViewBaseAdapter().notifyItemInserted(adapterPosition); } relocateAppearanceHelper(); } private void relocateAppearanceHelper() { Iterator<Map.Entry<String, AppearanceHelper>> iterator = mAppearComponents.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, AppearanceHelper> item = iterator.next(); AppearanceHelper value = item.getValue(); WXComponent dChild = findDirectListChild(value.getAwareChild()); int index = mChildren.indexOf(dChild); value.setCellPosition(index); } } /** * 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 protected void addSubView(View child, int index) { } /** * Remove the child from WXListComponent. This method will use {@link * java.util.List#indexOf(Object)} to retrieve the component to be deleted. Like {@link * #addChild(WXComponent)}, this method will not refresh the view immediately, the adapter will * decide when to refresh. * * @param child the child to be removed */ @Override public void remove(WXComponent child, boolean destroy) { int index = mChildren.indexOf(child); if (destroy) { child.detachViewAndClearPreInfo(); } unBindViewType(child); T view = getHostView(); if (view == null) { return; } view.getRecyclerViewBaseAdapter().notifyItemRemoved(index); if (WXEnvironment.isApkDebugable()) { WXLogUtils.d(TAG, "removeChild child at " + index); } super.remove(child, destroy); } @Override public void computeVisiblePointInViewCoordinate(PointF pointF) { RecyclerView view = getHostView().getInnerView(); pointF.set(view.computeHorizontalScrollOffset(), view.computeVerticalScrollOffset()); } /** * Recycle viewHolder and its underlying view. This may because the view is removed or reused. * Either case, this method will be called. * * @param holder The view holder to be recycled. */ @Override public void onViewRecycled(ListBaseViewHolder holder) { long begin = System.currentTimeMillis(); holder.setComponentUsing(false); if (holder.canRecycled()) { recycleViewList.add(holder); } else { WXLogUtils.w(TAG, "this holder can not be allowed to recycled"); } if (WXEnvironment.isApkDebugable()) { WXLogUtils.d(TAG, "Recycle holder " + (System.currentTimeMillis() - begin) + " Thread:" + Thread.currentThread().getName()); } } /** * Bind the component of the position to the holder. Then flush the view. * * @param holder viewHolder, which holds reference to the view * @param position position of component in WXListComponent */ @Override public void onBindViewHolder(ListBaseViewHolder holder, int position) { if (holder == null) return; holder.setComponentUsing(true); WXComponent component = getChild(position); if (component == null || (component instanceof WXRefresh) || (component instanceof WXLoading) || (component.getDomObject() != null && component.getDomObject().isFixed())) { if (WXEnvironment.isApkDebugable()) { WXLogUtils.d(TAG, "Bind WXRefresh & WXLoading " + holder); } return; } if (holder.getComponent() != null && holder.getComponent() instanceof WXCell) { holder.getComponent().bindData(component); // holder.getComponent().refreshData(component); } } /** * Create an instance of {@link ListBaseViewHolder} for the given viewType (not for the given * index). This method will look up for the first component that fits the viewType requirement and * doesn't be used. Then create the certain type of view, detach the view f[rom the component. * * @param parent the ViewGroup into which the new view will be inserted * @param viewType the type of the new view * @return the created view holder. */ @Override public ListBaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mChildren != null) { if (mViewTypes == null) return createVHForFakeComponent(viewType); ArrayList<WXComponent> mTypes = mViewTypes.get(viewType); checkRecycledViewPool(viewType); if (mTypes == null) return createVHForFakeComponent(viewType); for (int i = 0; i < mTypes.size(); i++) { WXComponent component = mTypes.get(i); if (component == null || component.isUsing()) { continue; } if (component.getDomObject() != null && component.getDomObject().isFixed()) { return createVHForFakeComponent(viewType); } else { if (component instanceof WXCell) { if (component.getRealView() != null) { return new ListBaseViewHolder(component, viewType); } else { ((WXCell) component).lazy(false); component.createView(); component.applyLayoutAndEvent(component); return new ListBaseViewHolder(component, viewType); } } else if (component instanceof WXBaseRefresh) { return createVHForRefreshComponent(viewType); } else { WXLogUtils.e(TAG, "List cannot include element except cell?header?fixed?refresh and loading"); return createVHForFakeComponent(viewType); } } } } if (WXEnvironment.isApkDebugable()) { WXLogUtils.e(TAG, "Cannot find request viewType: " + viewType); } return createVHForFakeComponent(viewType); } /** * Forbid ViewHolder cache if viewType > MAX_VIEWTYPE_ALLOW_CACHE * * @param viewType */ private void checkRecycledViewPool(int viewType) { try { if (mViewTypes.size() > MAX_VIEWTYPE_ALLOW_CACHE) mAllowCacheViewHolder = false; if (mDownForBidCacheViewHolder) if (getHostView() != null && getHostView().getInnerView() != null) getHostView().getInnerView().getRecycledViewPool().setMaxRecycledViews(viewType, 0); if (!mDownForBidCacheViewHolder) { if (!mAllowCacheViewHolder) { if (getHostView() != null && getHostView().getInnerView() != null) { for (int i = 0; i < mViewTypes.size(); i++) { getHostView().getInnerView().getRecycledViewPool() .setMaxRecycledViews(mViewTypes.keyAt(i), 0); } mDownForBidCacheViewHolder = true; } } } } catch (Exception e) { WXLogUtils.e(TAG, "Clear recycledViewPool error!"); } } /** * Return the child component type. The type is defined by scopeValue in .we file. * * @param position the position of the child component. * @return the type of certain component. */ @Override public int getItemViewType(int position) { return generateViewType(getChild(position)); } /** * ViewType will be classified into {HashMap<Integer,ArrayList<Integer>> mViewTypes} * * @param component */ private void bindViewType(WXComponent component) { int id = generateViewType(component); if (mViewTypes == null) { mViewTypes = new SparseArray<>(); } ArrayList<WXComponent> mTypes = mViewTypes.get(id); if (mTypes == null) { mTypes = new ArrayList<>(); mViewTypes.put(id, mTypes); } mTypes.add(component); } private void unBindViewType(WXComponent component) { int id = generateViewType(component); if (mViewTypes == null) return; ArrayList<WXComponent> mTypes = mViewTypes.get(id); if (mTypes == null) return; mTypes.remove(component); } /** * generate viewtype by component * * @param component * @return */ private int generateViewType(WXComponent component) { long id; try { id = Integer.parseInt(component.getDomObject().getRef()); String type = component.getDomObject().getAttrs().getScope(); if (!TextUtils.isEmpty(type)) { if (mRefToViewType == null) { mRefToViewType = new ArrayMap<>(); } if (!mRefToViewType.containsKey(type)) { mRefToViewType.put(type, id); } id = mRefToViewType.get(type); } } catch (RuntimeException e) { WXLogUtils.eTag(TAG, e); id = RecyclerView.NO_ID; WXLogUtils.e(TAG, "getItemViewType: NO ID, this will crash the whole render system of WXListRecyclerView"); } return (int) id; } /** * Get child component num. * * @return return the size of {@link #mChildren} if mChildren is not empty, otherwise, return 0; */ @Override public int getItemCount() { return getChildCount(); } @Override public boolean onFailedToRecycleView(ListBaseViewHolder holder) { if (WXEnvironment.isApkDebugable()) { WXLogUtils.d(TAG, "Failed to recycle " + holder); } return false; } @Override public long getItemId(int position) { long id; try { id = Long.parseLong(getChild(position).getDomObject().getRef()); } catch (RuntimeException e) { WXLogUtils.e(TAG, WXLogUtils.getStackTrace(e)); id = RecyclerView.NO_ID; } return id; } @Override public void onLoadMore(int offScreenY) { try { String offset = getDomObject().getAttrs().getLoadMoreOffset(); if (TextUtils.isEmpty(offset)) { offset = "0"; } if (offScreenY < Integer.parseInt(offset)) { String loadMoreRetry = getDomObject().getAttrs().getLoadMoreRetry(); if (loadMoreRetry == null) { loadMoreRetry = mLoadMoreRetry; } if (mListCellCount != mChildren.size() || mLoadMoreRetry == null || !mLoadMoreRetry.equals(loadMoreRetry)) { fireEvent(Constants.Event.LOADMORE); mListCellCount = mChildren.size(); mLoadMoreRetry = loadMoreRetry; } } } catch (Exception e) { WXLogUtils.d(TAG + "onLoadMore :", e); } } @Override public void notifyAppearStateChange(int firstVisible, int lastVisible, int directionX, int directionY) { //notify appear state Iterator<AppearanceHelper> it = mAppearComponents.values().iterator(); 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; } while (it.hasNext()) { AppearanceHelper item = it.next(); WXComponent component = item.getAwareChild(); if (!item.isWatch()) { continue; } boolean outOfVisibleRange = item.getCellPositionINScollable() < firstVisible || item.getCellPositionINScollable() > lastVisible; View view = component.getHostView(); if (view == null) { continue; } boolean visible = (!outOfVisibleRange) && item.isViewVisible(); int result = item.setAppearStatus(visible); if (WXEnvironment.isApkDebugable()) { WXLogUtils.d("appear", "item " + item.getCellPositionINScollable() + " result " + result); } if (result == AppearanceHelper.RESULT_NO_CHANGE) { continue; } component.notifyAppearStateChange( result == AppearanceHelper.RESULT_APPEAR ? Constants.Event.APPEAR : Constants.Event.DISAPPEAR, direction); } } private void recycleImage(View view) { if (view instanceof ImageView) { if (getInstance().getImgLoaderAdapter() != null) { getInstance().getImgLoaderAdapter().setImage(null, (ImageView) view, null, null); } else { if (WXEnvironment.isApkDebugable()) { throw new WXRuntimeException("getImgLoaderAdapter() == null"); } WXLogUtils.e("Error getImgLoaderAdapter() == null"); } } else if (view instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { recycleImage(((ViewGroup) view).getChildAt(i)); } } } @NonNull private ListBaseViewHolder createVHForFakeComponent(int viewType) { FrameLayout view = new FrameLayout(getContext()); view.setBackgroundColor(Color.WHITE); view.setLayoutParams(new FrameLayout.LayoutParams(0, 0)); return new ListBaseViewHolder(view, viewType); } private ListBaseViewHolder createVHForRefreshComponent(int viewType) { FrameLayout view = new FrameLayout(getContext()); view.setBackgroundColor(Color.WHITE); view.setLayoutParams(new FrameLayout.LayoutParams(1, 1)); view.setVisibility(View.GONE); return new ListBaseViewHolder(view, viewType); } @JSMethod public void resetLoadmore() { mLoadMoreRetry = ""; } @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() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int offsetX = recyclerView.computeHorizontalScrollOffset(); int offsetY = recyclerView.computeVerticalScrollOffset(); if (shouldReport(offsetX, offsetY)) { int contentWidth = recyclerView.getMeasuredWidth() + recyclerView.computeHorizontalScrollRange(); int contentHeight = recyclerView.computeVerticalScrollRange(); 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().getViewPortWidth())); contentSize.put(Constants.Name.HEIGHT, WXViewUtils.getWebPxByWidth(contentHeight, getInstance().getViewPortWidth())); contentOffset.put(Constants.Name.X, -WXViewUtils.getWebPxByWidth(offsetX, getInstance().getViewPortWidth())); contentOffset.put(Constants.Name.Y, -WXViewUtils.getWebPxByWidth(offsetY, getInstance().getViewPortWidth())); 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; } if (Math.abs(mLastReport.x - offsetX) >= mOffsetAccuracy || Math.abs(mLastReport.y - offsetY) >= mOffsetAccuracy) { mLastReport.x = offsetX; mLastReport.y = offsetY; return true; } return false; } }