package de.marcelpociot.collectionview; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.kroll.common.Log; import org.appcelerator.titanium.TiC; import org.appcelerator.titanium.TiDimension; 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.TiRHelper.ResourceNotFoundException; import org.appcelerator.titanium.view.TiCompositeLayout; import org.appcelerator.titanium.view.TiCompositeLayout.LayoutArrangement; import org.appcelerator.titanium.view.TiCompositeLayout.LayoutParams; import org.appcelerator.titanium.view.TiUIView; import ti.modules.titanium.ui.SearchBarProxy; import ti.modules.titanium.ui.UIModule; import ti.modules.titanium.ui.android.SearchViewProxy; 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.app.Activity; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.util.Pair; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.GridView; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; import de.marcelpociot.collectionview.SwipeRefreshLayout.OnRefreshListener; public class CollectionView extends TiUIView implements OnSearchChangeListener { private TiGridView listView; private TiBaseAdapter adapter; private ArrayList<CollectionSectionProxy> sections; private AtomicInteger itemTypeCount; private String defaultTemplateBinding; private ListViewWrapper wrapper; private HashMap<String, CollectionViewTemplate> templatesByBinding; private int listItemId; public static int listContentId; public static int isCheck; public static int hasChild; public static int disclosure; public static int accessory; private int headerFooterId; public static LayoutInflater inflater; private int titleId; private View headerView; private View footerView; private int[] marker = new int[2]; private String searchText; private boolean caseInsensitive; private RelativeLayout searchLayout; private static final String TAG = "CollectionView"; private CollectionSwipeRefreshLayout layout; public static final String PROPERTY_COLOR_SCHEME = "colorScheme"; int color1 = 0; int color2 = 0; int color3 = 0; int color4 = 0; int layout_swipe_refresh = 0; private Boolean useSwipe = false; /* 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 String MIN_ROW_HEIGHT = "30dp"; public static final int HEADER_FOOTER_WRAP_ID = 12345; public static final int HEADER_FOOTER_VIEW_TYPE = 0; public static final int HEADER_FOOTER_TITLE_TYPE = 1; public static final int BUILT_IN_TEMPLATE_ITEM_TYPE = 2; public static final int CUSTOM_TEMPLATE_ITEM_TYPE = 3; class ListViewWrapper extends FrameLayout { private boolean viewFocused = false; public ListViewWrapper(Context context) { super(context); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // To prevent undesired "focus" and "blur" events during layout caused // by ListView temporarily taking focus, we will disable focus events until // layout has finished. // First check for a quick exit. listView can be null, such as if window closing. // Starting with API 18, calling requestFocus() will trigger another layout pass of the listview, // resulting in an infinite loop. Here we check if the view is already focused, and stop the loop. if (listView == null || (Build.VERSION.SDK_INT >= 18 && listView != null && !changed && viewFocused)) { viewFocused = false; super.onLayout(changed, left, top, right, bottom); return; } OnFocusChangeListener focusListener = null; View focusedView = listView.findFocus(); int cursorPosition = -1; if (focusedView != null) { OnFocusChangeListener listener = focusedView.getOnFocusChangeListener(); if (listener != null && listener instanceof TiUIView) { //Before unfocus the current editText, store cursor position so //we can restore it later if (focusedView instanceof EditText) { cursorPosition = ((EditText)focusedView).getSelectionStart(); } focusedView.setOnFocusChangeListener(null); focusListener = listener; } } //We are temporarily going to block focus to descendants //because LinearLayout on layout will try to find a focusable descendant if (focusedView != null) { listView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); } super.onLayout(changed, left, top, right, bottom); //Now we reset the descendant focusability listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); TiViewProxy viewProxy = proxy; if (viewProxy != null && viewProxy.hasListeners(TiC.EVENT_POST_LAYOUT)) { viewProxy.fireEvent(TiC.EVENT_POST_LAYOUT, null); } // Layout is finished, re-enable focus events. if (focusListener != null) { // If the configuration changed, we manually fire the blur event if (changed) { focusedView.setOnFocusChangeListener(focusListener); 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); } } } } } public class TiBaseAdapter extends BaseAdapter { Activity context; public TiBaseAdapter(Activity activity) { context = activity; } @Override public int getCount() { int count = 0; for (int i = 0; i < sections.size(); i++) { CollectionSectionProxy section = sections.get(i); count += section.getItemCount(); } return count; } @Override public Object getItem(int arg0) { //not using this method return arg0; } @Override public long getItemId(int position) { //not using this method return position; } //One type for header/footer title, one for header/footer view, one for built-in template, and one type per custom template. @Override public int getViewTypeCount() { return 3 + templatesByBinding.size(); } @Override public int getItemViewType(int position) { Pair<CollectionSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(position); CollectionSectionProxy section = info.first; int sectionItemIndex = info.second.second; if (section.isHeaderTitle(sectionItemIndex) || section.isFooterTitle(sectionItemIndex)) return HEADER_FOOTER_TITLE_TYPE; if (section.isHeaderView(sectionItemIndex) || section.isFooterView(sectionItemIndex)) { return HEADER_FOOTER_VIEW_TYPE; } return section.getTemplateByIndex(sectionItemIndex).getType(); } @Override public View getView(int position, View convertView, ViewGroup parent) { //Get section info from index Pair<CollectionSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(position); CollectionSectionProxy section = info.first; int sectionItemIndex = info.second.second; int sectionIndex = info.second.first; //check marker if (sectionIndex > marker[0] || (sectionIndex == marker[0] && sectionItemIndex >= marker[1])) { proxy.fireEvent(TiC.EVENT_MARKER, null, false); resetMarker(); } View content = convertView; //Handles header/footer views and titles. if (section.isHeaderView(sectionItemIndex) || section.isFooterView(sectionItemIndex)) { return section.getHeaderOrFooterView(sectionItemIndex); } else if (section.isHeaderTitle(sectionItemIndex) || section.isFooterTitle(sectionItemIndex)) { //No content to reuse, so we create a new view if (content == null) { content = inflater.inflate(headerFooterId, null); } TextView title = (TextView)content.findViewById(titleId); title.setText(section.getHeaderOrFooterTitle(sectionItemIndex)); return content; } //Handling templates KrollDict data = section.getCollectionItemData(sectionItemIndex); CollectionViewTemplate template = section.getTemplateByIndex(sectionItemIndex); if (content != null) { BaseCollectionViewItem itemContent = (BaseCollectionViewItem) content.findViewById(listContentId); section.populateViews(data, itemContent, template, sectionItemIndex, sectionIndex, content); } else { content = inflater.inflate(listItemId, null); BaseCollectionViewItem itemContent = (BaseCollectionViewItem) content.findViewById(listContentId); LayoutParams params = new LayoutParams(); params.autoFillsWidth = true; itemContent.setLayoutParams(params); section.generateCellContent(sectionIndex, data, template, itemContent, sectionItemIndex, content); } return content; } } public CollectionView(TiViewProxy proxy, Activity activity) { super(proxy); final TiViewProxy viewProxy = proxy; //initializing variables sections = new ArrayList<CollectionSectionProxy>(); itemTypeCount = new AtomicInteger(CUSTOM_TEMPLATE_ITEM_TYPE); templatesByBinding = new HashMap<String, CollectionViewTemplate>(); defaultTemplateBinding = UIModule.LIST_ITEM_TEMPLATE_DEFAULT; caseInsensitive = true; //handling marker HashMap<String, Integer> preloadMarker = ((CollectionViewProxy)proxy).getPreloadMarker(); if (preloadMarker != null) { setMarker(preloadMarker); } else { resetMarker(); } if (proxy.getProperty("swipeRefresh") != null) { useSwipe = TiConvert.toBoolean(proxy.getProperty("swipeRefresh")); } //init inflater if (inflater == null) { inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } try { if (useSwipe) { layout_swipe_refresh = TiRHelper.getResource("layout.swipe_refresh"); color1 = TiRHelper.getResource("color.swipe_color1"); color2 = TiRHelper.getResource("color.swipe_color2"); color3 = TiRHelper.getResource("color.swipe_color3"); color4 = TiRHelper.getResource("color.swipe_color4"); layout = (CollectionSwipeRefreshLayout) inflater.inflate(layout_swipe_refresh, null, false); layout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { if (viewProxy.hasListeners("refreshing")) { viewProxy.fireEvent("refreshing", null); } } }); } headerFooterId = TiRHelper.getResource("layout.ticollection_ui_list_header_or_footer"); listItemId = TiRHelper.getResource("layout.titanium_ui_collection_item"); titleId = TiRHelper.getResource("id.ticollection_ui_list_header_or_footer_title"); listContentId = TiRHelper.getResource("id.titanium_ui_collection_item_content"); isCheck = TiRHelper.getResource("drawable.btn_check_buttonless_on_64"); hasChild = TiRHelper.getResource("drawable.btn_more_64"); disclosure = TiRHelper.getResource("drawable.disclosure_64"); accessory = TiRHelper.getResource("id.titanium_ui_collection_item_accessoryType"); } catch (ResourceNotFoundException e) { Log.e(TAG, "XML resources could not be found!!!", Log.DEBUG_MODE); } //initializing listView and adapter ListViewWrapper wrapper = new ListViewWrapper(activity); wrapper.setFocusable(false); wrapper.setFocusableInTouchMode(false); listView = new TiGridView(activity); listView.setNumColumns( GridView.AUTO_FIT ); listView.setColumnWidth( TiConvert.toInt( proxy.getProperty("columnWidth") ) ); listView.setVerticalSpacing( TiConvert.toInt( proxy.getProperty("verticalSpacing") ) ); listView.setHorizontalSpacing( TiConvert.toInt( proxy.getProperty("horizontalSpacing") ) ); listView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); wrapper.addView(listView); adapter = new TiBaseAdapter(activity); listView.setCacheColorHint(Color.TRANSPARENT); getLayoutParams().autoFillsHeight = true; getLayoutParams().autoFillsWidth = true; listView.setFocusable(true); listView.setFocusableInTouchMode(true); listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); this.wrapper = wrapper; if (useSwipe) { layout.setNativeView(wrapper); layout.addView(wrapper); setNativeView(layout); } else { setNativeView(wrapper); } } public boolean isRefreshing() { return useSwipe && this.layout.isRefreshing(); } public void setRefreshing(boolean refreshing) { if (useSwipe) { this.layout.setRefreshing(refreshing); } } public String getSearchText() { return searchText; } public boolean getCaseInsensitive() { return caseInsensitive; } private void resetMarker() { marker[0] = Integer.MAX_VALUE; marker[1] = Integer.MAX_VALUE; } public void setHeaderTitle(String title) { TextView textView = (TextView) headerView.findViewById(titleId); textView.setText(title); if (textView.getVisibility() == View.GONE) { textView.setVisibility(View.VISIBLE); } } public void setFooterTitle(String title) { TextView textView = (TextView) footerView.findViewById(titleId); textView.setText(title); if (textView.getVisibility() == View.GONE) { textView.setVisibility(View.VISIBLE); } } @Override public void registerForTouch() { registerForTouch(listView); } public void setMarker(HashMap<String, Integer> markerItem) { marker[0] = markerItem.get(TiC.PROPERTY_SECTION_INDEX); marker[1] = markerItem.get(TiC.PROPERTY_ITEM_INDEX); } public void processProperties(KrollDict d) { if (d.containsKey(TiC.PROPERTY_TEMPLATES)) { Object templates = d.get(TiC.PROPERTY_TEMPLATES); if (templates != null) { processTemplates(new KrollDict((HashMap)templates)); } } if (d.containsKey(TiC.PROPERTY_SEARCH_TEXT)) { this.searchText = TiConvert.toString(d, TiC.PROPERTY_SEARCH_TEXT); } if (d.containsKey(TiC.PROPERTY_SEARCH_VIEW)) { TiViewProxy searchView = (TiViewProxy) d.get(TiC.PROPERTY_SEARCH_VIEW); if (isSearchViewValid(searchView)) { TiUIView search = searchView.getOrCreateView(); setSearchListener(searchView, search); layoutSearchView(searchView); } else { Log.e(TAG, "Searchview type is invalid"); } } if (d.containsKey(TiC.PROPERTY_CASE_INSENSITIVE_SEARCH)) { this.caseInsensitive = TiConvert.toBoolean(d, TiC.PROPERTY_CASE_INSENSITIVE_SEARCH, true); } if (d.containsKey(TiC.PROPERTY_SEPARATOR_COLOR)) { String color = TiConvert.toString(d, TiC.PROPERTY_SEPARATOR_COLOR); setSeparatorColor(color); } if (d.containsKey(TiC.PROPERTY_SHOW_VERTICAL_SCROLL_INDICATOR)) { listView.setVerticalScrollBarEnabled(TiConvert.toBoolean(d, TiC.PROPERTY_SHOW_VERTICAL_SCROLL_INDICATOR, true)); } if (d.containsKey(TiC.PROPERTY_DEFAULT_ITEM_TEMPLATE)) { defaultTemplateBinding = TiConvert.toString(d, TiC.PROPERTY_DEFAULT_ITEM_TEMPLATE); } CollectionViewProxy listProxy = (CollectionViewProxy) proxy; if (d.containsKey(TiC.PROPERTY_SECTIONS)) { //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. if (!listProxy.getPreload()) { processSections((Object[])d.get(TiC.PROPERTY_SECTIONS)); } else { processSections(listProxy.getPreloadSections().toArray()); } } else if (listProxy.getPreload()) { //if user didn't specify 'sections' property upon creation of listview but append/insert it afterwards //we process them instead. processSections(listProxy.getPreloadSections().toArray()); } listProxy.clearPreloadSections(); listProxy.setPreload(false); if (d.containsKey(TiC.PROPERTY_HEADER_VIEW)) { Object viewObj = d.get(TiC.PROPERTY_HEADER_VIEW); setHeaderOrFooterView(viewObj, true); } else if (d.containsKey(TiC.PROPERTY_HEADER_TITLE)) { headerView = inflater.inflate(headerFooterId, null); setHeaderTitle(TiConvert.toString(d, TiC.PROPERTY_HEADER_TITLE)); } if (d.containsKey(TiC.PROPERTY_FOOTER_VIEW)) { Object viewObj = d.get(TiC.PROPERTY_FOOTER_VIEW); setHeaderOrFooterView(viewObj, false); } else if (d.containsKey(TiC.PROPERTY_FOOTER_TITLE)) { footerView = inflater.inflate(headerFooterId, null); setFooterTitle(TiConvert.toString(d, TiC.PROPERTY_FOOTER_TITLE)); } //Check to see if headerView and footerView are specified. If not, we hide the views if (headerView == null) { headerView = inflater.inflate(headerFooterId, null); headerView.findViewById(titleId).setVisibility(View.GONE); } if (footerView == null) { footerView = inflater.inflate(headerFooterId, null); footerView.findViewById(titleId).setVisibility(View.GONE); } //Have to add header and footer before setting adapter listView.addHeaderView(headerView, null, false); listView.addFooterView(footerView, null, false); listView.setAdapter(adapter); super.processProperties(d); } private void layoutSearchView(TiViewProxy searchView) { TiUIView search = searchView.getOrCreateView(); RelativeLayout layout = new RelativeLayout(proxy.getActivity()); layout.setGravity(Gravity.NO_GRAVITY); layout.setPadding(0, 0, 0, 0); addSearchLayout(layout, searchView, search); setNativeView(layout); } private void addSearchLayout(RelativeLayout layout, TiViewProxy searchView, TiUIView search) { RelativeLayout.LayoutParams p = createBasicSearchLayout(); p.addRule(RelativeLayout.ALIGN_PARENT_TOP); TiDimension rawHeight; if (searchView.hasProperty(TiC.PROPERTY_HEIGHT)) { rawHeight = TiConvert.toTiDimension(searchView.getProperty(TiC.PROPERTY_HEIGHT), 0); } else { rawHeight = TiConvert.toTiDimension(MIN_SEARCH_HEIGHT, 0); } p.height = rawHeight.getAsPixels(layout); View nativeView = search.getNativeView(); layout.addView(nativeView, p); p = createBasicSearchLayout(); p.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); p.addRule(RelativeLayout.BELOW, nativeView.getId()); ViewParent parentWrapper = wrapper.getParent(); if (parentWrapper != null && parentWrapper instanceof ViewGroup) { //get the previous layout params so we can reset with new layout ViewGroup.LayoutParams lp = wrapper.getLayoutParams(); ViewGroup parentView = (ViewGroup) parentWrapper; //remove view from parent parentView.removeView(wrapper); //add new layout layout.addView(wrapper, p); parentView.addView(layout, lp); } else { layout.addView(wrapper, p); } this.searchLayout = layout; } private RelativeLayout.LayoutParams createBasicSearchLayout() { RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); p.addRule(RelativeLayout.ALIGN_PARENT_LEFT); p.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); return p; } private void setHeaderOrFooterView (Object viewObj, boolean isHeader) { if (viewObj instanceof TiViewProxy) { TiViewProxy viewProxy = (TiViewProxy)viewObj; View view = layoutHeaderOrFooterView(viewProxy); if (view != null) { if (isHeader) { headerView = view; } else { footerView = view; } } } } private void reFilter(String searchText) { if (searchText != null) { for (int i = 0; i < sections.size(); ++i) { CollectionSectionProxy section = sections.get(i); section.applyFilter(searchText); } } if (adapter != null) { adapter.notifyDataSetChanged(); } } private boolean isSearchViewValid(TiViewProxy proxy) { if (proxy instanceof SearchBarProxy || proxy instanceof SearchViewProxy) { return true; } else { return false; } } public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) { if (key.equals(TiC.PROPERTY_HEADER_TITLE)) { setHeaderTitle(TiConvert.toString(newValue)); } else if (key.equals(TiC.PROPERTY_FOOTER_TITLE)) { setFooterTitle(TiConvert.toString(newValue)); } else if (key.equals(TiC.PROPERTY_SECTIONS) && newValue instanceof Object[] ) { processSectionsAndNotify((Object[])newValue); } else if (key.equals(TiC.PROPERTY_SEARCH_TEXT)) { this.searchText = TiConvert.toString(newValue); if (this.searchText != null) { reFilter(this.searchText); } } else if (key.equals(TiC.PROPERTY_CASE_INSENSITIVE_SEARCH)) { this.caseInsensitive = TiConvert.toBoolean(newValue, true); if (this.searchText != null) { reFilter(this.searchText); } } else if (key.equals(TiC.PROPERTY_SEARCH_VIEW)) { TiViewProxy searchView = (TiViewProxy) newValue; if (isSearchViewValid(searchView)) { TiUIView search = searchView.getOrCreateView(); setSearchListener(searchView, search); if (searchLayout != null) { searchLayout.removeAllViews(); addSearchLayout(searchLayout, searchView, search); } else { layoutSearchView(searchView); } } else { Log.e(TAG, "Searchview type is invalid"); } } else if (key.equals(TiC.PROPERTY_SHOW_VERTICAL_SCROLL_INDICATOR) && newValue != null) { listView.setVerticalScrollBarEnabled(TiConvert.toBoolean(newValue)); } else if (key.equals(TiC.PROPERTY_DEFAULT_ITEM_TEMPLATE) && newValue != null) { defaultTemplateBinding = TiConvert.toString(newValue); refreshItems(); } else if (key.equals(TiC.PROPERTY_SEPARATOR_COLOR)) { String color = TiConvert.toString(newValue); setSeparatorColor(color); } else { super.propertyChanged(key, oldValue, newValue, proxy); } } private void setSearchListener(TiViewProxy searchView, TiUIView search) { if (searchView instanceof SearchBarProxy) { ((TiUISearchBar)search).setOnSearchChangeListener(this); } else if (searchView instanceof SearchViewProxy) { ((TiUISearchView)search).setOnSearchChangeListener(this); } } private void setSeparatorColor(String color) { int sepColor = TiColorHelper.parseColor(color); } private void refreshItems() { for (int i = 0; i < sections.size(); i++) { CollectionSectionProxy section = sections.get(i); section.refreshItems(); } } protected void processTemplates(KrollDict templates) { for (String key : templates.keySet()) { //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)); CollectionViewTemplate template = new CollectionViewTemplate(key, properties); //Set type to template, for recycling purposes. template.setType(getItemType()); templatesByBinding.put(key, template); //set parent of root item template.setRootParent(proxy); } } public View layoutHeaderOrFooterView (TiViewProxy viewProxy) { TiUIView tiView = viewProxy.peekView(); if (tiView != null) { TiViewProxy parentProxy = viewProxy.getParent(); //Remove parent view if possible if (parentProxy != null) { TiUIView parentView = parentProxy.peekView(); if (parentView != null) { parentView.remove(tiView); } } } else { tiView = viewProxy.forceCreateView(); } View outerView = tiView.getOuterView(); ViewGroup parentView = (ViewGroup) outerView.getParent(); if (parentView != null && parentView.getId() == HEADER_FOOTER_WRAP_ID) { return parentView; } else { //add a wrapper so layout params such as height, width takes in effect. TiCompositeLayout wrapper = new TiCompositeLayout(viewProxy.getActivity(), LayoutArrangement.DEFAULT, null); AbsListView.LayoutParams params = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT); wrapper.setLayoutParams(params); outerView = tiView.getOuterView(); wrapper.addView(outerView, tiView.getLayoutParams()); wrapper.setId(HEADER_FOOTER_WRAP_ID); return wrapper; } } protected void processSections(Object[] sections) { this.sections.clear(); for (int i = 0; i < sections.length; i++) { processSection(sections[i], -1); } } protected void processSectionsAndNotify(Object[] sections) { processSections(sections); if (adapter != null) { adapter.notifyDataSetChanged(); } } protected void processSection(Object sec, int index) { if (sec instanceof CollectionSectionProxy) { CollectionSectionProxy section = (CollectionSectionProxy) sec; if (this.sections.contains(section)) { return; } if (index == -1 || index >= sections.size()) { this.sections.add(section); } else { this.sections.add(index, section); } section.setAdapter(adapter); section.setListView(this); //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); } } } protected Pair<CollectionSectionProxy, Pair<Integer, Integer>> getSectionInfoByEntryIndex(int index) { if (index < 0) { return null; } for (int i = 0; i < sections.size(); i++) { CollectionSectionProxy section = sections.get(i); int sectionItemCount = section.getItemCount(); if (index <= sectionItemCount - 1) { return new Pair<CollectionSectionProxy, Pair<Integer, Integer>>(section, new Pair<Integer, Integer>(i, index)); } else { index -= sectionItemCount; } } return null; } public int getItemType() { return itemTypeCount.getAndIncrement(); } public CollectionViewTemplate getTemplateByBinding(String binding) { return templatesByBinding.get(binding); } public String getDefaultTemplateBinding() { return defaultTemplateBinding; } public int getSectionCount() { 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); } adapter.notifyDataSetChanged(); } public void deleteSectionAt(int index) { if (index >= 0 && index < sections.size()) { sections.remove(index); adapter.notifyDataSetChanged(); } else { Log.e(TAG, "Invalid index to delete section"); } } public void insertSectionAt(int index, Object section) { 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); } adapter.notifyDataSetChanged(); } public void replaceSectionAt(int index, Object section) { deleteSectionAt(index); insertSectionAt(index, section); } private int findItemPosition(int sectionIndex, int sectionItemIndex) { int position = 0; for (int i = 0; i < sections.size(); i++) { CollectionSectionProxy section = sections.get(i); if (i == sectionIndex) { if (sectionItemIndex >= section.getContentCount()) { Log.e(TAG, "Invalid item index"); return -1; } position += sectionItemIndex; break; } else { position += section.getItemCount(); } } return position; } protected void scrollToItem(int sectionIndex, int sectionItemIndex, boolean animated) { final int position = findItemPosition(sectionIndex, sectionItemIndex); if (position > -1) { if (animated) { listView.smoothScrollToPosition(position + 1); } else { listView.post(new Runnable() { @Override public void run() { listView.setSelection(position + 1); } }); } } } public void release() { for (int i = 0; i < sections.size(); i++) { sections.get(i).releaseViews(); } templatesByBinding.clear(); sections.clear(); if (wrapper != null) { wrapper = null; } if (listView != null) { listView.setAdapter(null); listView = null; } if (headerView != null) { headerView = null; } if (footerView != null) { footerView = null; } super.release(); } @Override public void filterBy(String text) { this.searchText = text; reFilter(text); } public CollectionSectionProxy[] getSections() { return sections.toArray(new CollectionSectionProxy[sections.size()]); } }