| /* |
| * 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; |
| |
| import android.content.Context; |
| import android.support.v4.util.ArrayMap; |
| import android.support.v7.widget.PagerSnapHelper; |
| import android.text.TextUtils; |
| import com.alibaba.fastjson.JSON; |
| import org.apache.weex.WXSDKInstance; |
| import org.apache.weex.annotation.Component; |
| import org.apache.weex.common.Constants; |
| import org.apache.weex.common.WXErrorCode; |
| import org.apache.weex.common.WXThread; |
| import org.apache.weex.ui.ComponentCreator; |
| import org.apache.weex.ui.action.BasicComponentData; |
| import org.apache.weex.ui.component.WXBaseRefresh; |
| import org.apache.weex.ui.component.WXBasicComponentType; |
| 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.view.listview.WXRecyclerView; |
| import org.apache.weex.ui.view.listview.adapter.ListBaseViewHolder; |
| 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 java.lang.reflect.InvocationTargetException; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| /** |
| * Unlike other components, there is immutable bi-directional association between View and |
| * ViewHolder, while only mutable and temporal uni-directional association between view and |
| * components. The association only exist from {@link #onBindViewHolder(ListBaseViewHolder, int)} to |
| * {@link #onViewRecycled(ListBaseViewHolder)}. In other situations, the association may not valid |
| * or not even exist. |
| */ |
| @Component(lazyload = false) |
| |
| public class WXListComponent extends BasicListComponent<BounceRecyclerView> { |
| private String TAG = "WXListComponent"; |
| // private WXRecyclerDomObject mDomObject; |
| private float mPaddingLeft; |
| private float mPaddingRight; |
| private String mSpanOffsetsStr; |
| private Float[] mSpanOffsets; |
| private boolean hasSetGapItemDecoration = false; |
| |
| public static class Creator implements ComponentCreator { |
| public WXComponent createInstance(WXSDKInstance instance, |
| WXVContainer parent, |
| BasicComponentData basicComponentData) |
| throws IllegalAccessException, InvocationTargetException, InstantiationException { |
| return new WXListComponent(instance, parent, true, basicComponentData); |
| } |
| } |
| |
| @Deprecated |
| public WXListComponent(WXSDKInstance instance, WXVContainer parent, String instanceId, boolean isLazy, BasicComponentData basicComponentData) { |
| this(instance, parent, isLazy, basicComponentData); |
| } |
| |
| |
| public WXListComponent(WXSDKInstance instance, WXVContainer parent, boolean lazy, BasicComponentData basicComponentData) { |
| super(instance, parent, basicComponentData); |
| } |
| |
| @Override |
| protected BounceRecyclerView generateListView(Context context, int orientation) { |
| BounceRecyclerView bounceRecyclerView = new BounceRecyclerView(context, mLayoutType, mColumnCount, mColumnGap, orientation); |
| if (bounceRecyclerView.getSwipeLayout() != null) { |
| if (WXUtils.getBoolean(getAttrs().get(Constants.Name.NEST_SCROLLING_ENABLED), false)) { |
| bounceRecyclerView.getSwipeLayout().setNestedScrollingEnabled(true); |
| } |
| } |
| |
| /** |
| * enable pagingEnabled attr |
| */ |
| if(WXUtils.getBoolean(getAttrs().get(Constants.Name.PAGE_ENABLED),false)){ |
| PagerSnapHelper snapHelper = null; |
| String pageSize = WXUtils.getString(getAttrs().get(Constants.Name.PAGE_SIZE), null); |
| if(TextUtils.isEmpty(pageSize)) { |
| snapHelper = new PagerSnapHelper(); |
| } else { |
| snapHelper = new WXPagerSnapHelper(); |
| } |
| |
| snapHelper.attachToRecyclerView(bounceRecyclerView.getInnerView()); |
| } |
| |
| return bounceRecyclerView; |
| } |
| |
| @Override |
| public void addChild(WXComponent child, int index) { |
| super.addChild(child, index); |
| if (child == null || index < -1) { |
| return; |
| } |
| setRefreshOrLoading(child); |
| // Synchronize DomObject's attr to Component and Native View |
| if(getHostView() != null && hasColumnPros()) { |
| updateRecyclerAttr(); |
| getHostView().getInnerView().initView(getContext(), mLayoutType,mColumnCount,mColumnGap,getOrientation()); |
| } |
| } |
| |
| private boolean hasColumnPros() { |
| return (getAttrs().containsKey(Constants.Name.COLUMN_WIDTH) && mColumnWidth != WXUtils.parseFloat(getAttrs().get(Constants.Name.COLUMN_WIDTH))) || |
| (getAttrs().containsKey(Constants.Name.COLUMN_COUNT) && mColumnCount != WXUtils.parseInt(getAttrs().get(Constants.Name.COLUMN_COUNT))) || |
| (getAttrs().containsKey(Constants.Name.COLUMN_GAP) && mColumnGap != WXUtils.parseFloat(getAttrs().get(Constants.Name.COLUMN_GAP))); |
| } |
| |
| /** |
| * Setting refresh view and loading view |
| * |
| * @param child the refresh_view or loading_view |
| */ |
| private boolean setRefreshOrLoading(final WXComponent child) { |
| |
| if (getHostView() == null) { |
| WXLogUtils.e(TAG, "setRefreshOrLoading: HostView == null !!!!!! check list attr has append =tree"); |
| return true; |
| } |
| if (child instanceof WXRefresh) { |
| 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().setOnLoadingListener((WXLoading) child); |
| getHostView().postDelayed(WXThread.secure(new Runnable() { |
| @Override |
| public void run() { |
| getHostView().setFooterView(child); |
| } |
| }), 100); |
| return true; |
| } |
| return false; |
| } |
| |
| private void updateRecyclerAttr() { |
| mColumnCount = WXUtils.parseInt(getAttrs().get(Constants.Name.COLUMN_COUNT)); |
| 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 = WXUtils.parseFloat(getAttrs().get(Constants.Name.COLUMN_GAP)); |
| mColumnWidth = WXUtils.parseFloat(getAttrs().get(Constants.Name.COLUMN_WIDTH)); |
| mPaddingLeft = WXUtils.parseFloat(getAttrs().get(Constants.Name.PADDING_LEFT)); |
| mPaddingRight = WXUtils.parseFloat(getAttrs().get(Constants.Name.PADDING_RIGHT)); |
| mSpanOffsetsStr = (String)getAttrs().get(Constants.Name.SPAN_OFFSETS); |
| |
| try { |
| if (!TextUtils.isEmpty(mSpanOffsetsStr)) { |
| List<Float> list = JSON.parseArray(mSpanOffsetsStr, Float.class); |
| final int size = list.size(); |
| if (null == mSpanOffsets || mSpanOffsets.length != size) { |
| mSpanOffsets = new Float[size]; |
| } |
| list.toArray(mSpanOffsets); |
| } else { |
| mSpanOffsets = null; |
| } |
| } catch (Throwable e) { |
| WXLogUtils.w("Parser SpanOffsets error ", e); |
| } |
| |
| if (!hasSetGapItemDecoration && null != getSpanOffsets() && null != getHostView() |
| && null != getHostView().getInnerView()) { |
| hasSetGapItemDecoration = true; |
| getHostView().getInnerView().addItemDecoration(new GapItemDecoration(this)); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.SPAN_OFFSETS) |
| public void setSpanOffsets(String spanOffsets) { |
| if (!TextUtils.equals(spanOffsets, mSpanOffsetsStr)) { |
| markComponentUsable(); |
| updateRecyclerAttr(); |
| WXRecyclerView wxRecyclerView = getHostView().getInnerView(); |
| wxRecyclerView.initView(getContext(), mLayoutType, mColumnCount, mColumnGap, getOrientation()); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.COLUMN_WIDTH) |
| public void setColumnWidth(float columnWidth) { |
| if(columnWidth != mColumnWidth){ |
| markComponentUsable(); |
| updateRecyclerAttr(); |
| WXRecyclerView wxRecyclerView = getHostView().getInnerView(); |
| wxRecyclerView.initView(getContext(), mLayoutType,mColumnCount,mColumnGap,getOrientation()); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.COLUMN_COUNT) |
| public void setColumnCount(int columnCount){ |
| if(columnCount != mColumnCount) { |
| markComponentUsable(); |
| 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(columnGap != mColumnGap) { |
| markComponentUsable(); |
| 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); |
| } |
| |
| @Override |
| public void updateProperties(Map<String, Object> props) { |
| super.updateProperties(props); |
| if (isRecycler(this)) { |
| if(WXBasicComponentType.WATERFALL.equals(getComponentType())){ |
| mLayoutType = WXRecyclerView.TYPE_STAGGERED_GRID_LAYOUT; |
| }else{ |
| mLayoutType = getAttrs().getLayoutType(); |
| } |
| } |
| |
| if(props.containsKey(Constants.Name.PADDING) |
| ||props.containsKey(Constants.Name.PADDING_LEFT) |
| || props.containsKey(Constants.Name.PADDING_RIGHT)){ |
| if(mPaddingLeft != WXUtils.parseFloat(props.get(Constants.Name.PADDING_LEFT)) || mPaddingRight != WXUtils.parseFloat(props.get(Constants.Name.PADDING_RIGHT))) { |
| markComponentUsable(); |
| updateRecyclerAttr(); |
| WXRecyclerView wxRecyclerView = getHostView().getInnerView(); |
| wxRecyclerView.initView(getContext(), mLayoutType, mColumnCount, mColumnGap, getOrientation()); |
| } |
| } |
| } |
| |
| @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(); |
| if (child instanceof WXRefresh) { |
| getHostView().setOnRefreshListener((WXRefresh) child); |
| getHostView().postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| getHostView().setHeaderView(child); |
| } |
| }, 100); |
| } else if (child instanceof WXLoading) { |
| getHostView().setOnLoadingListener((WXLoading) child); |
| getHostView().postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| getHostView().setFooterView(child); |
| } |
| }, 100); |
| } |
| } else { |
| super.createChildViewAt(indexToCreate); |
| } |
| } |
| |
| public void remove(WXComponent child, boolean destroy) { |
| super.remove(child, destroy); |
| removeFooterOrHeader(child); |
| } |
| |
| private void removeFooterOrHeader(WXComponent child) { |
| if (child instanceof WXLoading) { |
| getHostView().removeFooterView(child); |
| } else if (child instanceof WXRefresh) { |
| getHostView().removeHeaderView(child); |
| } |
| } |
| |
| private boolean isRecycler(WXComponent component) { |
| return WXBasicComponentType.WATERFALL.equals(component.getComponentType()) |
| || WXBasicComponentType.RECYCLE_LIST.equals(component.getComponentType()) |
| || WXBasicComponentType.RECYCLER.equals(component.getComponentType()); |
| } |
| |
| public Float[] getSpanOffsets() { |
| return mSpanOffsets; |
| } |
| } |