Java tutorial
/** * Appcelerator Titanium Mobile * Copyright (c) 2013 by Appcelerator, Inc. 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.abslistview; import java.util.ArrayList; import java.util.Arrays; 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.KrollProxy; import org.appcelerator.kroll.common.Log; import org.appcelerator.titanium.TiApplication; import org.appcelerator.titanium.TiC; import org.appcelerator.titanium.TiPoint; 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.TiCompositeLayout.LayoutArrangement; import org.appcelerator.titanium.view.TiUINonViewGroupView; import org.appcelerator.titanium.view.TiUIView; import org.json.JSONException; import com.nhaarman.listviewanimations.ListViewAnimationsBaseAdapter; import com.nhaarman.listviewanimations.itemmanipulation.DynamicListItemView; import com.nhaarman.listviewanimations.itemmanipulation.DynamicStickyListHeadersAbsListViewInterface; import com.nhaarman.listviewanimations.itemmanipulation.DynamicWrapperViewList; import com.nhaarman.listviewanimations.itemmanipulation.swipemenu.MenuAdapter; import com.nhaarman.listviewanimations.util.Insertable; import com.nhaarman.listviewanimations.util.Removable; import se.emilsjolander.stickylistheaders.OnStickyHeaderChangedListener; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListViewAbstract; import se.emilsjolander.stickylistheaders.WrapperView; import ti.modules.titanium.ui.RefreshControlProxy; import ti.modules.titanium.ui.SearchBarProxy; import ti.modules.titanium.ui.UIModule; import ti.modules.titanium.ui.ViewProxy; import android.annotation.SuppressLint; import ti.modules.titanium.ui.android.SearchViewProxy; import ti.modules.titanium.ui.widget.CustomListView; import ti.modules.titanium.ui.widget.TiUITableView; //import ti.modules.titanium.ui.widget.abslistview.AbsListSectionProxy.AbsListItemData; 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 yaochangwei.pulltorefreshlistview.widget.RefreshableListView; import yaochangwei.pulltorefreshlistview.widget.RefreshableListView.OnPullListener; import android.app.Activity; import android.graphics.Color; import android.graphics.Point; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.support.v4.view.ViewPager; import android.util.Pair; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.FrameLayout; import android.widget.ListView; import android.widget.SectionIndexer; import android.widget.AbsListView.OnScrollListener; @SuppressLint("NewApi") public abstract class TiAbsListView<C extends StickyListHeadersListViewAbstract & DynamicStickyListHeadersAbsListViewInterface> extends TiUINonViewGroupView implements OnSearchChangeListener, TiCollectionViewInterface { protected C listView; private TiBaseAdapter adapter; private List<AbsListSectionProxy> sections; private AtomicInteger itemTypeCount; private String defaultTemplateBinding; private HashMap<String, TiAbsListViewTemplate> templatesByBinding; // public static int isCheck; // public static int hasChild; // public static int disclosure; // public static int accessory = 24124; private int[] marker = new int[2]; private String searchText; private boolean caseInsensitive; private boolean ignoreExactMatch; private static final String TAG = "TiListView"; private boolean hideKeyboardOnScroll = true; private boolean canShowMenus = false; protected static final int TIFLAG_NEEDS_DATASET = 0x00000001; protected static final int TIFLAG_NEEDS_ADAPTER_CHANGE = 0x00000002; private Set<TiViewProxy> handledProxies; private int currentScrollOffset = -1; 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_FOOTER_VIEW_TYPE = 0; public static final int BUILT_IN_TEMPLATE_ITEM_TYPE = 1; public static final int CUSTOM_TEMPLATE_ITEM_TYPE = 2; private void addHandledProxy(final TiViewProxy proxy) { if (handledProxies == null) { handledProxies = new HashSet<TiViewProxy>(); } handledProxies.add(proxy); } // private void removeHandledProxy(final TiViewProxy proxy) { // if (handledProxies == null) { // return; // } // handledProxies.remove(proxy); // } private AbsListView getInternalListView() { return listView.getWrappedList(); } protected static String getCellProxyRootType() { return "Ti.UI.ListItem"; } 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_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; } public class TiBaseAdapter extends ListViewAnimationsBaseAdapter implements StickyListHeadersAdapter, SectionIndexer, MenuAdapter, Insertable<Object>, Removable<Object>, TiCollectionViewAdapter { Activity context; private boolean mCounted = false; private int mCount = 0; private boolean canNotifyDataSetChanged = true; public TiBaseAdapter(Activity activity) { super(); context = activity; } public boolean hasStableIds() { return true; } @Override public int getCount() { if (mCounted) { return mCount; } mCount = 0; synchronized (sections) { for (int i = 0; i < sections.size(); i++) { AbsListSectionProxy section = sections.get(i); mCount += section.getItemCount(); } } mCounted = true; return mCount; } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { 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 itemTypeCount.get(); } @Override public int getItemViewType(int position) { Pair<AbsListSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(position); if (info == null) { return -1; } AbsListSectionProxy section = info.first; int sectionItemIndex = info.second.second; if (section.isHeaderView(sectionItemIndex) || section.isFooterView(sectionItemIndex)) { return HEADER_FOOTER_VIEW_TYPE; } return getTemplate(section.getTemplateByIndex(sectionItemIndex), true).getType(); } @Override public View getView(int position, View convertView, ViewGroup parent) { //Get section info from index Pair<AbsListSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(position); if (info == null) { return null; // possible because of WrapperView } AbsListSectionProxy section = info.first; if (section.hidden) { return null; // possible because of WrapperView } int sectionItemIndex = info.second.second; int sectionIndex = info.second.first; //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(); } View content = convertView; if (section.isFooterView(sectionItemIndex)) { KrollProxy vp = section.getHoldedProxy(TiC.PROPERTY_FOOTER_VIEW); if (vp instanceof TiViewProxy) { return layoutHeaderOrFooterView((TiViewProxy) vp, convertView); } return null; } //Handling templates Object item = section.getListItem(sectionItemIndex); if (item == null) { return null; } String templateId = null; if (item instanceof HashMap) { templateId = TiConvert.toString((HashMap) item, TiC.PROPERTY_TEMPLATE); } TiAbsListViewTemplate template = getTemplate(templateId, true); int itemViewType = template.getType(); TiBaseAbsListViewItem itemContent = null; if (content != null && (int) content.getTag() == itemViewType) { itemContent = (TiBaseAbsListViewItem) content.findViewById(TiBaseAbsListViewItem.listContentId); if (itemContent == null) { return content; } boolean reusing = sectionIndex != itemContent.sectionIndex || itemContent.itemIndex >= section.getItemCount() || item != section.getListItem(itemContent.itemIndex); section.populateViews(item, itemContent, template, sectionItemIndex, sectionIndex, content, reusing); // setBoundsForBaseItem(itemContent); } else { content = new TiBaseAbsListViewItemHolder(getContext()); content.setTag(itemViewType); itemContent = (TiBaseAbsListViewItem) content.findViewById(TiBaseAbsListViewItem.listContentId); AbsListItemProxy itemProxy = template.generateCellProxy(proxy, getCellProxyRootType()); itemProxy.setListProxy(proxy); addHandledProxy(itemProxy); section.generateCellContent(sectionIndex, item, itemProxy, itemContent, template, sectionItemIndex, content); } if (content instanceof TiBaseAbsListViewItemHolder) { ((TiBaseAbsListViewItemHolder) content).setItem(itemContent, item, listView); } // setBoundsForBaseItem(content, itemContent, item); canShowMenus |= itemContent.getListItem().canShowMenus(); return content; } public void setCanNotifyDataSetChanged(final boolean canNotifyDataSetChanged) { this.canNotifyDataSetChanged = canNotifyDataSetChanged; } // private void setBoundsForBaseItem(View content) { // TiBaseAbsListViewItemHolder holder; // if (content instanceof TiBaseAbsListViewItemHolder) // { // holder = (TiBaseAbsListViewItemHolder) content; // } // else if (content instanceof TiBorderWrapperView) // { // holder = (TiBaseAbsListViewItemHolder) content.getParent(); // } // else return; // String minRowHeight = MIN_ROW_HEIGHT; // if (proxy != null && proxy.hasProperty(TiC.PROPERTY_MIN_ROW_HEIGHT)) { // minRowHeight = TiConvert.toString(proxy.getProperty(TiC.PROPERTY_MIN_ROW_HEIGHT)); // } // item.setMinHeight(TiConvert.toTiDimension(minRowHeight, TiDimension.TYPE_HEIGHT)); // if (proxy == null) return; // if (proxy.hasProperty(TiC.PROPERTY_MAX_ROW_HEIGHT)) { // item.setMaxHeight(TiConvert.toTiDimension(proxy.getProperty(TiC.PROPERTY_MAX_ROW_HEIGHT), TiDimension.TYPE_HEIGHT)); // } // } @Override public void notifyDataSetChanged() { if (!canNotifyDataSetChanged) { return; } canShowMenus = false; mCounted = false; // mSectionInfoCache.clear(); if (listView != null) { // save index and top position int index = listView.getFirstVisiblePosition(); View v = listView.getListChildAt(0); int top = (v == null) ? 0 : v.getTop(); super.notifyDataSetChanged(); // restore // listView.setSelectionFromTop(index, top); } else { super.notifyDataSetChanged(); } } @Override public long getHeaderId(int position) { //Get section info from index Pair<AbsListSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(position); if (info != null) { AbsListSectionProxy section = info.first; return section.getIndex(); } return -1; } @Override public View getHeaderView(int position, View convertView, ViewGroup parent) { //Get section info from index Pair<AbsListSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(position); if (info != null) { AbsListSectionProxy section = info.first; KrollProxy vp = section.getHoldedProxy(TiC.PROPERTY_HEADER_VIEW); if (vp instanceof TiViewProxy) { return layoutHeaderOrFooterView((TiViewProxy) vp, convertView); } } if (convertView != null) { return convertView; } //StickyListHeaderView always wants a header return new FrameLayout(getContext()); } @Override public Object[] getSections() { synchronized (sections) { return sections.toArray(); } } @Override public int getPositionForSection(int sectionIndex) { return getSectionFirstPosition(sectionIndex); } @Override public int getSectionForPosition(int position) { Pair<AbsListSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(position); if (info != null) { AbsListSectionProxy section = info.first; return section.getIndex(); } return -1; } @Override public Object remove(int position) { // Pair<AbsListSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(position); // Object result = null; // if (info != null) { // result = info.first.deleteItemsData(info.second.second, 1); // } notifyDataSetChanged(); // return result; return null; } @Override public void add(int position, Object data) { // Pair<AbsListSectionProxy, Pair<Integer, Integer>> info = getSectionInfoByEntryIndex(Math.max(0, position - 1)); // if (info != null) { // info.first.insertItemsData(info.second.second + 1, data); // } notifyDataSetChanged(); } @Override public boolean canShowLeftMenu(int position, final DynamicListItemView view) { if (!canShowMenus) return false; TiBaseAbsListViewItem viewItem = (TiBaseAbsListViewItem) view .findViewById(TiBaseAbsListViewItem.listContentId); if (viewItem != null) { TiAbsListItem listItem = viewItem.getListItem(); return listItem.canShowLeftMenu(); } return false; } @Override public boolean canShowRightMenu(int position, final DynamicListItemView view) { if (!canShowMenus) return false; TiBaseAbsListViewItem viewItem = (TiBaseAbsListViewItem) view .findViewById(TiBaseAbsListViewItem.listContentId); if (viewItem != null) { TiAbsListItem listItem = viewItem.getListItem(); return listItem.canShowRightMenu(); } return false; } @Override public View[] getLeftButtons(int position, final DynamicListItemView view) { TiBaseAbsListViewItem viewItem = (TiBaseAbsListViewItem) view .findViewById(TiBaseAbsListViewItem.listContentId); if (viewItem != null) { TiAbsListItem listItem = viewItem.getListItem(); return listItem.getLeftButtons(); } return null; } @Override public View[] getRightButtons(int position, final DynamicListItemView view) { TiBaseAbsListViewItem viewItem = (TiBaseAbsListViewItem) view .findViewById(TiBaseAbsListViewItem.listContentId); if (viewItem != null) { TiAbsListItem listItem = viewItem.getListItem(); return listItem.getRightButtons(); } return null; } } private Dictionary<Integer, Integer> listViewItemHeights = new Hashtable<Integer, Integer>(); public int getScroll() { View c = listView.getListChildAt(0); //this is the first visible row int scrollY = -c.getTop(); int first = listView.getFirstVisiblePosition(); listViewItemHeights.put(first, c.getHeight()); for (int i = 0; i < first; ++i) { if (listViewItemHeights.get(i) != null) // (this is a sanity check) scrollY += listViewItemHeights.get(i); //add all heights of the views that are gone } return (int) (scrollY / TiApplication.getAppDensity()); } public int getViewHeigth(View v) { int viewPosition = listView.getPositionForView(v); int scrollY = 0; for (int i = 0; i < viewPosition; ++i) { scrollY += listView.getListChildAt(i).getHeight(); } return scrollY; } private KrollDict dictForScrollEvent(final int yScroll) { KrollDict eventArgs = new KrollDict(); KrollDict size = new KrollDict(); size.put(TiC.PROPERTY_WIDTH, TiAbsListView.this.getNativeView().getWidth()); size.put(TiC.PROPERTY_HEIGHT, TiAbsListView.this.getNativeView().getHeight()); eventArgs.put(TiC.PROPERTY_SIZE, size); int firstVisibleItem = listView.getFirstVisiblePosition(); int lastVisiblePosition = listView.getLastVisiblePosition(); eventArgs.put("firstVisibleItem", firstVisibleItem); eventArgs.put("visibleItemCount", lastVisiblePosition - firstVisibleItem); KrollDict point = new KrollDict(); point.put(TiC.PROPERTY_X, 0); point.put(TiC.PROPERTY_Y, yScroll); eventArgs.put(TiC.PROPERTY_CONTENT_OFFSET, point); return eventArgs; } protected KrollDict dictForScrollEvent() { return dictForScrollEvent(getScroll()); } protected abstract C createListView(final Activity activity); public TiAbsListView(TiViewProxy proxy, Activity activity) { super(proxy); //initializing variables sections = Collections.synchronizedList(new ArrayList<AbsListSectionProxy>()); itemTypeCount = new AtomicInteger(CUSTOM_TEMPLATE_ITEM_TYPE); templatesByBinding = new HashMap<String, TiAbsListViewTemplate>(); defaultTemplateBinding = defaultTemplateKey; templatesByBinding.put(defaultTemplateKey, defaultTemplate); defaultTemplate.setType(BUILT_IN_TEMPLATE_ITEM_TYPE); caseInsensitive = true; ignoreExactMatch = false; //handling marker HashMap preloadMarker = ((AbsListViewProxy) proxy).getPreloadMarker(); if (preloadMarker != null) { setMarker(preloadMarker); } else { resetMarker(); } final KrollProxy fProxy = proxy; //initializing listView listView = createListView(activity); listView.setSelector(android.R.color.transparent); listView.setAreHeadersSticky(false); // listView.setDuplicateParentStateEnabled(true); AbsListView internalListView = getInternalListView(); if (TiC.LOLLIPOP_OR_GREATER) { listView.setNestedScrollingEnabled(true); internalListView.setNestedScrollingEnabled(true); } if (internalListView instanceof ListView) { ((ListView) internalListView).setHeaderDividersEnabled(false); ((ListView) internalListView).setFooterDividersEnabled(false); } if (listView instanceof CustomListView) { ((CustomListView) listView).setOnPullListener(new OnPullListener() { private boolean canUpdate = false; @Override public void onPull(boolean canUpdate) { if (canUpdate != this.canUpdate) { this.canUpdate = canUpdate; if (fProxy.hasListeners(TiC.EVENT_PULL_CHANGED, false)) { KrollDict event = dictForScrollEvent(); event.put("active", canUpdate); fProxy.fireEvent(TiC.EVENT_PULL_CHANGED, event, false, false); } } if (fProxy.hasListeners(TiC.EVENT_PULL, false)) { KrollDict event = dictForScrollEvent(); event.put("active", canUpdate); fProxy.fireEvent(TiC.EVENT_PULL, event, false, false); } } @Override public void onPullEnd(boolean canUpdate) { if (fProxy.hasListeners(TiC.EVENT_PULL_END, false)) { KrollDict event = dictForScrollEvent(); event.put("active", canUpdate); fProxy.fireEvent(TiC.EVENT_PULL_END, event, false, false); } } }); } adapter = new TiBaseAdapter(activity); listView.setOnScrollListener(new OnScrollListener() { private boolean scrollTouch = false; private int lastValidfirstItem = 0; 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(AbsListView view, int scrollState) { view.requestDisallowInterceptTouchEvent(scrollState != ViewPager.SCROLL_STATE_IDLE); if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) { if (scrollTouch) { delayEndCall(); } } else if (scrollState == OnScrollListener.SCROLL_STATE_FLING) { cancelEndCall(); } else if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 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 onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // Log.d(TAG, "onScroll : " + scrollValid, Log.DEBUG_MODE); // boolean fireScroll = scrollValid; // if (!fireScroll && visibleItemCount > 0) { // //Items in a list can be selected with a track ball in which case // //we must check to see if the first visibleItem has changed. // fireScroll = (lastValidfirstItem != firstVisibleItem); // } if (fProxy.hasListeners(TiC.EVENT_SCROLL, false)) { int newScrollOffset = getScroll(); // Log.d(TAG, "newScrollOffset : " + newScrollOffset, Log.DEBUG_MODE); lastValidfirstItem = firstVisibleItem; if (newScrollOffset != currentScrollOffset) { currentScrollOffset = newScrollOffset; fProxy.fireEvent(TiC.EVENT_SCROLL, dictForScrollEvent(currentScrollOffset), false, false); } } } }); listView.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); } } }); internalListView.setCacheColorHint(Color.TRANSPARENT); listView.setEnabled(true); getLayoutParams().autoFillsHeight = true; getLayoutParams().autoFillsWidth = true; // listView.setFocusable(false); listView.setFocusable(true); listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); // try { // // headerFooterId = TiRHelper.getApplicationResource("layout.titanium_ui_list_header_or_footer"); // // titleId = TiRHelper.getApplicationResource("id.titanium_ui_list_header_or_footer_title"); // 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); // } setNativeView(listView); } @Override protected void handleTouchEvent(MotionEvent event) { super.handleTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP) { final int x = (int) event.getX(); final int y = (int) event.getY(); int motionPosition = listView.getWrappedList().pointToPosition(x, y); if (motionPosition == -1) { listView.performClick(); } } } @Override protected TiUIView associatedTiViewForView(View childView) { if (childView instanceof DynamicWrapperViewList) { return this; } if (childView instanceof WrapperView) { View view = childView.findViewById(TiBaseAbsListViewItem.listContentId); if (view != null) { return ((TiCompositeLayout) view).getView(); } } return super.associatedTiViewForView(childView); } @Override protected boolean viewShouldPassThrough(final View view, final MotionEvent event) { if (view == listView) { return touchPassThrough(getInternalListView(), event); } return super.viewShouldPassThrough(view, event); } @Override public String getSearchText() { return searchText; } private void resetMarker() { marker[0] = Integer.MAX_VALUE; marker[1] = Integer.MAX_VALUE; } 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 TiUIView layoutHeaderOrFooter(TiViewProxy viewProxy) // { // //We are always going to create a new view here. So detach outer view here and recreate // View outerView = (viewProxy.peekView() == null) ? null : viewProxy.peekView().getOuterView(); // if (outerView != null) { // ViewParent vParent = outerView.getParent(); // if ( vParent instanceof ViewGroup ) { // ((ViewGroup)vParent).removeView(outerView); // } // } // TiUIView tiView = viewProxy.forceCreateView(); // View nativeView = tiView.getOuterView(); // TiCompositeLayout.LayoutParams params = tiView.getLayoutParams(); // // int width = AbsListView.LayoutParams.WRAP_CONTENT; // int height = AbsListView.LayoutParams.WRAP_CONTENT; // if (params.sizeOrFillHeightEnabled) { // if (params.autoFillsHeight) { // height = AbsListView.LayoutParams.MATCH_PARENT; // } // } else if (params.optionHeight != null) { // height = params.optionHeight.getAsPixels(listView); // } // if (params.sizeOrFillWidthEnabled) { // if (params.autoFillsWidth) { // width = AbsListView.LayoutParams.MATCH_PARENT; // } // } else if (params.optionWidth != null) { // width = params.optionWidth.getAsPixels(listView); // } // AbsListView.LayoutParams p = new AbsListView.LayoutParams(width, height); // nativeView.setLayoutParams(p); // return tiView; // } @Override public void registerForTouch() { registerForTouch(listView); } public void setMarker(HashMap markerItem) { marker[0] = TiConvert.toInt(markerItem, TiC.PROPERTY_SECTION_INDEX, -1); marker[1] = TiConvert.toInt(markerItem, TiC.PROPERTY_ITEM_INDEX, -1); } protected void notifyDataSetChanged() { if (!TiApplication.isUIThread()) { proxy.getActivity().runOnUiThread(new Runnable() { @Override public void run() { notifyDataSetChanged(); } }); return; } if (adapter != null) { adapter.notifyDataSetChanged(); } } protected static final ArrayList<String> KEY_SEQUENCE; static { ArrayList<String> tmp = new ArrayList<String>(); tmp.add(TiC.PROPERTY_SEARCH_TEXT); //make sure searchText is set before sections KEY_SEQUENCE = tmp; } @Override protected ArrayList<String> keySequence() { return KEY_SEQUENCE; } @Override public void propertySet(String key, Object newValue, Object oldValue, boolean changedProperty) { switch (key) { 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_IGNORE_EXACT_MATCH: this.ignoreExactMatch = TiConvert.toBoolean(newValue, true); filterBy(TiConvert.toString(this.searchText)); 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: listView.setAreHeadersSticky(TiConvert.toBoolean(newValue, false)); 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_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: listView.setVerticalScrollBarEnabled(TiConvert.toBoolean(newValue, true)); break; case TiC.PROPERTY_DEFAULT_ITEM_TEMPLATE: defaultTemplateBinding = TiConvert.toString(newValue); if (changedProperty) { notifyDataSetChanged(); } 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()) { processSections((Object[]) newValue); } } break; case TiC.PROPERTY_SEPARATOR_STYLE: Drawable drawable = listView.getDivider(); listView.setDivider(drawable); listView.setDividerHeight(TiConvert.toInt(newValue, TiUITableView.SEPARATOR_SINGLE_LINE)); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; case TiC.PROPERTY_OVER_SCROLL_MODE: // if (Build.VERSION.SDK_INT >= 9) { listView.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: // if (footerView == null || footerView.getId() != HEADER_FOOTER_WRAP_ID) { // if (footerView == null) { // footerView = inflater.inflate(headerFooterId, null); // } setFooterTitle(TiConvert.toString(newValue)); // } mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; case TiC.PROPERTY_PULL_VIEW: ((RefreshableListView) listView).setHeaderPullView(setPullView(newValue)); mProcessUpdateFlags |= TIFLAG_NEEDS_DATASET; break; // case TiC.PROPERTY_REFRESH_CONTROL: // if (newValue instanceof RefreshControlProxy) { // ((RefreshControlProxy)newValue).assignTo(this.wrapper); // } else { // RefreshControlProxy.unassignFrom(this.wrapper); // } // break; default: super.propertySet(key, newValue, oldValue, changedProperty); break; } } @Override protected void aboutToProcessProperties(HashMap d) { super.aboutToProcessProperties(d); updateToPassProps(d); if (adapter != null) { adapter.setCanNotifyDataSetChanged(false); } if (listView.getAdapter() == null) { mProcessUpdateFlags |= TIFLAG_NEEDS_ADAPTER_CHANGE; } } @Override protected void didProcessProperties() { AbsListViewProxy listProxy = (AbsListViewProxy) proxy; if (listProxy.getPreload()) { ArrayList preload = listProxy.getPreloadSections(); if (preload != null) { processSections(preload.toArray()); } listProxy.setPreload(false); listProxy.clearPreloadSections(); } super.didProcessProperties(); if (adapter != null) { adapter.setCanNotifyDataSetChanged(true); } if ((mProcessUpdateFlags & TIFLAG_NEEDS_ADAPTER_CHANGE) != 0) { mProcessUpdateFlags &= ~TIFLAG_NEEDS_ADAPTER_CHANGE; setListViewAdapter(adapter); } if ((mProcessUpdateFlags & TIFLAG_NEEDS_DATASET) != 0) { mProcessUpdateFlags &= ~TIFLAG_NEEDS_DATASET; notifyDataSetChanged(); } } protected void setListViewAdapter(TiBaseAdapter adapter) { listView.setAdapter(adapter); } private void setHeaderOrFooterView(Object viewObj, boolean isHeader) { KrollProxy viewProxy = proxy.addProxyToHold(viewObj, isHeader ? "headerView" : "footerView", false, true); if (viewProxy instanceof TiViewProxy) { if (isHeader) { getOrCreateHeaderWrapperView().add(viewProxy, 1); } else { getOrCreateFooterWrapperView().add(viewProxy, 1); } } } 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>(); templatesByBinding.put(defaultTemplateKey, 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); template.setType(getItemType()); templatesByBinding.put(key, template); } else { Log.e(TAG, "null template definition: " + key); } } } } 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)); listView.addHeaderView(view.getOuterView(), null, false); 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)); listView.addFooterView(view.getOuterView(), null, false); 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; } public static View layoutHeaderOrFooterView(TiViewProxy viewProxy, View convertView) { TiUIView tiView = viewProxy.getOrCreateView(); View outerView = null; ViewGroup parentView = null; if (tiView != null) { outerView = tiView.getOuterView(); parentView = (ViewGroup) outerView.getParent(); } if ((parentView != null && parentView.getId() == HEADER_FOOTER_WRAP_ID) && (convertView == null || convertView == parentView)) { return parentView; } else { TiCompositeLayout wrapper = null; if (convertView instanceof TiCompositeLayout && convertView.getId() == HEADER_FOOTER_WRAP_ID) { wrapper = (TiCompositeLayout) convertView; wrapper.removeAllViews(); } else { wrapper = new TiCompositeLayout(viewProxy.getActivity(), LayoutArrangement.DEFAULT, null); //add a wrapper so layout params such as height, width takes in effect. AbsListView.LayoutParams params = new AbsListView.LayoutParams( AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT); wrapper.setLayoutParams(params); wrapper.setInternalTouchPassThrough(true); } if (parentView != null) { parentView.removeView(outerView); } // TiUIHelper.removeViewFromSuperView(viewProxy); if (outerView != null && tiView != null) { TiCompositeLayout.LayoutParams headerParams = tiView.getLayoutParams(); //If height is not dip, explicitly set it to SIZE if (!headerParams.fixedSizeHeight()) { headerParams.sizeOrFillHeightEnabled = true; headerParams.autoFillsHeight = false; } if (headerParams.optionWidth == null && !viewProxy.hasProperty(TiC.PROPERTY_WIDTH)) { headerParams.sizeOrFillWidthEnabled = true; headerParams.autoFillsWidth = true; } wrapper.addView(outerView, tiView.getLayoutParams()); } wrapper.setId(HEADER_FOOTER_WRAP_ID); wrapper.setTag(HEADER_FOOTER_WRAP_ID); return wrapper; } } protected void processSections(Object[] sections) { synchronized (this.sections) { this.sections.clear(); } for (int i = 0; i < sections.length; i++) { processSection(sections[i], -1); } } 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 AbsListSectionProxy) { AbsListSectionProxy section = (AbsListSectionProxy) sec; section.setListView(this); section.setActivity(proxy.getActivity()); section.setAdapter(adapter); 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) { AbsListSectionProxy section = (AbsListSectionProxy) 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 Pair<AbsListSectionProxy, Pair<Integer, Integer>> getSectionInfoByEntryIndex(int index) { if (index < 0) { return null; } // if (mSectionInfoCache .containsKey(index)) { // return (Pair<AbsListSectionProxy, Pair<Integer, Integer>>) mSectionInfoCache.get(index); // } 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)); // mSectionInfoCache.put(index, result); return result; } else { index -= sectionItemCount; } } } return null; } protected int getSectionFirstPosition(int sectionIndex) { int result = 0; synchronized (sections) { for (int i = 0; i < sectionIndex; i++) { result += sections.get(i).getItemCount(); } } return result; } 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) { int position = 0; synchronized (sections) { for (int i = 0; i < sections.size(); i++) { AbsListSectionProxy section = sections.get(i); if (i == sectionIndex) { if (sectionItemIndex >= section.getContentCount()) { Log.e(TAG, "Invalid item index"); return -1; } position += sectionItemIndex; // if (section.hasHeader()) { // position += 1; // } if (section.hasFooter()) { position += 1; } break; } else { position += section.getItemCount(); } } } return position; } public int getHeaderViewCount() { return listView.getHeaderViewsCount(); } private int getCount() { if (adapter != null) { return adapter.getCount(); } return 0; } public static void ensureVisible(StickyListHeadersListViewAbstract listView, int pos) { if (listView == null) { return; } if (pos < 0 || pos >= listView.getCount()) { return; } int first = listView.getFirstVisiblePosition(); int last = listView.getLastVisiblePosition(); if (pos < first) { listView.setSelection(pos); return; } if (pos >= last) { listView.setSelection(1 + pos - (last - first)); return; } } public void scrollToItem(int sectionIndex, int sectionItemIndex, boolean animated) { final int position = findItemPosition(sectionIndex, sectionItemIndex); if (position > -1) { if (animated) listView.smoothScrollToPosition(position); else ensureVisible(listView, position); } } public void scrollToTop(final int y, boolean animated) { if (animated) { listView.smoothScrollToPositionFromTop(0, y); } else { listView.setSelectionFromTop(0, y); } } 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) { listView.smoothScrollToPosition(getCount() - 1); } else { listView.setSelection(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 a refresh control is currently assigned, then detach it. // RefreshControlProxy.unassignFrom(this.wrapper); if (handledProxies != null) { for (TiViewProxy viewProxy : handledProxies) { viewProxy.releaseViews(true); viewProxy.setParent(null); } handledProxies = null; } if (listView != null) { listView.setAdapter(null); listView = 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) { if (listView == null) { return null; } int position = findItemPosition(sectionIndex, itemIndex); int childCount = listView.getListChildCount(); for (int i = 0; i < childCount; i++) { View child = listView.getListChildAt(i); TiBaseAbsListViewItem itemContent = (TiBaseAbsListViewItem) child .findViewById(TiBaseAbsListViewItem.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 = listView.getListChildAt(position); return content; } return null; } private View setPullView(Object viewObj) { KrollProxy viewProxy = proxy.addProxyToHold(viewObj, "pull"); if (viewProxy instanceof ViewProxy) { return layoutHeaderOrFooterView((TiViewProxy) viewProxy, null); } return null; } public void showPullView(final boolean animated) { if (!TiApplication.isUIThread()) { proxy.getActivity().runOnUiThread(new Runnable() { @Override public void run() { showPullView(animated); } }); return; } ((RefreshableListView) listView).showHeaderPullView(animated); } public void closePullView(final boolean animated) { if (!TiApplication.isUIThread()) { proxy.getActivity().runOnUiThread(new Runnable() { @Override public void run() { closePullView(animated); } }); return; } ((RefreshableListView) listView).closeHeaderPullView(animated); } public Object getContentOffset() { if (nativeView == null) return proxy.getProperty(TiC.PROPERTY_CONTENT_OFFSET); KrollDict point = new KrollDict(); point.put(TiC.PROPERTY_X, 0); point.put(TiC.PROPERTY_Y, getScroll()); return point; } 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()); scrollToTop(p.y, animated); } } }