2013年5月20日星期一

ADF_223:ADF Mobile 11.1.2.4 Samples 介绍(9):StockTracker

开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

StockTracker演示了当数据改变时,如何使用Java把改变的数据显示到用户界面上。
这是一个非常实用的例子,其中数据的改变使用了Java多线程技术,并且无需用户操作,更新后的数据会自动显示在界面上。

知识点:

1. 列表功能的实现

Portfolio.amx页面主要代码:

<amx:listView var="row" value="#{bindings.stocks.collectionModel}" id="listView1">
  <amx:listItem id="listItem1" action="#{viewScope.StockBean.GetAction}">
      <amx:tableLayout id="tl1" width="100%">
          <amx:rowLayout id="rl1">
              <amx:cellFormat id="cf1" width="10px" rowSpan="2"/>
              <amx:cellFormat id="cf2" width="60%" height="28px">
                  <amx:outputText value="#{row.ticker}" id="outputText2"/>
              </amx:cellFormat>
              <amx:cellFormat id="cf3" width="10px" rowSpan="2"/>
              <amx:cellFormat id="cf4" width="40%" halign="end">
                  <amx:outputText value="#{row.price}" id="outputText4"
                                  styleClass="adfmf-listItem-highlightText" inlineStyle="#{row.change}"
                                  rendered="#{row.ticker ne bindings.deleteTicker.inputValue}">
                      <amx:convertNumber groupingUsed="true" currencySymbol="$" minFractionDigits="2"
                                         maxFractionDigits="2"/>
                  </amx:outputText>
              </amx:cellFormat>
          </amx:rowLayout>
          <amx:rowLayout id="rla2">
              <amx:cellFormat id="cf5" width="60%" height="12px">
                  <amx:outputText value="#{row.company}" id="outputText3"
                                  styleClass="adfmf-listItem-captionText"/>
              </amx:cellFormat>
              <amx:cellFormat id="cf6" width="40%" halign="end">
                  <amx:outputText value="#{row.volume}" id="outputText5"
                                  styleClass="adfmf-listItem-captionText"
                                  rendered="#{row.ticker ne bindings.deleteTicker.inputValue}">
                      <amx:convertNumber groupingUsed="true" integerOnly="true"/>
                  </amx:outputText>
              </amx:cellFormat>
          </amx:rowLayout>
      </amx:tableLayout>
      <amx:actionListener binding="#{bindings.StopRefresher.execute}"/>
      <amx:commandButton id="commandButton1" text="Delete" styleClass="adfmf-commandButton-alert"
                         rendered="#{row.ticker eq bindings.deleteTicker.inputValue}"
                         actionListener="#{bindings.RemoveStock.execute}"
                         inlineStyle="position:absolute;margin:5px;right:0;top:0;"/>
      <amx:setPropertyListener from="#{row.rowKey}" to="#{pageFlowScope.ticker}"/>
      <amx:setPropertyListener from="#{row.ticker}" to="#{bindings.deleteTicker.inputValue}"
                               type="swipeRight"/>
  </amx:listItem>
</amx:listView>

可以看出:
(1)集合数据是来自Portfolio.java中的stocks对象,即getStocks()返回的对象。
(2)inlineStyle="#{row.change}",来自Stock.java的getChange()方法,它会根据新旧价格的对比,显示红色或绿色。
(3)使用amx:convertNumber,转换数字格式。
(4)点击某一项,会调用setPropertyListener,把#{row.rowKey}传递给#{pageFlowScope.ticker}。
(5)向右滑动某一项,会调用setPropertyListener,把#{row.ticker} 传递给#{bindings.deleteTicker.inputValue}。
(6)当#{row.ticker eq bindings.deleteTicker.inputValue}时,会显示Delete按钮,隐藏原来的股票价格和数量。

2. 删除功能的实现
点击Delete按钮,会调用Portfolio.java中的RemoveStock()方法,删除完数据之后,使用providerChangeSupport.fireProviderDelete()来告诉界面发生了Delete事件,这样才能刷新界面。

3. 增加功能的实现
页面代码

<amx:commandButton id="addbutton" text="Add">
    <amx:actionListener binding="#{bindings.StopRefresher.execute}"/>
    <amx:actionListener binding="#{bindings.InitNewStock.execute}"/>
    <amx:showPopupBehavior popupId="add" align="overlapTop" alignId="listView1"/>
</amx:commandButton>
点击Add按钮时做了三件事:
(1)调用Portfolio.java中的StopRefresher,停止多线程
(2)调用Portfolio.java中的InitNewStock,初始化一个新股票。
(3)弹出一个popup窗口。
添加完毕后,点击Save按钮时,调用Portfolio.java中的AddNewStock,增加一个新股票之后,使用providerChangeSupport.fireProviderCreate()来告诉界面发生了Create事件,这样才能刷新界面。

4. Change功能的实现
点击Change按钮,会调用Portfolio.java中的DoChange()方法,注意该方法的描述符有synchronized,说明这个方法会被多线程调用。

5. 多线程的启动与停止
(1)点击Start按钮,会调用Portfolio.java中的StartRefresher()方法,其中设置属性go=true。
(2)点击Stop按钮,会调用Portfolio.java中的StopRefresher()方法,其中设置属性go=false。
多线程的定义是在Refresher.java中,当属性go=true时,每隔1秒钟会调用Portfolio.java中的DoChange()方法,然后调用AdfmfJavaUtilities.flushDataChangeEvent()刷新界面。

6. Portfolio.java 代码

package Portfolio;

import com.sun.util.logging.Level;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;
import oracle.adfmf.java.beans.ProviderChangeListener;
import oracle.adfmf.java.beans.ProviderChangeSupport;
import oracle.adfmf.util.Utility;
import oracle.adfmf.util.logging.Trace;

public class Portfolio {
    protected transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this);

    private static List s_stocks = null;
    private Stock newStock = new Stock(0, "", "", new Double(1.0), new Double(1.0), new Long(1000));
    private Refresher refresher = new Refresher(this);
    private Thread worker = new Thread(refresher);
    private String deleteTicker = "";
    private boolean threadStarted = false;
    private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    public void addProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.addProviderChangeListener(l);
    }

    public void removeProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.removeProviderChangeListener(l);
    }

    public Portfolio() {
        if (s_stocks == null) {
            s_stocks = new ArrayList();
        }
        ResetStocks();
    }

    public synchronized void ResetStocks() {
        s_stocks.clear();

        s_stocks.add((new Stock(0, "ORCL", "Oracle Corp.", new Double(28.55), new Double(27.15), new Long(9919000))));
        s_stocks.add((new Stock(1, "MSFT", "Microsoft Corp", new Double(32.29), new Double(34.00),
                                new Long(22454330))));
        s_stocks.add((new Stock(2, "AAPL", "Apple Inc.", new Double(392.51), new Double(389.30), new Long(6559023))));
        s_stocks.add((new Stock(3, "RIMM", "Research In Motion Limited", new Double(30.02), new Double(30.72),
                                new Long(7340100))));
        s_stocks.add((new Stock(4, "GOOG", "Google Inc.", new Double(537.93), new Double(532.07), new Long(908766))));
        providerChangeSupport.fireProviderRefresh("stocks");
    }

    public synchronized Stock[] getStocks() {
        Stock s[] = null;

        s = (Stock[])s_stocks.toArray(new Stock[s_stocks.size()]);

        return s;
    }


    public synchronized void DoChange() {
        Random r = new Random(new Random().nextInt(1000));

        int i = 0;
        while (i < s_stocks.size()) {
            Stock s = (Stock)s_stocks.get(i);
            int ran = r.nextInt(1000);
            double change = ((double)ran - 500) / 10000 + 1;
            s.setPrice(new Double(s.getPrice().doubleValue() * change));
            s_stocks.set(i, s);
            i++;
        }
    }

    public void setNewStock(Stock newStock) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, Portfolio.class, "setNewStock",
                  "##################Inside setNewStock!!!");
        this.newStock = newStock;
    }

    public Stock getNewStock() {
        return newStock;
    }

    public void InitNewStock() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, Portfolio.class, "InitNewStock",
                  "##################Inside InitNewStock!!!");
        newStock.setId(0);
        newStock.setTicker("");
        newStock.setCompany("");
        newStock.setPrice(new Double(1.0));
        newStock.setPrevious(new Double(1.0));
        newStock.setVolume(new Long(1000));
    }

    public synchronized void AddNewStock() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, Portfolio.class, "AddNewStock",
                  "##################Inside AddNewStock!!!");
        int newId = 0;
        int size = s_stocks.size();
        for (int i = 0; i < size; i++) {
            Stock s = (Stock)s_stocks.get(i);
            if (s.getId() > newId) {
                newId = s.getId() + 1;
            }
        }


        Stock stock =
            new Stock(newId, newStock.getTicker(), newStock.getCompany(), newStock.getPrice(), newStock.getPrevious(),
                      newStock.getVolume());

        s_stocks.add(stock);
        providerChangeSupport.fireProviderCreate("stocks", stock.getTicker(), stock);
    }

    public synchronized void RemoveStock(String ticker) {
        Stock stock = null;

        int size = s_stocks.size();
        for (int i = 0; i < size; i++) {
            Stock s = (Stock)s_stocks.get(i);
            if (s.getTicker().compareTo(ticker) == 0) {
                setDeleteTicker("");
                stock = (Stock)s_stocks.remove(i);
                providerChangeSupport.fireProviderDelete("stocks", stock.getTicker());
                break;
            }
        }
    }

    public void StartRefresher() {
        setThreadStarted(true);
        refresher.go = true;
        if (!worker.isAlive()) {
            worker.start();
        }
        setThreadStarted(refresher.go);
    }

    public void StopRefresher() {
        if (refresher.go == true) {
            refresher.go = false;
            setThreadStarted(false);
        }
    }


    public void setDeleteTicker(String deleteTicker) {
        String oldDeleteTicker = this.deleteTicker;
        this.deleteTicker = deleteTicker;
        propertyChangeSupport.firePropertyChange("deleteTicker", oldDeleteTicker, deleteTicker);
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public String getDeleteTicker() {
        return deleteTicker;
    }

    public void setThreadStarted(boolean threadStarted) {
        boolean oldThreadStarted = this.threadStarted;
        this.threadStarted = threadStarted;
        propertyChangeSupport.firePropertyChange("threadStarted", oldThreadStarted, threadStarted);
    }

    public boolean getThreadStarted() {
        return threadStarted;
    }
}

7. StockBean.java 代码

package Portfolio;

import javax.el.ValueExpression;

import oracle.adfmf.framework.api.AdfmfJavaUtilities;

public class StockBean {
    public StockBean() {
    }

    public String GetAction() {
        String outcome = "";
        ValueExpression ve = AdfmfJavaUtilities.getValueExpression("#{bindings.deleteTicker.inputValue}", String.class);
        String deleteTicker = (String)ve.getValue(AdfmfJavaUtilities.getAdfELContext());

        if (deleteTicker.length() == 0) {
            outcome = "details";
        } else {
            ve.setValue(AdfmfJavaUtilities.getAdfELContext(), "");
        }
        return outcome;
    }
}

8. Stock.java 部分代码

    public String getChange() {
        String c = "color:green";
        if( price.doubleValue() < previous.doubleValue() ) {
            c = "color:red";
        }
        return c;
    }

9. Refresher.java 代码

package Portfolio;

import oracle.adfmf.framework.api.AdfmfJavaUtilities;

public class Refresher implements Runnable {

    public Refresher() {
        super();
    }

    Portfolio p = null;

    public Refresher(Portfolio p) {
        this.p = p;
    }

    boolean go = false;

    public void run() {
        while (true) {
            if (go) {
                p.DoChange();
                AdfmfJavaUtilities.flushDataChangeEvent(); 
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
}

没有评论: