LWUIT的List运用系列(七) List的终极运用(下篇)

来源:互联网 发布:猪出栏时间知乎 编辑:程序博客网 时间:2024/05/23 14:59

在LWUIT的List运用系列(六) List的终极使用(上篇)中我介绍了LWUIT_MakeOver项目,虽然有部分代码看不懂,但这并不阻碍我去模仿它的形式去应用List。这一篇我按照作者的思想写了一个简单的Demo,希望那些跟我一样不理解源代码的同胞们能够加深一下理解。
如果你现在还没有这个项目的源代码,可以到这里下载(不要资源分的)。

既然是模仿别人的程序,在自己动手之前,我们首先要明白源代码的基本含义,至少要能保证大半代码我们能够理解,我先对源代码做一个简单的分析和说明。
我把显示详细信息的showDetail()方法和显示地图的showMap()方法去掉了。

    //创建Form,主要是把Menu前的默认数字给去掉    private Form createForm(String title) {        Form f = new Form(title);        f.getTitleComponent().setAlignment(Component.LEFT);        f.setMenuCellRenderer(new DefaultListCellRenderer(false));        return f;    }
 //主界面,就是表单界面,我们不用理会那些字段的含义    private void showMainForm() {        Form mainForm = createForm("Local Search");        mainForm.setTransitionInAnimator(Transition3D.createCube(500, false));        mainForm.setTransitionOutAnimator(Transition3D.createCube(500, true));        mainForm.setLayout(new BoxLayout(BoxLayout.Y_AXIS));        mainForm.addComponent(new Label("search for:"));        final TextField searchFor = new TextField("coffee", 50);        mainForm.addComponent(searchFor);        mainForm.addComponent(new Label("location:"));        final TextField location = new TextField("95054", 50);        mainForm.addComponent(location);        mainForm.addComponent(new Label("street:"));        final TextField street = new TextField(50);        mainForm.addComponent(street);        mainForm.addComponent(new Label("sort results by:"));        final ComboBox sortResults = new ComboBox(new String[] {"Distance", "Title", "Rating", "Relevance"});        mainForm.addComponent(sortResults);        mainForm.addCommand(exitCommand);        mainForm.addCommand(defaultThemeCommand);        mainForm.addCommand(javaThemeCommand);        mainForm.addCommand(new Command("Search") {            public void actionPerformed(ActionEvent ev) {                showSearchResultForm(searchFor.getText(), location.getText(), street.getText(), (String) sortResults.getSelectedItem());            }        });        mainForm.show();    }
    //报告异常信息的对话框,网络连接异常或者中断时会报此异常    private void exception(Exception ex) {        ex.printStackTrace();        Dialog.show("Error", "Error connecting to search service - Turning on DEMO MODE", "OK", null);        demoMode = true;        showMainForm();    }

//显示搜索结果页面,传的那些值都来自于MainForm的文本框,这个页面时用来显示List的    //测试时有虽然有396条数据,结果却显示的非常流畅    private void showSearchResultForm(String searchFor, String location, String street, String sortOrder) {        final Form resultForm = createForm("result list");        resultForm.setScrollable(false);        resultForm.setLayout(new BorderLayout());        InfiniteProgressIndicator tempIndicator = null;        try {            tempIndicator = new InfiniteProgressIndicator(Image.createImage("/wait-circle.png"));        } catch (IOException ex) {            tempIndicator = null;            ex.printStackTrace();        }        final InfiniteProgressIndicator indicator = tempIndicator;        final List resultList = new List(new LocalResultModel(searchFor, location, sortOrder, street)) {            public boolean animate() {                boolean val = super.animate();                // return true of animate only if there is data loading, this saves battery and CPU                if(indicator.animate()) {                    int index = getSelectedIndex();                    index = Math.max(0, index - 4);                    ListModel model = getModel();                    int dest = Math.min(index + 4, model.getSize());                    for(int iter = index ; iter < dest ; iter++) {                        if(model.getItemAt(index) == LOADING_MARKER) {                            return true;                        }                    }                }                return val;            }        };        Links pro = new Links();        pro.title = "prototype";        pro.tel = "9999999999";        pro.distance = "9999999";        pro.address = "Long address string";        pro.rating = "5";        resultList.setRenderingPrototype(pro);        resultList.setFixedSelection(List.FIXED_NONE_CYCLIC);        resultList.getStyle().setBorder(null);        //这一部分属于关键代码,还是比较容易懂的,作为Controller控制界面的显示        //我一直理解ListCellaRenderer为Controller,不知道理解有没有错。        resultList.setListCellRenderer(new DefaultListCellRenderer(false) {            private Label focus;            private Container selected;            private Label firstLine;            private Label secondLine;            private boolean loading;            //代码块,会在构造方法执行时一起执行            {                selected = new Container(new BoxLayout(BoxLayout.Y_AXIS));                firstLine = new Label("First Line");                secondLine = new Label("Second Line");                int iconWidth = 20;                firstLine.getStyle().setMargin(LEFT, iconWidth);                secondLine.getStyle().setMargin(LEFT, iconWidth);                selected.addComponent(firstLine);                selected.addComponent(secondLine);            }            public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {                if(value == null || value == LOADING_MARKER) {                    loading = true;                    if(isSelected) {                        firstLine.setText("Loading...");                        secondLine.setText("Loading...");                        return selected;                    }                    return indicator;                }                loading = false;                //如果为选中状态就显示Label为白色                if(isSelected) {                    int listSelectionColor = list.getStyle().getFgSelectionColor();                    firstLine.getStyle().setFgColor(0xffffff);                    secondLine.getStyle().setFgColor(0xffffff);                    firstLine.getStyle().setBgTransparency(0);                    secondLine.getStyle().setBgTransparency(0);                    Links l = (Links)value;                    firstLine.setText(l.address + " " + l.tel);                    secondLine.setText(l.distance + " miles " + ("".equals(l.rating) ? "" : ", " + l.rating + "*"));                    return selected;                }                //如果为未选中状态,恢复Label的默认显示                super.getListCellRendererComponent(list, ((Links)value).title, index, isSelected);                return this;            }            public void paint(Graphics g) {                if(loading) {                    indicator.setX(getX());                    indicator.setY(getY());                    indicator.setWidth(getWidth());                    indicator.setHeight(getHeight());                    indicator.paint(g);                } else {                    super.paint(g);                }            }            //在List的选中项前添加一个箭头图标            public Component getListFocusComponent(List list) {                if(focus == null) {                    try {                        focus = new Label(Image.createImage("/svgSelectionMarker.png"));                        focus.getStyle().setBgTransparency(0);                    } catch (IOException ex1) {                        ex1.printStackTrace();                    }                }                return focus;            }        });        resultForm.addComponent(BorderLayout.CENTER, resultList);        resultForm.addCommand(new Command("Map") {            public void actionPerformed(ActionEvent ev) {                showMap(resultForm, resultList.getSelectedItem());            }        });        resultForm.addCommand(new Command("Details") {            public void actionPerformed(ActionEvent ev) {                showDetails(resultForm, resultList.getSelectedItem());            }        });        resultForm.addCommand(new Command("Back") {            public void actionPerformed(ActionEvent ev) {                showMainForm();            }        });        resultForm.addCommand(exitCommand);        resultForm.show();    }

 /**     * A list model that lazily fetches a result over the web if its unavailable     * 这个类就非常关键了,它是整个List动态加载数据的核心     */    class LocalResultModel implements ListModel {        private Vector cache;        private Arg[] args;        private boolean fetching;        private Vector fetchQueue = new Vector();        private Vector dataListeners = new Vector();        private Vector selectionListeners = new Vector();        private int selectedIndex = 0;        private boolean firstTime = true;        public LocalResultModel(String searchFor, String location, String sortOrder, String street) {            cache = new Vector();            cache.setSize(1);            args = new Arg[]{                new Arg("output", "json"),                new Arg("appid", APPID),                new Arg("query", searchFor),                new Arg("location", location),                new Arg("sort", sortOrder.toLowerCase()),                null,                null            };            final String str = street;            if (!"".equals(str)) {                args[6] = new Arg("street", str);            }        }        /**         * 这里就是它动态加载的强大之处了,startOffset相当于一个索引值,         * 396条数据并不是1次性的加载,手机屏幕就那么大,满屏也只能显示几条数据,         * 作者就让它只加载10条数据,如果用户想浏览后面的数据,它会根据这个startOffset值         * 请求服务器,返回10条数据。比如第一次请求startOffset = 1,那么第二次就是11,         * 每次只请求10条数据。         * @param startOffset         */        private void fetch(final int startOffset) {            int count = Math.min(cache.size(), startOffset + 9);            for(int iter = startOffset - 1 ; iter < count ; iter++) {                if(cache.elementAt(iter) == null) {                    cache.setElementAt(LOADING_MARKER, iter);                }            }            if(!fetching) {                fetching = true;                new Thread() {                    public void run() {                        if(firstTime) {                            firstTime = false;                            try {                                // yield a bit CPU the first time around since the model                                // call might occur before the display is refreshed                                Thread.sleep(400);                            } catch (InterruptedException ex) {                                ex.printStackTrace();                            }                        }                        fetchThread(startOffset);                        while(fetchQueue.size() > 0) {                            int i = ((Integer)fetchQueue.elementAt(0)).intValue();                            fetchQueue.removeElementAt(0);                            fetchThread(i);                        }                        fetching = false;                    }                }.start();            } else {                fetchQueue.addElement(new Integer(startOffset));            }        }        //这个方法就是根据startOffset进行请求,然后返回相应的数据        private void fetchThread(int startOffset) {            try {                Response response;                args[5] = new Arg("start", Integer.toString(startOffset));                if (!demoMode) {                    response = Request.get(LOCAL_BASE, args, null, null);                } else {                    response = Request.get(Request.DEMO_URL, args, null, null);                }                final Exception ex = response.getException();                if (ex != null || response.getCode() != HttpConnection.HTTP_OK) {                    Dialog.show("Error", "Error connecting to search service - Turning on DEMO MODE", "OK", null);                    demoMode = true;                    showMainForm();                    return;                }                Result result = response.getResult();                //String mapAllLink = result.getAsString("ResultSet.ResultSetMapUrl");                int totalResultsAvailable = result.getAsInteger("ResultSet.totalResultsAvailable");                final int resultCount = result.getSizeOfArray("ResultSet.Result");                // this is the first time... set the size of the vector to match the results!                if(startOffset == 1) {                    cache.setSize(totalResultsAvailable);                }                for(int i = 0 ; i < resultCount ; i++) {                    String title = result.getAsString("ResultSet.Result["+i+"].Title");                    Links link = new Links();                    link.title = title;                    link.address = result.getAsString("ResultSet.Result["+i+"].Address");                    link.map = result.getAsString("ResultSet.Result["+i+"].MapUrl");                    link.listing = result.getAsString("ResultSet.Result["+i+"].ClickUrl");                    link.business = result.getAsString("ResultSet.Result["+i+"].BusinessClickUrl");                    link.tel = result.getAsString("ResultSet.Result["+i+"].Phone");                    link.latitude = result.getAsString("ResultSet.Result["+i+"].Latitude");                    link.longitude = result.getAsString("ResultSet.Result["+i+"].Longitude");                    link.rating = result.getAsString("ResultSet.Result["+i+"].Rating.AverageRating");                    link.distance = result.getAsString("ResultSet.Result["+i+"].Distance");                    cache.setElementAt(link, startOffset + i - 1);                    fireDataChangedEvent(DataChangedListener.CHANGED, startOffset + i - 1);                }            } catch (Exception ex) {                exception(ex);            }        }        public Object getItemAt(int index) {            Object val = cache.elementAt(index);            if(val == null) {                fetch(index + 1);                return LOADING_MARKER;            }            return val;        }        public int getSize() {            return cache.size();        }        public void setSelectedIndex(int index) {            int oldIndex = selectedIndex;            this.selectedIndex = index;            fireSelectionEvent(oldIndex, selectedIndex);        }        public void addDataChangedListener(DataChangedListener l) {            dataListeners.addElement(l);        }        public void removeDataChangedListener(DataChangedListener l) {            dataListeners.removeElement(l);        }        private void fireDataChangedEvent(final int status, final int index){            if(!Display.getInstance().isEdt()) {                Display.getInstance().callSeriallyAndWait(new Runnable() {                    public void run() {                        fireDataChangedEvent(status, index);                    }                });                return;            }            // we query size with every iteration and avoid an Enumeration since a data            // changed event can remove a listener instance thus break the enum...            for(int iter = 0 ; iter < dataListeners.size() ; iter++) {                DataChangedListener l = (DataChangedListener)dataListeners.elementAt(iter);                l.dataChanged(status, index);            }        }        public void addSelectionListener(SelectionListener l) {            selectionListeners.addElement(l);        }        public void removeSelectionListener(SelectionListener l) {            selectionListeners.removeElement(l);        }        private void fireSelectionEvent(int oldIndex, int newIndex){            Enumeration listenersEnum = selectionListeners.elements();            while(listenersEnum.hasMoreElements()){                SelectionListener l = (SelectionListener)listenersEnum.nextElement();                l.selectionChanged(oldIndex, newIndex);            }        }        public void addItem(Object item) {        }        public void removeItem(int index) {        }        public int getSelectedIndex() {            return selectedIndex;        }    }

注意一点:private void fetch(final int startOffset)方法在我实际的测试中,第一次请求startOffset = 1,第二次请求startOffset = 2,至今我还不明白为什么第一次请求的时候那个线程没有运行,从第三次请求开始startoffSet = 12,以后的请求都很正常,都是返回10条数据。

可能我对以上代码解释的不够清楚,但现在又了一个大概的了解,我对实现这种List做了一个简要的需求:

1、手机在首次获取数据或者刷新数据时,将从服务器发起请求,如果有很多条数据,并不是一次性返回给手机端供手机接收。
2、在手机端用List显示数据时需要按时间降序来显示,所以服务器端返回的数据也是按时间降序的形式返回xml或者json。
3、请求的方式按照这种方式来进行:第一次请求时返回10条数据,如果用户浏览完这10条数据,想继续浏览更多的数据,这再次发送请求,依次类推,每次请求只返回10条数据。
4、服务器端对数据要做分页处理,根据请求的起始索引和请求的数据条数(最后一次请求可能没有10条数据),返回相应的数据。进行web请求时,至少要提供startOffset和resultCount这两个参数。
5、List在显示时,不能用默认的List的MVC写法,其中Model部分和Controller部分需要自己进行构造。这样做的作用:一是能够节约内存并加快数据加载速度,二是能够比较灵活的构造List中的View部分。(目前最后一点我还不是很确信,但是感觉上比我用原有的List快一些,原有的List的Model部分是不用像这样继承几口然后,接口里面有一堆方法要实现,如果这个确实能够加快速度,多写点代码也不为过,毕竟大量的数据显示性能非常重要)。

下面是我删减后的代码,有了上面的基础,理解起来应该更轻松了!

/** * * @author 水货程序员 */public class MembersForm extends Form {    static final Object LOADING_MARKER = new Object();    //InfiniteProgressIndicator这个类在LWUIT_MakeOver中有,是用来显示动画的。    //我在http://blog.csdn.net/pjw100/archive/2009/12/14/5006882.aspx解释了这个类    InfiniteProgressIndicator indicator = null;    InfiniteProgressIndicator tempIndicator = null;    //构造方法    public MembersForm() {        setScrollable(false);        setLayout(new BorderLayout());        try {            tempIndicator = new InfiniteProgressIndicator(Image.createImage("/wait-circle.png"));        } catch (IOException ex) {            ex.printStackTrace();        }        indicator = tempIndicator;        showSearchResultForm();    }    private void showSearchResultForm() {        final List resultList = new List(new LocalResultModel()) {            public boolean animate() {                boolean val = super.animate();                // return true of animate only if there is data loading, this saves battery and CPU                if (indicator.animate()) {                    int index = getSelectedIndex();                    index = Math.max(0, index - 4);                    ListModel model = getModel();                    int dest = Math.min(index + 4, model.getSize());                    for (int iter = index; iter < dest; iter++) {                        if (model.getItemAt(iter) == LOADING_MARKER) {                            return true;                        }                    }                }                return val;            }        };        Person pro = new Person();        pro.photo = null;        pro.name = "Sunny";        pro.sex = "male";        pro.address = "Long address string";        resultList.setRenderingPrototype(pro);        resultList.setFixedSelection(List.FIXED_NONE_CYCLIC);        resultList.getStyle().setBorder(null);        resultList.setListCellRenderer(new DefaultListCellRenderer(false) {            private Label focus;            private Container selected;            private Container selectedInfo;            private Label selectedPhoto;            private Label selectedName;            private Label selectedSex;            private Label selectedAddress;            private boolean loading;            {                selected = new Container(new BoxLayout(BoxLayout.X_AXIS));                selectedInfo = new Container(new BoxLayout(BoxLayout.Y_AXIS));                selectedPhoto = new Label();                selectedName = new Label();                selectedSex = new Label();                selectedAddress = new Label();                int iconWidth = 20;                selectedPhoto.getStyle().setMargin(LEFT, iconWidth);                selectedInfo.addComponent(selectedName);                //selectedInfo.addComponent(selectedSex);                selectedInfo.addComponent(selectedAddress);                selected.addComponent(selectedPhoto);                selected.addComponent(selectedInfo);            }            public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {                if (value == null || value == LOADING_MARKER) {                    loading = true;                    if (isSelected) {                        //unselectedPhoto.setText("Loading...");                        return selected;                    }                    return indicator;                }                loading = false;                if (isSelected) {                    int listSelectionColor = list.getStyle().getFgSelectionColor();                    selectedName.getStyle().setFgColor(listSelectionColor);                    selectedSex.getStyle().setFgColor(listSelectionColor);                    selectedAddress.getStyle().setFgColor(listSelectionColor);                    selectedName.getStyle().setBgTransparency(0);                    selectedSex.getStyle().setBgTransparency(0);                    selectedAddress.getStyle().setBgTransparency(0);                    Person person = (Person) value;                    selectedPhoto.setIcon(person.photo);                    selectedName.setText(person.name);                    selectedSex.setText(person.sex);                    selectedAddress.setText(person.address);                    return selected;                }                super.getListCellRendererComponent(list, ((Person) value).name, index, isSelected);                return this;            }            public void paint(Graphics g) {                if (loading) {                    indicator.setX(getX());                    indicator.setY(getY());                    indicator.setWidth(getWidth());                    indicator.setHeight(getHeight());                    indicator.paint(g);                } else {                    super.paint(g);                }            }            public Component getListFocusComponent(List list) {                if (focus == null) {                    try {                        focus = new Label(Image.createImage("/svgSelectionMarker.png"));                        focus.getStyle().setBgTransparency(0);                    } catch (IOException ex1) {                        ex1.printStackTrace();                    }                }                return focus;            }        });        this.addComponent(BorderLayout.CENTER, resultList);    }    private void exception(Exception ex) {        ex.printStackTrace();        Dialog.show("Error", "Error connecting to search service - Turning on DEMO MODE", "OK", null);    }    class LocalResultModel implements ListModel {        private Vector cache;        //private Arg[] args;        private boolean fetching;        private Vector fetchQueue = new Vector();        private Vector dataListeners = new Vector();        private Vector selectionListeners = new Vector();        private int selectedIndex = 0;        private boolean firstTime = true;        public LocalResultModel() {            cache = new Vector();            cache.setSize(1);        }        private void fetch(final int startOffset) {            int count = Math.min(cache.size(), startOffset + 9);            for (int iter = startOffset - 1; iter < count; iter++) {                if (cache.elementAt(iter) == null) {                    cache.setElementAt(LOADING_MARKER, iter);                }            }            if (!fetching) {                fetching = true;                new Thread() {                    public void run() {                        if (firstTime) {                            firstTime = false;                            try {                                // yield a bit CPU the first time around since the model                                // call might occur before the display is refreshed                                Thread.sleep(400);                            } catch (InterruptedException ex) {                                ex.printStackTrace();                            }                        }                        fetchThread(startOffset);                        while (fetchQueue.size() > 0) {                            int i = ((Integer) fetchQueue.elementAt(0)).intValue();                            fetchQueue.removeElementAt(0);                            fetchThread(i);                        }                        fetching = false;                    }                }.start();            } else {                fetchQueue.addElement(new Integer(startOffset));            }        }        private void fetchThread(final int startOffset) {            System.out.println("startOffset:" + startOffset);            try {                //下面这两个变量都是进行模拟使用的,totalResultsAvailable代表记录总数                int totalResultsAvailable = 26;                int resultCount = 10;                //cache集合用来盛装数据,第一次请求时设置cache的大小                if (startOffset == 1) {                    cache.setSize(totalResultsAvailable);                }                //并不是每次请求都能够返回10条数据,比如最后一次请求可能只有5条数据,resultCount就不等于10了。                if (totalResultsAvailable - startOffset < 10) {                    resultCount = totalResultsAvailable - startOffset + 1;                }                Image pic = null;                try {                    pic = Image.createImage("/smallphoto.jpg");                } catch (Exception ex) {                    ex.printStackTrace();                }                //模拟从服务器端请求的数据,返回一个对象数组,然后遍历数组,把数据添加到cache中,缓存起来。                Person[] personArr = getPersons(startOffset, resultCount);                for (int i = 0; i < resultCount; i++) {                    cache.setElementAt(personArr[i], startOffset + i - 1);                    fireDataChangedEvent(DataChangedListener.CHANGED, startOffset + i - 1);                }            } catch (Exception ex) {                exception(ex);            }        }        //由于自己的Demo没有服务端,我在这里做了模拟数据,startOffset为索引值,count为每次请求的记录条数        private Person[] getPersons(int startOffset, int count) {            Image pic = null;            try {                pic = Image.createImage("/smallphoto.jpg");            } catch (Exception ex) {                ex.printStackTrace();            }            Person[] personArr = new Person[26];            for (int i = 0; i < 26; i++) {                char  c= (char)(65+i);                String sname = String.valueOf(c);                personArr[i] = new Person();                personArr[i].name = sname;                personArr[i].sex = "";                personArr[i].address = "深圳";                personArr[i].photo = pic;            }            Person[] datas = new Person[count];            for(int i = 0;inew Person();                datas[i] = personArr[startOffset + i -1];            }            return datas;        }        public Object getItemAt(int index) {            Object val = cache.elementAt(index);            if (val == null) {                fetch(index + 1);                return LOADING_MARKER;            }            return val;        }        public int getSize() {            return cache.size();        }        public void setSelectedIndex(int index) {            int oldIndex = selectedIndex;            this.selectedIndex = index;            fireSelectionEvent(oldIndex, selectedIndex);        }        public void addDataChangedListener(DataChangedListener l) {            dataListeners.addElement(l);        }        public void removeDataChangedListener(DataChangedListener l) {            dataListeners.removeElement(l);        }        private void fireDataChangedEvent(final int status, final int index) {            if (!Display.getInstance().isEdt()) {                Display.getInstance().callSeriallyAndWait(new Runnable() {                    public void run() {                        fireDataChangedEvent(status, index);                    }                });                return;            }            // we query size with every iteration and avoid an Enumeration since a data            // changed event can remove a listener instance thus break the enum...            for (int iter = 0; iter < dataListeners.size(); iter++) {                DataChangedListener l = (DataChangedListener) dataListeners.elementAt(iter);                l.dataChanged(status, index);            }        }        public void addSelectionListener(SelectionListener l) {            selectionListeners.addElement(l);        }        public void removeSelectionListener(SelectionListener l) {            selectionListeners.removeElement(l);        }        private void fireSelectionEvent(int oldIndex, int newIndex) {            Enumeration listenersEnum = selectionListeners.elements();            while (listenersEnum.hasMoreElements()) {                SelectionListener l = (SelectionListener) listenersEnum.nextElement();                l.selectionChanged(oldIndex, newIndex);            }        }        public void addItem(Object item) {        }        public void removeItem(int index) {        }        public int getSelectedIndex() {            return selectedIndex;        }    }    //实体类    private static class Person {        Image photo;        String name;        String sex;        String address;    }}

附上效果截图:

Technorati 标签: LWUIT的List运用


第一张:加载Form
第二张:首次加载时的效果
第三张:滑动滚动条时,动态加载的效果,加载时间1秒都不到,很流畅!

0 1 2

原创粉丝点击