ti.modules.titanium.ui.widget.tableview.TiTableView.java Source code

Java tutorial

Introduction

Here is the source code for ti.modules.titanium.ui.widget.tableview.TiTableView.java

Source

/**
 * Appcelerator Titanium Mobile
 * Copyright (c) 2009-2017 by Axway, 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.tableview;

import java.util.ArrayList;
import java.util.HashMap;
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.proxy.TiViewProxy;
import org.appcelerator.titanium.util.TiConvert;
import org.appcelerator.titanium.util.TiUIHelper;
import org.appcelerator.titanium.view.TiCompositeLayout;
import org.appcelerator.titanium.view.TiCompositeLayout.LayoutArrangement;
import org.appcelerator.titanium.view.TiUIView;

import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
import ti.modules.titanium.ui.TableViewProxy;
import ti.modules.titanium.ui.TableViewRowProxy;
import ti.modules.titanium.ui.widget.CustomListView;
import ti.modules.titanium.ui.UIModule;
import ti.modules.titanium.ui.widget.searchbar.TiUISearchBar.OnSearchChangeListener;
import ti.modules.titanium.ui.widget.tableview.TableViewModel.Item;
import ti.modules.titanium.ui.widget.TiSwipeRefreshLayout;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.support.v4.view.ViewPager;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;

public class TiTableView extends TiSwipeRefreshLayout implements OnSearchChangeListener {
    public static final int TI_TABLE_VIEW_ID = 101;
    public static final int HEADER_FOOTER_WRAP_ID = 54321;
    private static final String TAG = "TiTableView";

    protected int maxClassname = 32;

    private TableViewModel viewModel;
    private CustomListView listView;
    private TTVListAdapter adapter;
    private OnItemClickedListener itemClickListener;
    private OnItemLongClickedListener itemLongClickListener;

    private HashMap<String, Integer> rowTypes;
    private AtomicInteger rowTypeCounter;

    private String filterAttribute;
    private String filterText;

    private int dividerHeight;
    private TableViewProxy proxy;
    private boolean filterCaseInsensitive = true;
    private boolean filterAnchored = false;
    private StateListDrawable selector;

    public interface OnItemClickedListener {
        public void onClick(KrollDict item);
    }

    public interface OnItemLongClickedListener {
        public boolean onLongClick(KrollDict item);
    }

    class TTVListAdapter extends BaseAdapter implements StickyListHeadersAdapter {
        TableViewModel viewModel;
        ArrayList<Integer> index;
        private boolean filtered;
        private TableViewProxy proxy;

        TTVListAdapter(TableViewModel viewModel, TableViewProxy proxy) {
            this.viewModel = viewModel;
            this.proxy = proxy;
            this.index = new ArrayList<Integer>(viewModel.getRowCount());
            reIndexItems();
        }

        protected void registerClassName(String className) {
            if (!rowTypes.containsKey(className)) {
                Log.d(TAG, "registering new className " + className, Log.DEBUG_MODE);
                rowTypes.put(className, rowTypeCounter.incrementAndGet());
            }
        }

        public void reIndexItems() {
            ArrayList<Item> items = viewModel.getViewModel();
            int count = items.size();
            index.clear();

            filtered = false;
            if (filterAttribute != null && filterText != null && filterAttribute.length() > 0
                    && filterText.length() > 0) {
                filtered = true;
                String filter = filterText;
                if (filterCaseInsensitive) {
                    filter = filterText.toLowerCase();
                }
                for (int i = 0; i < count; i++) {
                    boolean keep = true;
                    Item item = items.get(i);
                    registerClassName(item.className);
                    if (item.proxy.hasProperty(filterAttribute)) {
                        String t = TiConvert.toString(item.proxy.getProperty(filterAttribute));
                        if (filterCaseInsensitive) {
                            t = t.toLowerCase();
                        }
                        if (filterAnchored) {
                            if (!t.startsWith(filter)) {
                                keep = false;
                            }
                        } else {
                            if (t.indexOf(filter) < 0) {
                                keep = false;
                            }
                        }
                    }
                    if (keep) {
                        index.add(i);
                    }
                }
            } else {
                for (int i = 0; i < count; i++) {
                    Item item = items.get(i);
                    registerClassName(item.className);
                    index.add(i);
                }
            }
            if (index.size() == 0) {
                proxy.fireEvent(TiC.EVENT_NO_RESULTS, null);
            }
        }

        public int getCount() {
            //return viewModel.getViewModel().length();
            return index.size();
        }

        public Object getItem(int position) {
            if (position >= index.size()) {
                return null;
            }

            return viewModel.getViewModel().get(index.get(position));
        }

        public long getItemId(int position) {
            return position;
        }

        @Override
        public int getViewTypeCount() {
            // Fix for TIMOB-20038. Seems that there are 3 more
            // hidden views that needs to be recreated onLayout
            return maxClassname + 3;
        }

        @Override
        public int getItemViewType(int position) {
            Item item = (Item) getItem(position);
            registerClassName(item.className);
            return rowTypes.get(item.className);
        }

        /*
         * IMPORTANT NOTE:
         * getView() is called by the Android framework whenever it needs a view.
         * The call to getView() could come on a measurement pass or on a layout
         * pass.  It's not possible to tell from the arguments whether the framework
         * is calling getView() for a measurement pass or for a layout pass.  Therefore,
         * it is important that getView() and all methods call by getView() only create
         * the views and fill them in with the appropriate data.  What getView() and the
         * methods call by getView MUST NOT do is to make any associations between
         * proxies and views.   Those associations must be made only for the views
         *  that are used for layout, and should be driven from the onLayout() callback.
         */
        public View getView(int position, View convertView, ViewGroup parent) {
            Item item = (Item) getItem(position);
            TiViewProxy newProxy = item.proxy;
            TiBaseTableViewItem v = null;
            boolean sameView = false;
            //we have a conversion view, first lets check if it s compatible with our proxy
            if (convertView != null) {
                v = (TiBaseTableViewItem) convertView;
                // Default creates view for each Item

                if (newProxy instanceof TableViewRowProxy) {
                    TableViewRowProxy row = (TableViewRowProxy) newProxy;
                    if (row.getTableViewRowProxyItem() != null) {
                        sameView = row.getTableViewRowProxyItem().equals(convertView);
                    }
                    // TIMOB-24560: prevent duplicate TableViewRowProxyItem on Android N
                    if (Build.VERSION.SDK_INT > 23) {
                        ArrayList<Item> models = viewModel.getViewModel();
                        for (Item model : models) {
                            TableViewRowProxy proxy = (TableViewRowProxy) model.proxy;
                            if (proxy.getTableViewRowProxyItem().equals(convertView)) {
                                sameView = true;
                                v = null;
                                break;
                            }
                        }
                    }
                }

                // TIMOB-24560: prevent duplicate TableViewRowProxyItem on Android N
                if (Build.VERSION.SDK_INT > 23) {
                    ArrayList<Item> models = viewModel.getViewModel();
                    if (models != null && v instanceof TiTableViewRowProxyItem && models.contains(v.getRowData())) {
                        v = null;
                        sameView = true;
                    }
                }

                if (!sameView) {
                    if (v.getClassName().equals(TableViewProxy.CLASSNAME_DEFAULT)) {
                        if (v.getRowData() != item) {
                            v = null;
                        }
                    } else if (v.getClassName().equals(TableViewProxy.CLASSNAME_HEADERVIEW)) {
                        //Always recreate the header view
                        v = null;
                    } else {
                        // otherwise compare class names
                        if (!(item.proxy instanceof TableViewRowProxy)
                                || !v.getClassName().equals(item.className)) {
                            Log.w(TAG, "Handed a view to convert with className " + v.getClassName() + " expected "
                                    + item.className, Log.DEBUG_MODE);
                            v = null;
                        }
                    }
                }
                if (v != null) {
                    TiViewProxy oldProxy = v.getRowData().proxy;
                    //last check, verify that we can transfer proxy
                    Boolean canreproxy = true;
                    KrollProxy[] oldproxies = oldProxy.getChildren();
                    KrollProxy[] newproxies = newProxy.getChildren();
                    if (oldproxies.length != newproxies.length) {
                        canreproxy = false;
                    } else {
                        for (int i = 0; i < oldproxies.length; i++) {
                            KrollProxy newSubProxy = newproxies[i];
                            KrollProxy oldSubProxy = oldproxies[i];

                            if (!(oldSubProxy instanceof TiViewProxy)
                                    || (oldSubProxy.getClass().equals(newSubProxy.getClass()))
                                    || ((TiViewProxy) oldSubProxy)
                                            .validateTransferToProxy((TiViewProxy) newSubProxy, true) == false) {
                                canreproxy = false;
                                break;
                            }
                        }
                    }

                    if (canreproxy == false && ((ViewGroup) v.getView()).getChildCount() > 0) {
                        Log.w(TAG, "We cant reuse that view, will create a new one", Log.DEBUG_MODE);

                        //we must clear this viewItem. As a consequence it will be as creating a new one
                        v.clearViews();
                    }
                }
            }
            if (v == null) {
                if (item.className.equals(TableViewProxy.CLASSNAME_HEADERVIEW)) {
                    TiViewProxy vproxy = item.proxy;
                    TiUIView headerView = layoutSectionHeaderOrFooter(vproxy);
                    v = new TiTableViewHeaderItem(proxy.getActivity(), headerView);
                    v.setClassName(TableViewProxy.CLASSNAME_HEADERVIEW);
                    return v;
                } else if (item.className.equals(TableViewProxy.CLASSNAME_HEADER)) {
                    v = new TiTableViewHeaderItem(proxy.getActivity());
                    v.setClassName(TableViewProxy.CLASSNAME_HEADER);
                } else if (item.className.equals(TableViewProxy.CLASSNAME_NORMAL)) {
                    v = new TiTableViewRowProxyItem(proxy.getActivity());
                    v.setClassName(TableViewProxy.CLASSNAME_NORMAL);
                } else if (item.className.equals(TableViewProxy.CLASSNAME_DEFAULT)) {
                    v = new TiTableViewRowProxyItem(proxy.getActivity());
                    v.setClassName(TableViewProxy.CLASSNAME_DEFAULT);
                } else {
                    v = new TiTableViewRowProxyItem(proxy.getActivity());
                    v.setClassName(item.className);
                }
                v.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
                        AbsListView.LayoutParams.MATCH_PARENT));
            } else if (sameView == false) {
                v.prepareForReuse();
            }
            if (v.getRowData() != item || sameView == false) {
                v.setRowData(item);
            }
            return v;
        }

        @Override
        public boolean areAllItemsEnabled() {
            return false;
        }

        @Override
        public boolean isEnabled(int position) {
            Item item = (Item) getItem(position);
            boolean enabled = true;
            if (item != null && item.className.equals(TableViewProxy.CLASSNAME_HEADER)) {
                enabled = false;
            }
            return enabled;
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        @Override
        public void notifyDataSetChanged() {
            reIndexItems();
            super.notifyDataSetChanged();
        }

        public boolean isFiltered() {
            return filtered;
        }

        @Override
        public long getHeaderId(int arg0) {
            // TODO Auto-generated method stub
            return 0;
        }

        @Override
        public View getHeaderView(int arg0, View arg1, ViewGroup arg2) {
            // TODO Auto-generated method stub
            return null;
        }
    }

    public TiTableView(TableViewProxy proxy) {
        super(proxy.getActivity());
        this.proxy = proxy;

        // Disable pull-down refresh support until a Titanium "RefreshControl" has been assigned.
        setSwipeRefreshEnabled(false);

        if (proxy.getProperties().containsKey(TiC.PROPERTY_MAX_CLASSNAME)) {
            maxClassname = Math.max(TiConvert.toInt(proxy.getProperty(TiC.PROPERTY_MAX_CLASSNAME)), maxClassname);
        }
        rowTypes = new HashMap<String, Integer>();
        rowTypeCounter = new AtomicInteger(-1);
        rowTypes.put(TableViewProxy.CLASSNAME_HEADER, rowTypeCounter.incrementAndGet());
        rowTypes.put(TableViewProxy.CLASSNAME_NORMAL, rowTypeCounter.incrementAndGet());
        rowTypes.put(TableViewProxy.CLASSNAME_DEFAULT, rowTypeCounter.incrementAndGet());

        this.viewModel = new TableViewModel(proxy);
        this.listView = new CustomListView(getContext());
        listView.setId(TI_TABLE_VIEW_ID);

        listView.setFocusable(true);
        listView.setFocusableInTouchMode(true);
        listView.setBackgroundColor(Color.TRANSPARENT);
        getInternalListView().setCacheColorHint(Color.TRANSPARENT);
        getInternalListView().setScrollingCacheEnabled(false);
        final KrollProxy fProxy = proxy;
        listView.setOnScrollListener(new OnScrollListener() {
            private boolean scrollValid = false;
            private int lastValidfirstItem = 0;

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                view.requestDisallowInterceptTouchEvent(scrollState != ViewPager.SCROLL_STATE_IDLE);
                if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
                    scrollValid = false;
                    if (fProxy.hasListeners(TiC.EVENT_SCROLLEND)) {
                        KrollDict eventArgs = new KrollDict();
                        KrollDict size = new KrollDict();
                        size.put(TiC.PROPERTY_WIDTH, TiTableView.this.getWidth());
                        size.put(TiC.PROPERTY_HEIGHT, TiTableView.this.getHeight());
                        eventArgs.put(TiC.PROPERTY_SIZE, size);
                        fProxy.fireEvent(TiC.EVENT_SCROLLEND, eventArgs);
                    }
                } else if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                    scrollValid = true;
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                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 (fireScroll) {
                    lastValidfirstItem = firstVisibleItem;
                    if (fProxy.hasListeners(TiC.EVENT_SCROLL)) {
                        KrollDict eventArgs = new KrollDict();
                        eventArgs.put("firstVisibleItem", firstVisibleItem);
                        eventArgs.put("visibleItemCount", visibleItemCount);
                        eventArgs.put("totalItemCount", totalItemCount);
                        KrollDict size = new KrollDict();
                        size.put(TiC.PROPERTY_WIDTH, TiTableView.this.getWidth());
                        size.put(TiC.PROPERTY_HEIGHT, TiTableView.this.getHeight());
                        eventArgs.put(TiC.PROPERTY_SIZE, size);
                        fProxy.fireEvent(TiC.EVENT_SCROLL, eventArgs);
                    }
                }
            }
        });
        // get default divider height
        dividerHeight = listView.getDividerHeight();
        if (proxy.hasProperty(TiC.PROPERTY_SEPARATOR_COLOR)) {
            setSeparatorColor(TiConvert.toColor(proxy.getProperty(TiC.PROPERTY_SEPARATOR_COLOR)));
        }

        if (proxy.hasProperty(TiC.PROPERTY_SEPARATOR_STYLE)) {
            setSeparatorStyle(TiConvert.toInt(proxy.getProperty(TiC.PROPERTY_SEPARATOR_STYLE),
                    UIModule.TABLE_VIEW_SEPARATOR_STYLE_NONE));
        }
        adapter = new TTVListAdapter(viewModel, proxy);
        if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_HEADER_VIEW)) {
            TiViewProxy view = (TiViewProxy) proxy.getProperty(TiC.PROPERTY_HEADER_VIEW);
            listView.addHeaderView(layoutTableHeaderOrFooter(view), null, false);
        }
        if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_FOOTER_VIEW)) {
            TiViewProxy view = (TiViewProxy) proxy.getProperty(TiC.PROPERTY_FOOTER_VIEW);
            listView.addFooterView(layoutTableHeaderOrFooter(view), null, false);
        }

        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (itemClickListener != null) {
                    if (!(view instanceof TiBaseTableViewItem)) {
                        return;
                    }
                    rowClicked((TiBaseTableViewItem) view, position, false);
                }
            }
        });
        listView.setOnItemLongClickListener(new OnItemLongClickListener() {
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                if (itemLongClickListener == null) {
                    return false;
                }
                TiBaseTableViewItem tvItem = null;
                if (view instanceof TiBaseTableViewItem) {
                    tvItem = (TiBaseTableViewItem) view;
                } else {
                    tvItem = getParentTableViewItem(view);
                }
                if (tvItem == null) {
                    return false;
                }
                return rowClicked(tvItem, position, true);
            }
        });
        addView(listView);
    }

    public void removeHeaderView(TiViewProxy viewProxy) {
        TiUIView peekView = viewProxy.peekView();
        View outerView = (peekView == null) ? null : peekView.getOuterView();
        if (outerView != null) {
            listView.removeHeaderView(outerView);
        }
    }

    public void setHeaderView() {
        if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_HEADER_VIEW)) {
            listView.setAdapter(null);
            TiViewProxy view = (TiViewProxy) proxy.getProperty(TiC.PROPERTY_HEADER_VIEW);
            listView.addHeaderView(layoutTableHeaderOrFooter(view), null, false);
            listView.setAdapter(adapter);
        }
    }

    public void removeFooterView(TiViewProxy viewProxy) {
        TiUIView peekView = viewProxy.peekView();
        View outerView = (peekView == null) ? null : peekView.getOuterView();
        if (outerView != null) {
            listView.removeFooterView(outerView);
        }
    }

    public void setFooterView() {
        if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_FOOTER_VIEW)) {
            listView.setAdapter(null);
            TiViewProxy view = (TiViewProxy) proxy.getProperty(TiC.PROPERTY_FOOTER_VIEW);
            listView.addFooterView(layoutTableHeaderOrFooter(view), null, false);
            listView.setAdapter(adapter);
        }
    }

    private TiBaseTableViewItem getParentTableViewItem(View view) {
        ViewParent parent = view.getParent();
        while (parent != null) {
            if (parent instanceof TiBaseTableViewItem) {
                return (TiBaseTableViewItem) parent;
            }
            parent = parent.getParent();
        }
        return null;
    }

    private AbsListView getInternalListView() {
        return listView.getWrappedList();
    }

    public void enableCustomSelector() {
        Drawable currentSelector = getInternalListView().getSelector();
        if (currentSelector != selector) {
            selector = new StateListDrawable();
            TiTableViewSelector selectorDrawable = new TiTableViewSelector(listView);
            selector.addState(new int[] { android.R.attr.state_pressed }, selectorDrawable);
            listView.setSelector(selector);
        }
    }

    public Item getItemAtPosition(int position) {
        if (proxy.hasPropertyAndNotNull(TiC.PROPERTY_HEADER_VIEW)) {
            position -= 1;
        }
        if (position == -1 || position == adapter.getCount()) {
            return null;
        }
        return viewModel.getViewModel().get(adapter.index.get(position));
    }

    public int getPositionForView(View view) {
        int result = -1;
        try {
            result = listView.getPositionForView(view);
        } catch (NullPointerException e) {

        }
        return result;
    }

    public int getIndexFromXY(double x, double y) {
        int bound = listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
        for (int i = 0; i <= bound; i++) {
            View child = listView.getChildAt(i);
            if (child != null && x >= child.getLeft() && x <= child.getRight() && y >= child.getTop()
                    && y <= child.getBottom()) {
                return listView.getFirstVisiblePosition() + i;
            }
        }
        return -1;
    }

    protected boolean rowClicked(TiBaseTableViewItem rowView, int position, boolean longClick) {
        String viewClicked = rowView.getLastClickedViewName();
        Item item = getItemAtPosition(position);
        KrollDict event = new KrollDict();
        String eventName = longClick ? TiC.EVENT_LONGCLICK : TiC.EVENT_CLICK;
        TableViewRowProxy.fillClickEvent(event, viewModel, item);
        if (viewClicked != null) {
            event.put(TiC.EVENT_PROPERTY_LAYOUT_NAME, viewClicked);
        }
        event.put(TiC.EVENT_PROPERTY_SEARCH_MODE, adapter.isFiltered());

        boolean longClickFired = false;
        if (item.proxy != null && item.proxy instanceof TableViewRowProxy) {
            TableViewRowProxy rp = (TableViewRowProxy) item.proxy;
            event.put(TiC.EVENT_PROPERTY_SOURCE, rp);
            // The event will bubble up to the parent.
            if (rp.hierarchyHasListener(eventName)) {
                rp.fireEvent(eventName, event);
                longClickFired = true;
            }
        }
        if (longClick && !longClickFired) {
            return itemLongClickListener.onLongClick(event);
        } else if (longClickFired) {
            return true;
        } else {
            return false; // standard (not-long) click handling has no return value.
        }
    }

    private View layoutTableHeaderOrFooter(TiViewProxy viewProxy) {
        TiUIView tiView = viewProxy.peekView();
        if (tiView != null) {
            TiViewProxy parentProxy = (TiViewProxy) viewProxy.getParent();
            // Remove parent view if possible
            if (parentProxy != null) {
                TiUIView parentView = parentProxy.peekView();
                if (parentView != null) {
                    parentView.remove(tiView);
                }
            }
        } else {
            if ((proxy != null) && (proxy.getActivity() != null)) {
                viewProxy.setActivity(proxy.getActivity());
            }
            tiView = viewProxy.forceCreateView();
        }
        View outerView = tiView.getOuterView();
        ViewGroup parentView = (ViewGroup) outerView.getParent();
        if (parentView != null && parentView.getId() == HEADER_FOOTER_WRAP_ID) {
            return parentView;
        } else {
            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;
        }
    }

    private TiUIView layoutSectionHeaderOrFooter(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);
            }
        }
        viewProxy.clearViews();
        TiUIView tiView = viewProxy.forceCreateView();
        View nativeView = tiView.getOuterView();
        TiCompositeLayout.LayoutParams params = tiView.getLayoutParams();

        // Set width to MATCH_PARENT to be consistent with iPhone
        int width = AbsListView.LayoutParams.MATCH_PARENT;
        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);
        }

        AbsListView.LayoutParams p = new AbsListView.LayoutParams(width, height);
        nativeView.setLayoutParams(p);
        return tiView;
    }

    public void dataSetChanged() {
        if (adapter != null) {
            adapter.notifyDataSetChanged();
        }
    }

    public int getCount() {
        if (adapter != null) {
            return adapter.getCount();
        }
        return 0;
    }

    public void setOnItemClickListener(OnItemClickedListener listener) {
        this.itemClickListener = listener;
    }

    public void setOnItemLongClickListener(OnItemLongClickedListener listener) {
        this.itemLongClickListener = listener;
    }

    public void setSeparatorColor(int sepColor) {
        listView.setDivider(new ColorDrawable(sepColor));
        listView.setDividerHeight(dividerHeight);
    }

    public void setSeparatorStyle(int style) {
        if (style == UIModule.TABLE_VIEW_SEPARATOR_STYLE_NONE) {
            listView.setDividerHeight(0);
        } else if (style == UIModule.TABLE_VIEW_SEPARATOR_STYLE_SINGLE_LINE) {
            listView.setDividerHeight(dividerHeight);
        }
    }

    public TableViewModel getTableViewModel() {
        return this.viewModel;
    }

    public CustomListView getListView() {
        return listView;
    }

    @Override
    public void filterBy(String text) {
        filterText = text;
        if (adapter != null) {
            proxy.getActivity().runOnUiThread(new Runnable() {
                public void run() {
                    dataSetChanged();
                }
            });
        }
    }

    public void setFilterAttribute(String filterAttribute) {
        this.filterAttribute = filterAttribute;
    }

    public void setFilterAnchored(boolean filterAnchored) {
        this.filterAnchored = filterAnchored;
    }

    public void setFilterCaseInsensitive(boolean filterCaseInsensitive) {
        this.filterCaseInsensitive = filterCaseInsensitive;
    }

    public void release() {
        adapter = null;
        if (listView != null) {
            listView.setAdapter(null);
        }
        listView = null;
        if (viewModel != null) {
            viewModel.release();
        }
        viewModel = null;
        itemClickListener = null;
    }

    @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.
        if (listView == null) {
            super.onLayout(changed, left, top, right, bottom);
            return;
        }
        OnFocusChangeListener focusListener = null;
        View focusedView = listView.findFocus();
        if (focusedView != null) {
            OnFocusChangeListener listener = focusedView.getOnFocusChangeListener();
            if (listener != null && listener instanceof TiUIView) {
                focusedView.setOnFocusChangeListener(null);
                focusListener = listener;
            }
        }

        super.onLayout(changed, left, top, right, bottom);

        if (proxy != null) {
            if (changed) {
                TiUIHelper.firePostLayoutEvent(proxy.peekView());
            }
        }

        // Layout is finished, re-enable focus events.
        if (focusListener != null) {
            focusedView.setOnFocusChangeListener(focusListener);
            // If the configuration changed, we manually fire the blur event
            if (changed) {
                focusListener.onFocusChange(focusedView, false);
            }
        }
    }
}