Java tutorial
/** * Akylas * Copyright (c) 2014-2015 by Akylas. All Rights Reserved. * Licensed under the terms of the Apache Public License * Please see the LICENSE included with this distribution for details. */ package ti.modules.titanium.ui.widget.collectionview; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollFunction; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.common.Log; import org.appcelerator.titanium.TiApplication; import org.appcelerator.titanium.TiC; import org.appcelerator.titanium.TiDimension; import org.appcelerator.titanium.TiPoint; import org.appcelerator.titanium.TiLifecycle.OnInstanceStateEvent; import org.appcelerator.titanium.animation.TiAnimation; import org.appcelerator.titanium.proxy.ParentingProxy; import org.appcelerator.titanium.proxy.TiViewProxy; import org.appcelerator.titanium.util.TiColorHelper; import org.appcelerator.titanium.util.TiConvert; import org.appcelerator.titanium.util.TiRHelper; import org.appcelerator.titanium.util.TiUIHelper; import org.appcelerator.titanium.util.TiRHelper.ResourceNotFoundException; import org.appcelerator.titanium.view.TiCompositeLayout; import org.appcelerator.titanium.view.TiUINonViewGroupView; import org.appcelerator.titanium.view.TiUIView; import org.json.JSONException; import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; import ti.modules.titanium.ui.SearchBarProxy; import ti.modules.titanium.ui.UIModule; import ti.modules.titanium.ui.ViewProxy; import ti.modules.titanium.ui.android.SearchViewProxy; import ti.modules.titanium.ui.widget.abslistview.AbsListItemProxy; import ti.modules.titanium.ui.widget.abslistview.AbsListSectionProxy; import ti.modules.titanium.ui.widget.abslistview.AbsListViewProxy; import ti.modules.titanium.ui.widget.abslistview.TiAbsListView; import ti.modules.titanium.ui.widget.abslistview.TiAbsListViewTemplate; import ti.modules.titanium.ui.widget.abslistview.TiBaseAbsListViewItem; import ti.modules.titanium.ui.widget.abslistview.TiBaseAbsListViewItemHolder; import ti.modules.titanium.ui.widget.abslistview.TiCollectionViewAdapter; import ti.modules.titanium.ui.widget.abslistview.TiCollectionViewInterface; import ti.modules.titanium.ui.widget.abslistview.TiDefaultAbsListViewTemplate; import ti.modules.titanium.ui.widget.collectionview.SwipeMenuTouchListener.SwipeMenuCallback; import ti.modules.titanium.ui.widget.searchbar.TiUISearchBar; import ti.modules.titanium.ui.widget.searchbar.TiUISearchBar.OnSearchChangeListener; import ti.modules.titanium.ui.widget.searchview.TiUISearchView; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorInflater; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.PointF; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearSmoothScroller; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.LayoutManager; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.support.v7.widget.RecyclerView.ViewHolder; import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ListView; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.common.DividerItemDecoration; import eu.davidea.flexibleadapter.common.PendingItemAnimator; import eu.davidea.flexibleadapter.section.SectionAdapter; public class TiCollectionView extends TiUINonViewGroupView implements OnSearchChangeListener, TiCollectionViewInterface, OnInstanceStateEvent, FlexibleAdapter.OnStickyHeaderChangeListener, SwipeMenuCallback { FastScrollRecyclerView mRecyclerView; TiGridLayoutManager layoutManager; private TiBaseAdapter mAdapter; private List<AbsListSectionProxy> sections; private AtomicInteger itemTypeCount; private String defaultTemplateBinding; private HashMap<String, TiAbsListViewTemplate> templatesByBinding; private SparseArray<TiAbsListViewTemplate> templatesByType; public static int listContentId = 24123; // public static int isCheck; // public static int hasChild; // public static int disclosure; private int[] marker = new int[2]; private String searchText; private boolean caseInsensitive; private boolean ignoreExactMatch; private static final String TAG = "TiCollectionView"; private boolean hideKeyboardOnScroll = true; private TiDimension mColumnsWidth = null; private int mNumColumns = 0; private Object mAppearAnimation = null; private boolean mUseAppearAnimation = false; private Animator mAppearAnimators = null; protected static final int TIFLAG_NEEDS_DATASET = 0x00000001; protected static final int TIFLAG_NEEDS_ADAPTER_CHANGE = 0x00000002; private Set<AbsListItemProxy> handledProxies; // private int overallXScroll = 0; // private int overallYScroll = 0; private SwipeMenuTouchListener mSwipeMenuTouchListener; private static final String defaultTemplateKey = UIModule.LIST_ITEM_TEMPLATE_DEFAULT; private static final TiAbsListViewTemplate defaultTemplate = new TiDefaultAbsListViewTemplate( defaultTemplateKey); /* * We cache properties that already applied to the recycled list tiem in * ViewItem.java However, since Android randomly selects a cached view to * recycle, our cached properties will not be in sync with the native view's * properties when user changes those values via User Interaction - i.e * click. For this reason, we create a list that contains the properties * that must be reset every time a view is recycled, to ensure * synchronization. Currently, only "value" is in this list to correctly * update the value of Ti.UI.Switch. */ // public static List<String> MUST_SET_PROPERTIES = // Arrays.asList(TiC.PROPERTY_VALUE, TiC.PROPERTY_AUTO_LINK, // TiC.PROPERTY_TEXT, TiC.PROPERTY_HTML); // public static final String MIN_SEARCH_HEIGHT = "50dp"; public static final int HEADER_FOOTER_WRAP_ID = 12345; public static final int HEADER_VIEW_TYPE = 0; public static final int FOOTER_VIEW_TYPE = 1; public static final int BUILT_IN_TEMPLATE_ITEM_TYPE = 2; public static final int BLANK_HEADER_ITEM_TYPE = 3; public static final int CUSTOM_PROXY_ITEM_TYPE = 4; public static final int CUSTOM_TEMPLATE_ITEM_TYPE = 5; // protected Pair<AbsListSectionProxy, Pair<Integer, Integer>> // getSectionInfoByEntryIndex( // int index) { // if (index < 0) { // return null; // } // synchronized (sections) { // for (int i = 0; i < sections.size(); i++) { // AbsListSectionProxy section = sections.get(i); // int sectionItemCount = section.getItemCount(); // if (index <= sectionItemCount - 1) { // Pair<AbsListSectionProxy, Pair<Integer, Integer>> result = new // Pair<AbsListSectionProxy, Pair<Integer, Integer>>( // section, new Pair<Integer, Integer>(i, index)); // return result; // } else { // index -= sectionItemCount; // } // } // } // // return null; // } public class TiGridLayoutManager extends GridLayoutManager { private boolean isScrollEnabled = true; protected int requestedColumnWidth = 0; protected int requestedColumnCount = 1; private boolean needsChange = true; public TiGridLayoutManager(Context context) { /* * Initially set spanCount to 1, will be changed automatically * later. */ super(context, 1); } public TiGridLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout) { /* * Initially set spanCount to 1, will be changed automatically * later. */ super(context, 1, orientation, reverseLayout); } public void setColumnWidth(final int width) { if (width != requestedColumnWidth) { requestedColumnCount = -1; requestedColumnWidth = width; needsChange = true; requestLayout(); } } public void setNumColumns(int requestedColumnCount) { if (this.requestedColumnCount != requestedColumnCount) { requestedColumnWidth = 0; this.requestedColumnCount = requestedColumnCount; needsChange = true; requestLayout(); } } public int getNumColumns() { return requestedColumnCount; } public int getColumnWidth() { return requestedColumnWidth; } public void requestColumnUpdate() { needsChange = true; } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { if (needsChange) { if (requestedColumnWidth > 0) { int totalSpace; if (getOrientation() == VERTICAL) { totalSpace = getWidth() - getPaddingRight() - getPaddingLeft(); } else { totalSpace = getHeight() - getPaddingTop() - getPaddingBottom(); } int spanCount = Math.max(1, totalSpace / requestedColumnWidth); setSpanCount(spanCount); } else { setSpanCount(requestedColumnCount); } needsChange = false; } super.onLayoutChildren(recycler, state); } public void setScrollEnabled(boolean flag) { this.isScrollEnabled = flag; } @Override public boolean canScrollHorizontally() { return isScrollEnabled && super.canScrollHorizontally(); } @Override public boolean canScrollVertically() { return isScrollEnabled && super.canScrollVertically(); } public abstract class SnapTopLinearSmoothScroller extends LinearSmoothScroller { public SnapTopLinearSmoothScroller(Context context) { super(context); } @Override protected int getVerticalSnapPreference() { return SNAP_TO_START; } @Override protected int getHorizontalSnapPreference() { return SNAP_TO_START; } } @Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { SnapTopLinearSmoothScroller linearSmoothScroller = new SnapTopLinearSmoothScroller( recyclerView.getContext()) { @Override public PointF computeScrollVectorForPosition(int targetPosition) { return TiGridLayoutManager.this.computeScrollVectorForPosition(targetPosition); } }; linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); } @Override public PointF computeScrollVectorForPosition(int targetPosition) { if (getChildCount() == 0) { return null; } final int firstChildPos = getPosition(getChildAt(0)); final int direction = targetPosition < firstChildPos != getReverseLayout() ? 1 : -1; if (getOrientation() == HORIZONTAL) { return new PointF(direction, 0); } else { return new PointF(0, direction); } } } class CollectionViewHolder extends SwipeMenuViewHolder implements AnimatorListener { TiBaseAbsListViewItem itemContent; Animator mCurrentAnimator = null; public CollectionViewHolder(final TiBaseAbsListViewItemHolder content) { super(content); itemContent = (TiBaseAbsListViewItem) content.findViewById(listContentId); } public void prepareForReuse() { if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); mCurrentAnimator = null; } } public void setCurrentAnimator(Animator animator) { mCurrentAnimator = animator; mCurrentAnimator.addListener(this); } @Override public boolean canShowLeftMenu() { return itemContent.getListItem().canShowLeftMenu(); } @Override public boolean canShowRightMenu() { return itemContent.getListItem().canShowRightMenu(); } @Override public View[] getLeftButtons() { return itemContent.getListItem().getLeftButtons(); } @Override public View[] getRightButtons() { return itemContent.getListItem().getRightButtons(); } @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { mCurrentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { mCurrentAnimator = null; } @Override public void onAnimationRepeat(Animator animation) { } } public class TiBaseAdapter extends FlexibleAdapter implements TiCollectionViewAdapter, SectionAdapter, FastScrollRecyclerView.SectionedAdapter { private boolean canNotifyDataSetChanged = true; private Set mShownIndexes; public TiBaseAdapter(final Context context, int[] expandedItemsSavedState) { super(context, TiCollectionView.this); } public void setCanNotifyDataSetChanged(final boolean canNotifyDataSetChanged) { this.canNotifyDataSetChanged = canNotifyDataSetChanged; } @Override public List<Animator> getAnimators(View itemView, int position, boolean isSelected) { List<Animator> animators = new ArrayList<Animator>(); // if (mRecyclerView.getLayoutManager() instanceof // GridLayoutManager) { // //GridLayout // if (position % 2 != 0) // addSlideInFromRightAnimator(animators, itemView, 0.5f); // else // addSlideInFromLeftAnimator(animators, itemView, 0.5f); // } else { // //LinearLayout // switch (getItemViewType(position)) { // case R.layout.recycler_uls_row: // case EXAMPLE_VIEW_TYPE: // addScaleInAnimator(animators, itemView, 0.0f); // break; // default: // if (isSelected) // addSlideInFromRightAnimator(animators, itemView, 0.5f); // else // addSlideInFromLeftAnimator(animators, itemView, 0.5f); // break; // } // } // Alpha Animator is automatically added return animators; } public RecyclerView.ViewHolder createViewHolder(ViewGroup parent, int viewType, TiAbsListViewTemplate template) { TiBaseAbsListViewItem itemContent = null; TICollectionViewItemHolder content = new TICollectionViewItemHolder(getContext()); itemContent = (TiBaseAbsListViewItem) content.findViewById(listContentId); AbsListItemProxy itemProxy = null; if (template != null) { content.setTag(template.getType()); itemProxy = template.generateCellProxy(proxy, getCellProxyRootType()); } else { itemProxy = new CollectionItemProxy(); } itemProxy.setListProxy(proxy); addHandledProxy(itemProxy); TiCollectionItem listItem = new TiCollectionItem(itemProxy, itemContent, content); itemProxy.setActivity(proxy.getActivity()); itemProxy.setView(listItem); itemContent.setView(listItem); itemProxy.realizeViews(); return new CollectionViewHolder(content); } @Override public int getSectionCount() { synchronized (sections) { return sections.size(); } } @Override public int getChildCount(int groupPosition) { synchronized (sections) { if (groupPosition < sections.size()) { return sections.get(groupPosition).getItemCount(); } } return 0; } @Override public long getChildId(int groupPosition, int childPosition) { return RecyclerView.NO_ID; } private AbsListSectionProxy getSection(int groupPosition) { synchronized (sections) { if (groupPosition < sections.size()) { return sections.get(groupPosition); } } return null; } @Override public long getSectionId(int groupPosition) { AbsListSectionProxy section = getSection(groupPosition); if (section != null) { return section.getIndex(); } return RecyclerView.NO_ID; } @Override public RecyclerView.ViewHolder onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType) { if (viewType == CUSTOM_PROXY_ITEM_TYPE) { return createViewHolder(parent, viewType, null); } return createViewHolder(parent, viewType, templatesByType.get(viewType)); } @Override public ViewHolder onCreateSectionChildViewHolder(ViewGroup parent, int viewType) { return createViewHolder(parent, viewType, templatesByType.get(viewType)); } @Override public void onBindSectionViewHolder(ViewHolder holder, int position, int sectionIndex, int sectionItemIndex, boolean selected) { AbsListSectionProxy section = getSection(sectionIndex); if (section == null) { return; } // check marker if (sectionIndex > marker[0] || (sectionIndex == marker[0] && sectionItemIndex >= marker[1])) { if (proxy.hasListeners(TiC.EVENT_MARKER, false)) { proxy.fireEvent(TiC.EVENT_MARKER, null, false, false); } resetMarker(); } if (!(holder instanceof CollectionViewHolder)) { return; } // Handling templates Object item = section.getListItem(sectionItemIndex); TiBaseAbsListViewItem itemContent = ((CollectionViewHolder) holder).itemContent; if (itemContent == null) { return; } if (holder.itemView.getParent() != null) { ((ViewGroup) holder.itemView.getParent()).setClipChildren(false); } String templateId = null; if (item instanceof HashMap) { templateId = TiConvert.toString((HashMap) item, TiC.PROPERTY_TEMPLATE); } TiAbsListViewTemplate template = getTemplate(templateId, true); final int sectionCount = section.getItemCount(); boolean reusing = sectionIndex != itemContent.sectionIndex || itemContent.itemIndex >= sectionCount || (item != null && item != section.getListItem(itemContent.itemIndex)); if (reusing) { ((CollectionViewHolder) holder).prepareForReuse(); } if (template != null) { section.populateViews(item, itemContent, template, sectionItemIndex, sectionIndex, holder.itemView, reusing); } else { AbsListItemProxy itemProxy = (AbsListItemProxy) itemContent.getProxy(); int realItemIndex = section.getUserItemIndexFromSectionPosition(sectionItemIndex); itemContent.setCurrentItem(sectionIndex, realItemIndex, section); itemProxy.setCurrentItem(sectionIndex, realItemIndex, section, item); if (section.hasHeader() && sectionItemIndex == RecyclerView.NO_POSITION) { KrollProxy childProxy = section.getHoldedProxy(TiC.PROPERTY_HEADER_VIEW); if (childProxy instanceof ParentingProxy && ((ParentingProxy) childProxy).getParent() != itemContent.getProxy()) { itemProxy.removeAllChildren(); itemProxy.add(childProxy); } } else if (section.hasFooter() && sectionItemIndex == sectionCount - 1) { KrollProxy childProxy = section.getHoldedProxy(TiC.PROPERTY_FOOTER_VIEW); if (childProxy instanceof ParentingProxy && ((ParentingProxy) childProxy).getParent() != itemContent.getProxy()) { itemProxy.removeAllChildren(); itemProxy.add(childProxy); } } } ((TiBaseAbsListViewItemHolder) itemContent.getParent()).setItem(itemContent, item, mRecyclerView); if (holder.itemView instanceof TiBaseAbsListViewItemHolder) { ((TiBaseAbsListViewItemHolder) holder.itemView).setItem(itemContent, item, mRecyclerView); } if (holder instanceof SwipeMenuViewHolder) { boolean isOpenedMenu = position == mSwipeMenuTouchListener.getCurrentMenuPosition(); if (isOpenedMenu) { ((SwipeMenuViewHolder) holder).swipeMenu .openMenu(mSwipeMenuTouchListener.getCurrentMenuDirection()); } else if (reusing) { ((SwipeMenuViewHolder) holder).swipeMenu.closeMenu(); } } if (mUseAppearAnimation && item != null) { if (mShownIndexes == null) { mShownIndexes = new HashSet<Integer>(); } if (!mShownIndexes.contains(position)) { mShownIndexes.add(position); Animator animator = null; if (mAppearAnimators != null) { animator = mAppearAnimators.clone(); animator.setTarget(holder.itemView); } else { Object anim = null; if (item instanceof HashMap) { anim = ((HashMap) item).get("appearAnimation"); } if (anim == null) { anim = mAppearAnimation; } if (anim != null) { AbsListItemProxy proxy = itemContent.getProxy(); animator = proxy.getAnimatorSetForAnimation(anim); } } if (animator != null) { ((CollectionViewHolder) holder).setCurrentAnimator(animator); animator.start(); } } } } @Override public boolean isSelectable(int position) { return !isHeader(position); } @Override public int getSectionViewType(int position, int groupPosition, int childPosition) { AbsListSectionProxy section = getSection(groupPosition); if (section == null) { return -1; // should never happen! } if (!section.hasHeader() && childPosition == RecyclerView.NO_POSITION) { return FlexibleAdapter.SECTION_NO_HEADER_VIEW_TYPE; } final String templateText = section.getTemplateByIndex(childPosition); if (templateText == "__custom__") { return CUSTOM_PROXY_ITEM_TYPE; } return getTemplate(templateText, true).getType(); } @Override public void notifySectionDataSetChanged() { if (!canNotifyDataSetChanged) { return; } if (mShownIndexes != null) { mShownIndexes.clear(); } super.notifySectionDataSetChanged(); mSwipeMenuTouchListener.closeMenusAnimated(); } @Override public void notifySectionItemRangeInserted(int groupPosition, int childPosition, int itemCount) { if (handledProxies != null) { for (AbsListItemProxy itemProxy : handledProxies) { if (itemProxy.sectionIndex == groupPosition && itemProxy.itemIndex >= childPosition) { itemProxy.updateItemIndex(itemProxy.itemIndex + itemCount); } } } super.notifySectionItemRangeInserted(groupPosition, childPosition, itemCount); } @Override public void notifySectionItemRangeRemoved(int groupPosition, int childPosition, int itemCount) { if (handledProxies != null) { for (AbsListItemProxy itemProxy : handledProxies) { if (itemProxy.sectionIndex == groupPosition && itemProxy.itemIndex >= childPosition + itemCount) { itemProxy.updateItemIndex(itemProxy.itemIndex - itemCount); } } } super.notifySectionItemRangeRemoved(groupPosition, childPosition, itemCount); mSwipeMenuTouchListener.closeMenusAnimated(); } @Override public FrameLayout getStickySectionHeadersHolder() { return getOrCreateBorderView(); } @Override public LayoutParams getStickySectionHeadersLayoutParams() { TiCompositeLayout.LayoutParams newParams = new TiCompositeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); newParams.optionLeft = TiConvert.toTiDimension(0, TiDimension.TYPE_LEFT); newParams.optionTop = TiConvert.toTiDimension(0, TiDimension.TYPE_TOP); return newParams; } @Override public String getSectionName(int position) { return String.valueOf(position); // int sectionIndex = getSectionIndex(position); // int sectionItemIndex = getSectionItemIndex(position); // AbsListSectionProxy section = getSection(sectionIndex); // if (section == null) { // return null; // } // HashMap item = section.getListItem(sectionItemIndex); // if (item == null) { // return null; // } // return TiConvert.toString(item, TiC.PROPERTY_SEARCHABLE_TEXT); } @Override public boolean shoudlIgnoreSectionName(int position) { int childPosition = getSectionItemIndex(position); return childPosition == RecyclerView.NO_POSITION; } } private void addHandledProxy(final AbsListItemProxy proxy) { if (handledProxies == null) { handledProxies = new HashSet<AbsListItemProxy>(); } handledProxies.add(proxy); } public static Point getScreenDimensions(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); DisplayMetrics dm = new DisplayMetrics(); display.getMetrics(dm); Point point = new Point(); point.set(dm.widthPixels, dm.heightPixels); return point; } public class TiItemAnimator extends PendingItemAnimator<CollectionViewHolder> { public TiItemAnimator() { setSupportsChangeAnimations(false); // setMoveDuration(200); // setRemoveDuration(200); // setAddDuration(300); setChangeDuration(0); } @Override protected boolean prepHolderForAnimateRemove(ViewHolder holder) { return true; } @Override protected ViewPropertyAnimatorCompat animateRemoveImpl(ViewHolder holder) { return ViewCompat.animate(holder.itemView).alpha(0); } @Override protected void onRemoveCanceled(ViewHolder holder) { ViewCompat.setAlpha(holder.itemView, 1); } @Override protected void animateRemoveEnded(ViewHolder holder) { super.animateRemoveEnded(holder); ViewCompat.setAlpha(holder.itemView, 1); } @Override protected boolean prepHolderForAnimateAdd(ViewHolder holder) { ViewCompat.setAlpha(holder.itemView, 0); return true; } @Override protected ViewPropertyAnimatorCompat animateAddImpl(ViewHolder holder) { return ViewCompat.animate(holder.itemView).alpha(1); } @Override protected void onAddCanceled(ViewHolder holder) { ViewCompat.setAlpha(holder.itemView, 0); } } public TiCollectionView(TiViewProxy proxy, Activity activity) { super(proxy); // initializing variables sections = Collections.synchronizedList(new ArrayList<AbsListSectionProxy>()); itemTypeCount = new AtomicInteger(CUSTOM_TEMPLATE_ITEM_TYPE); defaultTemplateBinding = defaultTemplateKey; defaultTemplate.setType(BUILT_IN_TEMPLATE_ITEM_TYPE); processTemplates(null); caseInsensitive = true; ignoreExactMatch = false; // handling marker HashMap<String, Integer> preloadMarker = ((AbsListViewProxy) proxy).getPreloadMarker(); if (preloadMarker != null) { setMarker(preloadMarker); } else { resetMarker(); } final KrollProxy fProxy = proxy; layoutManager = new TiGridLayoutManager(activity); // trick to get scrollbar to be initialized! mRecyclerView = new FastScrollRecyclerView(activity, null, Resources.getSystem().getIdentifier("listViewStyle", "attr", "android")) { // private boolean viewFocused = false; private boolean selectionSet = false; @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // if ((Build.VERSION.SDK_INT >= 18 && !changed && viewFocused)) // { // viewFocused = false; // super.onLayout(changed, left, top, right, bottom); // return; // } // Starting with API 21, setSelection() triggers another layout // pass, so we need to end it here to prevent // an infinite loop if (Build.VERSION.SDK_INT >= 21 && selectionSet) { selectionSet = false; return; } OnFocusChangeListener focusListener = null; View focusedView = findFocus(); int cursorPosition = -1; if (focusedView != null) { if (focusedView instanceof EditText) { cursorPosition = ((EditText) focusedView).getSelectionStart(); } OnFocusChangeListener listener = focusedView.getOnFocusChangeListener(); if (listener != null && listener instanceof TiUIView) { focusedView.setOnFocusChangeListener(null); focusListener = listener; } } if (focusedView != null) { setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); } super.onLayout(changed, left, top, right, bottom); setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); if (changed) { TiUIHelper.firePostLayoutEvent(TiCollectionView.this); } // Layout is finished, re-enable focus events. if (focusedView != null) { // If the configuration changed, we manually fire the blur // event if (changed) { focusedView.setOnFocusChangeListener(focusListener); if (focusListener != null) { focusListener.onFocusChange(focusedView, false); } } else { // Ok right now focus is with listView. So set it back // to the focusedView // viewFocused = true; focusedView.requestFocus(); focusedView.setOnFocusChangeListener(focusListener); // Restore cursor position if (cursorPosition != -1) { ((EditText) focusedView).setSelection(cursorPosition); selectionSet = true; } } } } // @Override // protected void onLayout(boolean changed, int left, int top, // int right, int bottom) { // // super.onLayout(changed, left, top, right, bottom); // if (changed) { // TiUIHelper.firePostLayoutEvent(TiCollectionView.this); // } // // } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mColumnsWidth != null && !mColumnsWidth.isUnitFixed()) { layoutManager.setColumnWidth(mColumnsWidth.getAsPixels(this)); // } else if (gridAdapter.updateNumColumns()) { // adapter.notifyDataSetChanged(); } layoutManager.requestColumnUpdate(); } @Override public void dispatchSetPressed(boolean pressed) { if (propagateSetPressed(this, pressed)) { super.dispatchSetPressed(pressed); } } @Override public boolean dispatchTouchEvent(MotionEvent event) { if (touchPassThrough(getParentViewForChild(), event)) return false; return super.dispatchTouchEvent(event); } // @Override // public boolean dispatchTouchEvent(MotionEvent event) { // if (touchPassThrough == true) // return false; // return super.dispatchTouchEvent(event); // } @Override protected void dispatchDraw(Canvas canvas) { try { super.dispatchDraw(canvas); } catch (IndexOutOfBoundsException e) { // samsung error } } }; // mRecyclerView.setClipChildren(false); mAdapter = new TiBaseAdapter(mRecyclerView.getContext(), null); mAdapter.setDisplayHeaders(true); mSwipeMenuTouchListener = new SwipeMenuTouchListener(this); mRecyclerView.addOnItemTouchListener(mSwipeMenuTouchListener); mRecyclerView.setItemAnimator(new TiItemAnimator()); mRecyclerView.setHorizontalScrollBarEnabled(false); mRecyclerView.setVerticalScrollBarEnabled(true); mRecyclerView.setHasFixedSize(true); layoutManager.setSmoothScrollbarEnabled(true); mRecyclerView.addOnScrollListener(new OnScrollListener() { private boolean scrollTouch = false; private Timer endTimer = null; public void cancelEndCall() { if (endTimer != null) { endTimer.cancel(); endTimer = null; } } public void delayEndCall() { cancelEndCall(); endTimer = new Timer(); TimerTask action = new TimerTask() { public void run() { scrollTouch = false; if (fProxy.hasListeners(TiC.EVENT_SCROLLEND, false)) { fProxy.fireEvent(TiC.EVENT_SCROLLEND, dictForScrollEvent(), false, false); } } }; this.endTimer.schedule(action, 200); } @Override public void onScrollStateChanged(RecyclerView view, int scrollState) { view.requestDisallowInterceptTouchEvent(scrollState != ViewPager.SCROLL_STATE_IDLE); if (scrollState == RecyclerView.SCROLL_STATE_IDLE) { if (scrollTouch) { delayEndCall(); } } else if (scrollState == RecyclerView.SCROLL_STATE_SETTLING) { cancelEndCall(); } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) { cancelEndCall(); if (hideKeyboardOnScroll && hasFocus()) { blur(); } if (scrollTouch == false) { scrollTouch = true; if (fProxy.hasListeners(TiC.EVENT_SCROLLSTART, false)) { fProxy.fireEvent(TiC.EVENT_SCROLLSTART, dictForScrollEvent(), false, false); } } } } @Override public void onScrolled(RecyclerView view, int dx, int dy) { if (dx == 0 && dy == 0) { return; } if (fProxy.hasListeners(TiC.EVENT_SCROLL, false)) { fProxy.fireEvent(TiC.EVENT_SCROLL, dictForScrollEvent(), false, false); } } }); // mRecyclerView.setOnStickyHeaderChangedListener( // new OnStickyHeaderChangedListener() { // // @Override // public void onStickyHeaderChanged( // StickyListHeadersListViewAbstract l, View header, // int itemPosition, long headerId) { // // for us headerId is the section index // int sectionIndex = (int) headerId; // if (fProxy.hasListeners(TiC.EVENT_HEADER_CHANGE, // false)) { // KrollDict data = new KrollDict(); // AbsListSectionProxy section = null; // synchronized (sections) { // if (sectionIndex >= 0 // && sectionIndex < sections.size()) { // section = sections.get(sectionIndex); // } else { // return; // } // } // data.put(TiC.PROPERTY_HEADER_VIEW, section // .getHoldedProxy(TiC.PROPERTY_HEADER_VIEW)); // data.put(TiC.PROPERTY_SECTION, section); // data.put(TiC.PROPERTY_SECTION_INDEX, sectionIndex); // fProxy.fireEvent(TiC.EVENT_HEADER_CHANGE, data, // false, false); // } // } // }); mRecyclerView.setEnabled(true); getLayoutParams().autoFillsHeight = true; getLayoutParams().autoFillsWidth = true; mRecyclerView.setFocusable(true); mRecyclerView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); // try { // isCheck = TiRHelper.getApplicationResource( // "drawable.btn_check_buttonless_on_64"); // hasChild = TiRHelper.getApplicationResource("drawable.btn_more_64"); // disclosure = TiRHelper // .getApplicationResource("drawable.disclosure_64"); // } catch (ResourceNotFoundException e) { // Log.e(TAG, "XML resources could not be found!!!", Log.DEBUG_MODE); // } // RelativeLayout layout = new RelativeLayout(proxy.getActivity()) { // @Override // protected void onLayout(boolean changed, int left, int top, // int right, int bottom) { // super.onLayout(changed, left, top, right, bottom); // if (changed) { // TiUIHelper.firePostLayoutEvent(TiCollectionView.this); // } // } // // @Override // public void dispatchSetPressed(boolean pressed) { // if (propagateSetPressed(this, pressed)) { // super.dispatchSetPressed(pressed); // } // } // // @Override // public boolean dispatchTouchEvent(MotionEvent event) { // if (touchPassThrough(getParentViewForChild(), event)) // return false; // return super.dispatchTouchEvent(event); // } // // }; // layout.addView(mRecyclerView); setNativeView(mRecyclerView); // needs to be fired after because // getStickyHeadersHolder will be called and need nativeView mRecyclerView.setLayoutManager(layoutManager); mAdapter.setLayoutManager(layoutManager); mRecyclerView.setAdapter(mAdapter); } public void setMarker(HashMap<String, Integer> markerItem) { marker[0] = markerItem.get(TiC.PROPERTY_SECTION_INDEX); marker[1] = markerItem.get(TiC.PROPERTY_ITEM_INDEX); } private void resetMarker() { marker[0] = Integer.MAX_VALUE; marker[1] = Integer.MAX_VALUE; } private Dictionary<Integer, Integer> listViewItemHeights = new Hashtable<Integer, Integer>(); private Dictionary<Integer, Integer> listViewItemWidth = new Hashtable<Integer, Integer>(); private TiPoint getScroll() { int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); View c = mRecyclerView.getChildAt(0); //this is the first visible row int scrollX = -c.getLeft(); int scrollY = -c.getTop(); listViewItemHeights.put(firstVisibleItem, c.getHeight()); listViewItemWidth.put(firstVisibleItem, c.getWidth()); for (int i = 0; i < firstVisibleItem; ++i) { if (listViewItemWidth.get(i) != null) { // (this is a sanity check) scrollX += listViewItemWidth.get(i); //add all heights of the views that are gone } if (listViewItemHeights.get(i) != null) { // (this is a sanity check) scrollY += listViewItemHeights.get(i); //add all heights of the views that are gone } } return new TiPoint(scrollX, scrollY); } private KrollDict dictForScrollEvent(final int xScroll, final int yScroll) { int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); int lastVisiblePosition = layoutManager.findLastVisibleItemPosition(); KrollDict eventArgs = new KrollDict(); KrollDict size = new KrollDict(); size.put(TiC.PROPERTY_WIDTH, TiCollectionView.this.getNativeView().getWidth()); size.put(TiC.PROPERTY_HEIGHT, TiCollectionView.this.getNativeView().getHeight()); eventArgs.put(TiC.PROPERTY_SIZE, size); eventArgs.put("firstVisibleItem", firstVisibleItem); eventArgs.put("visibleItemCount", lastVisiblePosition - firstVisibleItem); eventArgs.put(TiC.PROPERTY_CONTENT_OFFSET, getScroll().toDict()); return eventArgs; } protected KrollDict dictForScrollEvent() { return dictForScrollEvent(mRecyclerView.computeHorizontalScrollOffset(), mRecyclerView.computeVerticalScrollOffset()); } protected void processSections(Object[] sections) { synchronized (this.sections) { this.sections.clear(); } for (int i = 0; i < sections.length; i++) { processSection(sections[i], -1); } } @Override public void processSectionsAndNotify(Object[] sections) { (new ProcessSectionsTask()).execute(sections); // processSections(sections); // if (adapter != null) { // adapter.notifyDataSetChanged(); // } } private class ProcessSectionsTask extends AsyncTask<Object[], Void, Void> { @Override protected Void doInBackground(Object[]... params) { processSections(params[0]); AbsListViewProxy listProxy = (AbsListViewProxy) proxy; listProxy.clearPreloadSections(); listProxy.setPreload(false); return null; } @Override protected void onPostExecute(Void result) { notifyDataSetChanged(); } } protected void processSection(Object sec, int index) { if (sec instanceof CollectionSectionProxy) { CollectionSectionProxy section = (CollectionSectionProxy) sec; section.setListView(this); section.setActivity(proxy.getActivity()); section.setAdapter(mAdapter); synchronized (sections) { if (this.sections.contains(section)) { return; } if (index == -1 || index >= sections.size()) { section.setIndex(this.sections.size()); this.sections.add(section); } else { section.setIndex(index); this.sections.add(index, section); } } // Attempts to set type for existing templates. // section.setTemplateType(); // Process preload data if any section.processPreloadData(); // Apply filter if necessary if (searchText != null) { section.applyFilter(searchText, caseInsensitive, ignoreExactMatch); } } else if (sec instanceof HashMap) { CollectionSectionProxy section = (CollectionSectionProxy) KrollProxy .createProxy(((AbsListViewProxy) proxy).sectionClass(), null, new Object[] { sec }, null); section.getKrollObject(); // make sure the krollobject exists section.updateKrollObjectProperties(); processSection(section, index); } } public Object getItem(int sectionIndex, int itemIndex) { if (sectionIndex < 0 || sectionIndex >= sections.size()) { Log.e(TAG, "getItem Invalid section index"); return null; } synchronized (sections) { return sections.get(sectionIndex).getItemAt(itemIndex); } } public AbsListSectionProxy getSectionAt(int sectionIndex) { synchronized (sections) { if (sectionIndex < 0 || sectionIndex >= sections.size()) { Log.e(TAG, "getItem Invalid section index"); return null; } return sections.get(sectionIndex); } } protected void notifyDataSetChanged() { if (mAdapter != null) { mAdapter.notifySectionDataSetChanged(); } } protected static String getCellProxyRootType() { return "Ti.UI.CollectionItem"; } protected static ArrayList<String> KEY_SEQUENCE; @Override protected ArrayList<String> keySequence() { if (KEY_SEQUENCE == null) { KEY_SEQUENCE = new ArrayList<String>(); KEY_SEQUENCE.add(TiC.PROPERTY_DEFAULT_ITEM_TEMPLATE); KEY_SEQUENCE.add(TiC.PROPERTY_SEARCH_TEXT); // make sure searchText // is set before // sections KEY_SEQUENCE.add(TiC.PROPERTY_NUM_COLUMNS); KEY_SEQUENCE.add(TiC.PROPERTY_COLUMN_WIDTH); } return KEY_SEQUENCE; } // private DragScrollBar mFastScroller; private DividerItemDecoration mDividerDecoration; @Override public void propertySet(String key, Object newValue, Object oldValue, boolean changedProperty) { switch (key) { case "fastScroller": HashMap args = TiConvert.toHashMap(newValue); if (args != null) { mRecyclerView.setFastScrollEnabled(true); mRecyclerView.setVerticalScrollBarEnabled(false); // if (mFastScroller == null) { // mFastScroller = new DragScrollBar(getContext(), // mRecyclerView, false); // mFastScroller.addIndicator(new CustomIndicator(getContext()), // true); // mFastScroller.setZ(100); // mFastScroller.setBackgroundColor(Color.RED);; // } // mFastScroller.setVisibility(View.VISIBLE); if (args.containsKey("color")) { int color = TiConvert.toColor(args, "color"); mRecyclerView.setThumbColor(color); mRecyclerView.setPopupBgColor(color); } // if (args.containsKey("barColor")) { // mFastScroller.setBarColour(TiConvert.toColor(args, // "barColor")); // } // if (args.containsKey("textColour")) { // mFastScroller.setTextColour(TiConvert.toColor(args, // "textColour")); // } } else { mRecyclerView.setFastScrollEnabled(false); mRecyclerView.setVerticalScrollBarEnabled( TiConvert.toBoolean(proxy.getProperty(TiC.PROPERTY_SHOW_VERTICAL_SCROLL_INDICATOR), true)); // if (mFastScroller != null) { // mFastScroller.setVisibility(View.GONE); // } } break; case TiC.PROPERTY_SCROLLING_ENABLED: layoutManager.setScrollEnabled(TiConvert.toBoolean(newValue)); break; case "appearAnimation": if (newValue instanceof HashMap || newValue instanceof TiAnimation) { mAppearAnimation = newValue; mUseAppearAnimation = mAppearAnimation != null; } else { int id = TiConvert.toInt(newValue); if (id != 0) { mAppearAnimators = AnimatorInflater.loadAnimator(getProxy().getActivity(), id); } else { mAppearAnimators = null; } mUseAppearAnimation = mAppearAnimators != null; } break; case "useAppearAnimation": mUseAppearAnimation = TiConvert.toBoolean(newValue, false); break; case TiC.PROPERTY_COLUMN_WIDTH: mColumnsWidth = TiConvert.toTiDimension(newValue, TiDimension.TYPE_WIDTH); if (layoutManager != null) { if (mColumnsWidth != null) { layoutManager.setColumnWidth(mColumnsWidth.getAsPixels(mRecyclerView)); } } break; case TiC.PROPERTY_NUM_COLUMNS: mColumnsWidth = null; mNumColumns = TiConvert.toInt(newValue); if (layoutManager != null) { layoutManager.setNumColumns(mNumColumns); } break; case TiC.PROPERTY_TEMPLATES: processTemplates((HashMap) newValue); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; mProcessUpdateFlags |= TIFLAG_NEEDS_ADAPTER_CHANGE; // if (changedProperty) { // notifyDataSetChanged(); // } break; case TiC.PROPERTY_SEARCH_TEXT: filterBy(TiConvert.toString(newValue)); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; case TiC.PROPERTY_SEARCH_VIEW: setSearchView(newValue, true); break; case TiC.PROPERTY_SEARCH_VIEW_EXTERNAL: setSearchView(newValue, false); break; case TiC.PROPERTY_SCROLL_HIDES_KEYBOARD: this.hideKeyboardOnScroll = TiConvert.toBoolean(newValue, true); break; case TiC.PROPERTY_STICKY_HEADERS: boolean enabled = TiConvert.toBoolean(newValue, true); mAdapter.setStickySectionHeaders(enabled); break; case TiC.PROPERTY_CASE_INSENSITIVE_SEARCH: this.caseInsensitive = TiConvert.toBoolean(newValue, true); filterBy(TiConvert.toString(this.searchText)); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; case TiC.PROPERTY_SEARCH_IGNORE_EXACT_MATCH: this.ignoreExactMatch = TiConvert.toBoolean(newValue, true); filterBy(TiConvert.toString(this.searchText)); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; // case TiC.PROPERTY_SEPARATOR_COLOR: // { // AbsListView internalListView = getInternalListView(); // if (internalListView instanceof ListView) { // int dividerHeight = listView.getDividerHeight(); // ((ListView) internalListView).setDivider(new // ColorDrawable(TiConvert.toColor(newValue))); // ((ListView) internalListView).setDividerHeight(dividerHeight); // } // break; // } // case TiC.PROPERTY_FOOTER_DIVIDERS_ENABLED: // { // AbsListView internalListView = getInternalListView(); // if (internalListView instanceof ListView) { // ((ListView) // internalListView).setFooterDividersEnabled(TiConvert.toBoolean(newValue, // false)); // } // break; // } // case TiC.PROPERTY_HEADER_DIVIDERS_ENABLED: // { // AbsListView internalListView = getInternalListView(); // if (internalListView instanceof ListView) { // ((ListView) // internalListView).setHeaderDividersEnabled(TiConvert.toBoolean(newValue, // false)); // } // break; // } case TiC.PROPERTY_SHOW_VERTICAL_SCROLL_INDICATOR: mRecyclerView.setVerticalScrollBarEnabled(TiConvert.toBoolean(newValue, true)); break; case TiC.PROPERTY_DEFAULT_ITEM_TEMPLATE: defaultTemplateBinding = TiConvert.toString(newValue); if (changedProperty) { notifyDataSetChanged(); } break; case TiC.PROPERTY_SCROLL_DIRECTION: { final String direction = TiConvert.toString(newValue); if (direction.equalsIgnoreCase("horizontal")) { layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); } else { layoutManager.setOrientation(LinearLayoutManager.VERTICAL); } break; } case TiC.PROPERTY_SECTIONS: if (changedProperty) { // mProcessUpdateFlags &= ~TIFLAG_NEEDS_DATASET; processSectionsAndNotify((Object[]) newValue); } else { // if user didn't append/modify/delete sections before this is // called, we process sections // as usual. Otherwise, we process the preloadSections, which // should also contain the section(s) // from this dictionary as well as other sections that user // append/insert/deleted prior to this. AbsListViewProxy listProxy = (AbsListViewProxy) proxy; if (!listProxy.getPreload()) { // mProcessUpdateFlags &= ~TIFLAG_NEEDS_DATASET; processSectionsAndNotify((Object[]) newValue); } } break; case TiC.PROPERTY_SEPARATOR_STYLE: int height = (int) TiUIHelper.getInPixels(newValue); if (height == 0) { // if (mDividerDecoration != null) { // mRecyclerView.removeItemDecoration(mDividerDecoration); // } if (mDividerDecoration != null) { mDividerDecoration.setDividerHeight(height); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; } } else { if (mDividerDecoration == null) { mDividerDecoration = new DividerItemDecoration(getContext()); mRecyclerView.addItemDecoration(mDividerDecoration); } mDividerDecoration.setDividerHeight(height); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; } break; case TiC.PROPERTY_OVER_SCROLL_MODE: // if (Build.VERSION.SDK_INT >= 9) { mRecyclerView.setOverScrollMode(TiConvert.toInt(newValue, View.OVER_SCROLL_ALWAYS)); // } break; case TiC.PROPERTY_HEADER_VIEW: setHeaderOrFooterView(newValue, true); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; case TiC.PROPERTY_HEADER_TITLE: setHeaderTitle(TiConvert.toString(newValue)); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; case TiC.PROPERTY_FOOTER_VIEW: setHeaderOrFooterView(newValue, false); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; case TiC.PROPERTY_FOOTER_TITLE: setFooterTitle(TiConvert.toString(newValue)); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; case "reverseDrawingOrder": boolean reversed = TiConvert.toBoolean(newValue, false); if (reversed) { mRecyclerView.setChildDrawingOrderCallback(new RecyclerView.ChildDrawingOrderCallback() { @Override public int onGetChildDrawingOrder(int childCount, int i) { return childCount - i - 1; } }); } else { mRecyclerView.setChildDrawingOrderCallback(null); } break; default: super.propertySet(key, newValue, oldValue, changedProperty); break; } } @Override protected void aboutToProcessProperties(HashMap d) { super.aboutToProcessProperties(d); updateToPassProps(d); if (mAdapter != null) { mAdapter.setCanNotifyDataSetChanged(false); } if (mRecyclerView.getAdapter() == null) { mProcessUpdateFlags |= TIFLAG_NEEDS_ADAPTER_CHANGE; } } @Override protected void didProcessProperties() { AbsListViewProxy listProxy = (AbsListViewProxy) proxy; if (listProxy.getPreload()) { processSections(listProxy.getPreloadSections().toArray()); listProxy.setPreload(false); listProxy.clearPreloadSections(); } super.didProcessProperties(); if (mAdapter != null) { mAdapter.setCanNotifyDataSetChanged(true); } if ((mProcessUpdateFlags & TIFLAG_NEEDS_ADAPTER_CHANGE) != 0) { mProcessUpdateFlags &= ~TIFLAG_NEEDS_ADAPTER_CHANGE; setListViewAdapter(mAdapter); } if ((mProcessUpdateFlags & TIFLAG_NEEDS_DATASET) != 0) { mProcessUpdateFlags &= ~TIFLAG_NEEDS_DATASET; notifyDataSetChanged(); } } protected void setListViewAdapter(TiBaseAdapter adapter) { mRecyclerView.setAdapter(adapter); } private ViewProxy getOrCreateHeaderWrapperView() { ViewProxy vp = (ViewProxy) this.proxy.getHoldedProxy("headerWrapper"); if (vp == null) { KrollDict props = new KrollDict(); props.put("width", "FILL"); props.put("height", "SIZE"); props.put("layout", "vertical"); props.put("touchPassThrough", true); vp = (ViewProxy) this.proxy.addProxyToHold(props, "headerWrapper"); TiUIView view = ((ViewProxy) vp).getOrCreateView(); view.setCustomLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT, ListView.LayoutParams.WRAP_CONTENT)); mAdapter.addHeaderView(view.getOuterView()); } return vp; } private ViewProxy getOrCreateFooterWrapperView() { ViewProxy vp = (ViewProxy) this.proxy.getHoldedProxy("footerWrapper"); if (vp == null) { KrollDict props = new KrollDict(); props.put("width", "FILL"); props.put("height", "SIZE"); props.put("layout", "vertical"); props.put("touchPassThrough", true); vp = (ViewProxy) this.proxy.addProxyToHold(props, "footerWrapper"); TiUIView view = ((ViewProxy) vp).getOrCreateView(); view.setCustomLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT, ListView.LayoutParams.WRAP_CONTENT)); mAdapter.addFooterView(view.getOuterView()); } return vp; } private static KrollDict DEFAULT_HEADER_DICT = null; public static KrollDict headerViewDict(final String text) { try { if (DEFAULT_HEADER_DICT == null) { // if (TiC.LOLLIPOP_OR_GREATER) { int colorAccent = TiUIHelper.getColorAccent(TiApplication.getAppCurrentActivity()); String color = TiColorHelper.toHexString(colorAccent); DEFAULT_HEADER_DICT = new KrollDict( "{type:'Ti.UI.Label',font:{size:14, weight:'bold'},padding:{top:12, bottom:1},color:'" + color + "',width:'FILL',left:15,right:15}"); // } else { // DEFAULT_HEADER_DICT = new // KrollDict("{type:'Ti.UI.Label',font:{size:14, // weight:'bold'},padding:{left:8, right:8,top:7, // bottom:7},borderPadding:{left:-2.5, right:-2.5, // top:-2.5},borderColor:'#666',borderWidth:2.5,color:'#ccc',width:'FILL',left:15,right:15,autocapitalization:true}"); // } } } catch (JSONException e) { } KrollDict result = new KrollDict(DEFAULT_HEADER_DICT); result.put(TiC.PROPERTY_TEXT, text); return result; } private static KrollDict DEFAULT_FOOTER_DICT = null; public static KrollDict footerViewDict(final String text) { try { if (DEFAULT_FOOTER_DICT == null) { DEFAULT_FOOTER_DICT = new KrollDict( "{type:'Ti.UI.Label',font:{size:14},padding:{left:8, right:8,top:8, bottom:8},color:'#ccc',width:'FILL',left:15,right:15}"); } } catch (JSONException e) { } KrollDict result = new KrollDict(DEFAULT_FOOTER_DICT); result.put(TiC.PROPERTY_TEXT, text); return result; } private void setHeaderOrFooterView(Object viewObj, boolean isHeader) { final String key = isHeader ? "headerView" : "footerView"; KrollProxy viewProxy = proxy.addProxyToHold(viewObj, key, false, true); if (viewProxy instanceof TiViewProxy) { if (isHeader) { getOrCreateHeaderWrapperView().add(viewProxy, 1); } else { getOrCreateFooterWrapperView().add(viewProxy, 1); } } } public void setHeaderTitle(String title) { if (title != null) { ViewProxy vp = (ViewProxy) this.proxy.addProxyToHold(headerViewDict(title), "headerView"); getOrCreateHeaderWrapperView().add(vp, 1); } else { this.proxy.removeHoldedProxy("headerView"); } } public void setFooterTitle(String title) { if (title != null) { ViewProxy vp = (ViewProxy) this.proxy.addProxyToHold(footerViewDict(title), "footerView"); getOrCreateFooterWrapperView().add(vp, 1); } else { this.proxy.removeHoldedProxy("footerView"); } } private void setSearchView(Object viewObj, boolean addInHeader) { KrollProxy viewProxy = proxy.addProxyToHold(viewObj, "search"); if (isSearchViewValid(viewProxy)) { TiUIView search = ((TiViewProxy) viewProxy).getOrCreateView(); setSearchListener((TiViewProxy) viewProxy, search); if (addInHeader) { getOrCreateHeaderWrapperView().add(viewProxy, 0); } } else { Log.e(TAG, "Searchview type is invalid"); } } private void reFilter(String searchText) { synchronized (sections) { for (int i = 0; i < sections.size(); ++i) { AbsListSectionProxy section = sections.get(i); section.applyFilter(searchText, caseInsensitive, ignoreExactMatch); } } notifyDataSetChanged(); } private boolean isSearchViewValid(Object proxy) { if (proxy instanceof SearchBarProxy || proxy instanceof SearchViewProxy) { return true; } else { return false; } } private void setSearchListener(TiViewProxy searchView, TiUIView search) { if (searchView instanceof SearchBarProxy) { ((TiUISearchBar) search).setOnSearchChangeListener(this); } else if (searchView instanceof SearchViewProxy) { ((TiUISearchView) search).setOnSearchChangeListener(this); } } public TiAbsListViewTemplate getTemplate(String template, final boolean canReturnDefault) { if (template == null) { template = defaultTemplateBinding; } if (templatesByBinding.containsKey(template)) { return templatesByBinding.get(template); } if (canReturnDefault) { return templatesByBinding.get(UIModule.LIST_ITEM_TEMPLATE_DEFAULT); } return null; } protected void processTemplates(HashMap<String, Object> templates) { templatesByBinding = new HashMap<String, TiAbsListViewTemplate>(); templatesByType = new SparseArray<TiAbsListViewTemplate>(); templatesByBinding.put(defaultTemplateKey, defaultTemplate); templatesByType.put(defaultTemplate.getType(), defaultTemplate); if (templates != null) { for (String key : templates.keySet()) { HashMap templateDict = (HashMap) templates.get(key); if (templateDict != null) { // Here we bind each template with a key so we can use it to // look up later KrollDict properties = new KrollDict((HashMap) templates.get(key)); TiAbsListViewTemplate template = new TiAbsListViewTemplate(key, properties); final int type = getItemType(); template.setType(type); templatesByBinding.put(key, template); templatesByType.put(type, template); mRecyclerView.getRecycledViewPool().setMaxRecycledViews(type, 20); } else { Log.e(TAG, "null template definition: " + key); } } } } public int getItemType() { return itemTypeCount.getAndIncrement(); } public TiAbsListViewTemplate getTemplateByBinding(String binding) { return templatesByBinding.get(binding); } public String getDefaultTemplateBinding() { return defaultTemplateBinding; } public int getSectionCount() { synchronized (sections) { return sections.size(); } } public void appendSection(Object section) { if (section instanceof Object[]) { Object[] secs = (Object[]) section; for (int i = 0; i < secs.length; i++) { processSection(secs[i], -1); } } else { processSection(section, -1); } notifyDataSetChanged(); } public void deleteSectionAt(int index) { synchronized (sections) { if (index >= 0 && index < sections.size()) { sections.remove(index); notifyDataSetChanged(); } else { Log.e(TAG, "Invalid index to delete section"); } } } public void insertSectionAt(int index, Object section) { synchronized (sections) { if (index > sections.size()) { Log.e(TAG, "Invalid index to insert/replace section"); return; } } if (section instanceof Object[]) { Object[] secs = (Object[]) section; for (int i = 0; i < secs.length; i++) { processSection(secs[i], index); index++; } } else { processSection(section, index); } notifyDataSetChanged(); } public void replaceSectionAt(int index, Object section) { deleteSectionAt(index); insertSectionAt(index, section); } public int findItemPosition(int sectionIndex, int sectionItemIndex, boolean canOverFlow) { int position = 0; synchronized (sections) { for (int i = 0; i < sections.size(); i++) { AbsListSectionProxy section = sections.get(i); if (i == sectionIndex) { int sectionLength = section.getContentCount(); if (sectionItemIndex >= sectionLength) { if (canOverFlow) { // return the last one sectionItemIndex = sectionLength; } else { Log.e(TAG, "Invalid item index"); return -1; } } position += sectionItemIndex; if (section.hasHeader()) { position += 1; } break; } else { position += section.getItemCount(); } } } return position; } @Override public int findItemPosition(int sectionIndex, int sectionItemIndex) { return mAdapter.getFlatPosition(sectionIndex, sectionItemIndex); } public int getHeaderViewCount() { int count = 0; synchronized (sections) { for (int i = 0; i < sections.size(); i++) { AbsListSectionProxy section = sections.get(i); if (section.hasHeader()) { count += 1; } } } return count; } private int getCount() { if (mAdapter != null) { return mAdapter.getItemCount(); } return 0; } public void ensureVisible(int pos) { if (mRecyclerView == null) { return; } if (pos < 0 || pos >= getCount()) { return; } int first = layoutManager.findFirstVisibleItemPosition(); int last = layoutManager.findLastVisibleItemPosition(); if (pos < first) { mRecyclerView.scrollToPosition(pos); return; } if (pos >= last) { mRecyclerView.scrollToPosition(1 + pos - (last - first)); return; } } @Override public void scrollToItem(int sectionIndex, int sectionItemIndex, boolean animated) { final int position = findItemPosition(sectionIndex, sectionItemIndex); if (position > -1) { if (animated) mRecyclerView.smoothScrollToPosition(position); else ensureVisible(position); } } public void scrollToTop(final int y, boolean animated) { if (animated) { mRecyclerView.smoothScrollToPosition(0); } else { mRecyclerView.scrollToPosition(0); } } public void scrollToBottom(final int y, boolean animated) { // strangely if i put getCount()-1 it doesnt go to the full bottom but // make sure the -1 is shown if (animated) { mRecyclerView.smoothScrollToPosition(getCount() - 1); } else { mRecyclerView.scrollToPosition(getCount() - 1); } } @Override public void release() { synchronized (sections) { for (int i = 0; i < sections.size(); i++) { sections.get(i).release(); } sections.clear(); } templatesByBinding.clear(); if (handledProxies != null) { for (TiViewProxy viewProxy : handledProxies) { viewProxy.releaseViews(true); viewProxy.setParent(null); } handledProxies = null; } if (mRecyclerView != null) { mRecyclerView.setAdapter(null); if (mDividerDecoration != null) { mRecyclerView.removeItemDecoration(mDividerDecoration); mDividerDecoration = null; } mRecyclerView = null; } // footerView = null; super.release(); } @Override public void filterBy(String text) { this.searchText = text; reFilter(text); } public AbsListSectionProxy[] getSections() { synchronized (sections) { return sections.toArray(new AbsListSectionProxy[sections.size()]); } } public KrollProxy getChildByBindId(int sectionIndex, int itemIndex, String bindId) { View content = getCellAt(sectionIndex, itemIndex); if (content != null) { TiBaseAbsListViewItem listItem = (TiBaseAbsListViewItem) content .findViewById(TiBaseAbsListViewItem.listContentId); if (listItem != null) { if (listItem.getItemIndex() == itemIndex) { return listItem.getViewProxyFromBinding(bindId); } } } return null; } public View getCellAt(int sectionIndex, int itemIndex) { int position = findItemPosition(sectionIndex, itemIndex); int childCount = layoutManager.getChildCount(); for (int i = 0; i < childCount; i++) { View child = layoutManager.getChildAt(i); TiBaseAbsListViewItem itemContent = (TiBaseAbsListViewItem) child.findViewById(listContentId); if (itemContent != null) { // first visible item of ours int firstposition = findItemPosition(itemContent.getSectionIndex(), itemContent.getItemIndex()); position -= firstposition; break; } else { position++; } } if (position > -1) { View content = layoutManager.getChildAt(position); return content; } return null; } private static HashMap<String, String> TO_PASS_PROPS; private HashMap<String, Object> toPassProps; private void updateToPassProps(HashMap<String, Object> props) { if (props == null || props.size() == 0) { return; } if (TO_PASS_PROPS == null) { TO_PASS_PROPS = new HashMap<String, String>(); TO_PASS_PROPS.put(TiC.PROPERTY_ACCESSORY_TYPE, TiC.PROPERTY_ACCESSORY_TYPE); TO_PASS_PROPS.put(TiC.PROPERTY_SELECTED_BACKGROUND_COLOR, TiC.PROPERTY_BACKGROUND_SELECTED_COLOR); TO_PASS_PROPS.put(TiC.PROPERTY_SELECTED_BACKGROUND_IMAGE, TiC.PROPERTY_BACKGROUND_SELECTED_IMAGE); TO_PASS_PROPS.put(TiC.PROPERTY_SELECTED_BACKGROUND_GRADIENT, TiC.PROPERTY_BACKGROUND_SELECTED_GRADIENT); TO_PASS_PROPS.put(TiC.PROPERTY_ROW_HEIGHT, TiC.PROPERTY_HEIGHT); TO_PASS_PROPS.put(TiC.PROPERTY_COLUMN_WIDTH, TiC.PROPERTY_WIDTH); TO_PASS_PROPS.put(TiC.PROPERTY_MIN_ROW_HEIGHT, TiC.PROPERTY_MIN_HEIGHT); TO_PASS_PROPS.put(TiC.PROPERTY_MAX_ROW_HEIGHT, TiC.PROPERTY_MAX_HEIGHT); } if (toPassProps == null) { toPassProps = new HashMap<>(); } for (Map.Entry<String, Object> entry : props.entrySet()) { String inProp = entry.getKey(); Object outProp = entry.getValue(); if (TO_PASS_PROPS.containsKey(inProp)) { toPassProps.put(TO_PASS_PROPS.get(inProp), outProp); } } } public HashMap<String, Object> getToPassProps() { return toPassProps; } @Override public void onSaveInstanceState(Bundle outState) { mAdapter.onSaveInstanceState(outState); } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { mAdapter.onRestoreInstanceState(savedInstanceState); } @Override public void onStickyHeaderChange(int sectionIndex) { if (proxy.hasListeners(TiC.EVENT_HEADER_CHANGE, false)) { AbsListSectionProxy section = getSectionAt(sectionIndex); KrollDict data = new KrollDict(); Object headerView = section.getHoldedProxy(TiC.PROPERTY_HEADER_VIEW); if (headerView == null) { headerView = section.getProperty(TiC.PROPERTY_HEADER_VIEW); } data.put(TiC.PROPERTY_HEADER_VIEW, headerView); data.put(TiC.PROPERTY_SECTION, section); data.put(TiC.PROPERTY_SECTION_INDEX, sectionIndex); proxy.fireEvent(TiC.EVENT_HEADER_CHANGE, data, false, false); } } public void closeSwipeMenu(Boolean animated, KrollFunction callback) { mOnMenuClosedCallback = callback; if (animated) { mSwipeMenuTouchListener.closeMenusAnimated(); } else { mSwipeMenuTouchListener.closeMenus(); } } @Override public String getSearchText() { return searchText; } @Override public void onStartSwipe(SwipeMenuViewHolder holder, int direction) { // TODO Auto-generated method stub } @Override public void onMenuShown(SwipeMenuViewHolder holder, int direction) { // TODO Auto-generated method stub } private KrollFunction mOnMenuClosedCallback; @Override public void onMenuClosed(SwipeMenuViewHolder holder, int direction) { if (mOnMenuClosedCallback != null) { mOnMenuClosedCallback.callAsync(proxy.getKrollObject(), (HashMap) null); } } @Override public void beforeMenuShow(SwipeMenuViewHolder holder, int direction) { // TODO Auto-generated method stub } @Override public void beforeMenuClose(SwipeMenuViewHolder holder, int direction) { // TODO Auto-generated method stub } public Object getContentOffset() { if (nativeView == null) return proxy.getProperty(TiC.PROPERTY_CONTENT_OFFSET); return getScroll().toDict(); } public void setContentOffset(final Object value, final boolean animated) { if (nativeView == null) return; TiPoint point = TiConvert.toPoint(value); if (point != null) { Point p = point.compute(nativeView.getWidth(), nativeView.getHeight()); if (this.layoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) { scrollToTop(p.x, animated); } else { scrollToTop(p.y, animated); } } } }