自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android中提取表單模型

移動開發(fā) Android
Form Model”的基本思想是,把處理UI交互以及數(shù)據(jù)綁定和狀態(tài)保持的代碼提取到單獨的類中。這種分離非常自然,并且讓我們的Activity變得簡單。我認為在Android中這個領(lǐng)域不太被關(guān)注——在大多數(shù)的開發(fā)文檔中數(shù)據(jù)錄入和表單不是重點。在很多流行的社交應(yīng)用程序中,大多數(shù)的畫面只是顯示信息;可能也有幾個畫面用于發(fā)微博或者消息,但不是應(yīng)用的痛點。

我一直追求從Android活動中分離代碼。在最近的一個項目中,我成功的實現(xiàn)了傳統(tǒng)的”Form Model”模式,想在此分享我的感想。

“Form Model”的基本思想是,把處理UI交互以及數(shù)據(jù)綁定和狀態(tài)保持的代碼提取到單獨的類中。這種分離非常自然,并且讓我們的Activity變得簡單。

我認為在Android中這個領(lǐng)域不太被關(guān)注——在大多數(shù)的開發(fā)文檔中數(shù)據(jù)錄入和表單不是重點。在很多流行的社交應(yīng)用程序中,大多數(shù)的畫面只是顯示信息;可能也有幾個畫面用于發(fā)微博或者消息,但不是應(yīng)用的痛點。

對我來說,上兩個Android應(yīng)用有特別多的數(shù)據(jù)錄入工作。部分原因是因為所處的領(lǐng)域(醫(yī)療、金融)和客戶(更貼近于企業(yè)應(yīng)用而不是創(chuàng)業(yè))。但我 們經(jīng)常把“表單輸入”界面搞得一片混亂——特別是當(dāng)開始添加?xùn)|西的時候,比如編輯現(xiàn)有的條目,提示丟棄未保存的更改,以及處理旋轉(zhuǎn)而不會清除所有字段值。

使用這種表單模型方案會減少bug,讓代碼更容易理解,開發(fā)者也會變得更快樂。

搜索表單示例

我們有一個銀行應(yīng)用程序,希望有一個畫面來搜索交易數(shù)據(jù)。有多個過濾條件:開始是一個金額下拉列表,一個關(guān)鍵字字段和一個金額范圍。(希望你可以想象在未來將會增加更多的這類過濾器,復(fù)雜性會激增)。

我們沒有把所有的視圖、單擊處理程序,驗證邏輯和數(shù)據(jù)綁定的代碼堆到一個Activity中,而是要創(chuàng)建一個 SearchForm類來處理這一切。

 

  1. public class SearchForm extends LinearLayout { 
  2.   
  3.   @InjectView(R.id.account) 
  4.   private Spinner mAccountSpinner; 
  5.   private AccountAdapter mAccountAdapter; 
  6.   
  7.   @InjectView(R.id.keyword) 
  8.   private EditText mKeywordField; 
  9.   
  10.   @InjectView(R.id.min_amount) 
  11.   private CurrencyEditText mMinAmountField; 
  12.   
  13.   @InjectView(R.id.max_amount) 
  14.   private CurrencyEditText mMaxAmountField; 
  15.   
  16.   public SearchFormModel(Context context, AttributeSet attrs) { 
  17.     super(context, attrs); 
  18.     setup(context); 
  19.   } 
  20.   
  21.   private void setup(Context context) { 
  22.     LayoutInflater.from(context).inflate(R.layout.search_form, thistrue); 
  23.   
  24.     ButterKnife.inject(this); // <3 @JakeWharton 
  25.   
  26.     mAccountAdapter = new AccountAdapter(context); 
  27.     mAccountSpinner.setAdapter(mAccountAdapter); 
  28.   } 
  29.   
  30.   public initialize(List<Account> accounts) { 
  31.     mAccountAdapter.setItems(accounts); 
  32.   } 
  33.   
  34.   public String getKeywords() { 
  35.     return mKeywordField.getText().toString(); 
  36.   } 
  37.   
  38.   public void setKeywords(String keywords) { 
  39.     mKeywordField.setText(keywords); 
  40.   } 
  41.   
  42.   public MoneyAmount getMinimumAmount() { 
  43.     return mMinAmountField.getAmount(); 
  44.   } 
  45.   
  46.   public void setMinimumAmount(double amount) { 
  47.     mMinmountField.setAmountFromDouble(amount); 
  48.   } 
  49.   
  50.   public MoneyAmount getMaximumAmount() { 
  51.     return mMaxAmountField.getAmount(); 
  52.   } 
  53.   
  54.   public void setMaximumAmount(double amount) { 
  55.     mMaxAmountField.setAmountFromDouble(amount); 
  56.   } 
  57.   
  58.   public Account getSelectedAccount() { 
  59.     return mAccountSpinner.getSelectedItem(); 
  60.   }  
  61.   
  62.   public boolean validate() { 
  63.     clearErrors(); 
  64.     boolean isValid = true
  65.   
  66.     if (!isValidAmountRange()) { 
  67.       isValid = false
  68.       mMinAmountField.setError("Invalid range"); 
  69.       mMaxAmountField.setError("Invalid range"); 
  70.     } 
  71.   
  72.     return isValid; 
  73.   } 
  74.   
  75.   private boolean isValidAmountRange() { 
  76.     return getMinimumAmount() <= getMaximumAmount(); 
  77.   } 
  78.   
  79.   private void clearErrors() { 
  80.     mMinAmountField.setError(null); 
  81.     mMaxAmountField.setError(null); 
  82.   } 
  83.   
  84.   public SearchParameters buildParameters() { 
  85.     return new SearchParameters(getSelectedAccount(), 
  86.                                 getKeywords(), 
  87.                                 getMinimumAmount(), 
  88.                                 getMaximumAmount()); 
  89.   } 
  90.   
  91.   public void persist(Bundle outState) { 
  92.     outState.putInt("SELECTED_ACCT_INDEX", mAccountSpinner.getSelectedItemPosition()); 
  93.   } 
  94.   
  95.   public void restore(Bundle bundle) { 
  96.     int accountPosition = bundle.getInt("SELECTED_ACCT_INDEX"); 
  97.     mAccountSpinner.setSelection(accountPosition, false); 
  98.   } 

 #p#

我們創(chuàng)建了一個類,繼承自LinearLayout(或者FrameLayout,由你的喜好決定)。它允許把相關(guān)的控件組織到一個布局中,我們將填充布局,設(shè)置列表視圖并為金額列表創(chuàng)建一個適配器。

我們把Android控件封裝到getter和setter方法中​——這可能會有些爭議,但我認為它使SearchForm擁有更好的公共API。我們有一個方法來驗證用戶的輸入,并根據(jù)需要提供錯誤信息。 buildParameters()方法做了一些數(shù)據(jù)綁定工作并返回業(yè)務(wù)對象。結(jié)尾的兩個方法使用了Android onSaveInstanceState中的Bundle,以處理自定義配置的更改(注意,大多數(shù)的原始UI控件會自行處理持久化)。

這是個一百行左右的代碼,大部分還不錯。這個類中所有內(nèi)容似乎都屬于“搜索表單”對象,對未來的特性有良好的功能擴展點(日期范圍過濾器、支出與存款過濾器、只用支票等)。我們有意避免處理如何獲取數(shù)據(jù),把它留給了其他更適合的地方處理這些邏輯代碼。

活動中的代碼是什么樣的呢?

 

  1. public class TransactionSearchActivity extends BaseActivity { 
  2.   
  3.   @InjectView(R.id.search_form) 
  4.   private SearchForm mForm; 
  5.   
  6.   @Override 
  7.   public void onCreate(Bundle savedInstanceState) { 
  8.     super.onCreate(savedInstanceState); 
  9.   
  10.     setContentView(R.layout.transaction_search); 
  11.     setTitle("Search Your Transactions"); 
  12.   
  13.     mForm.initialize(mAccounts); // fetch accounts via API/DB/etc 
  14.   
  15.     if (savedInstanceState != null) { 
  16.       mForm.restore(savedInstanceState); 
  17.     } 
  18.   } 
  19.   
  20.   @Override 
  21.   public boolean onOptionsItemSelected(MenuItem menu) { 
  22.     switch (menu.getItemId()) { 
  23.       case R.id.action_submit_search: 
  24.         onSubmitSearch(); 
  25.         return true
  26.     } 
  27.   
  28.     return super.onOptionsItemSelected(menu) 
  29.   } 
  30.   
  31.   private void onSubmitSearch() { 
  32.     if (mForm.validate()) { 
  33.       // Do your magic, post to an API/DB/etc 
  34.       // You have access to the domain object with mForm.buildParameters() 
  35.     } 
  36.   } 
  37.   
  38.   @Override 
  39.   public boolean onCreateOptionsMenu(Menu menu) { 
  40.     getMenuInflater().inflate(R.menu.search_menu, menu); 
  41.     return super.onCreateOptionsMenu(menu); 
  42.   } 
  43.   
  44.   @Override 
  45.   protected void onSaveInstanceState(Bundle outState) { 
  46.     super.onSaveInstanceState(outState); 
  47.   
  48.     mForm.persist(outState); 
  49.   } 

 

我們的Activity在XML布局文件中包含了一個 **標簽,并且只處理高層面的用戶交互(點擊動作欄中的提交按鈕),并協(xié)調(diào)獲取和存儲數(shù)據(jù)。繁重的UI控制和表單邏輯都委托給了 **SearchForm。

Activity的代碼在50行左右——其中大部分是處理框架中生命周期和菜單創(chuàng)建的樣板代碼。

總體印象

一旦涉及到API或數(shù)據(jù)庫,事情總是會變得更復(fù)雜。但總體來講,通過把表單特定的邏輯和視圖相關(guān)內(nèi)容移出活動,代碼變得更容易理解。

我可以為 SearchForm編寫大量的Robolectric測試代碼而且不會帶來與活動生命周期有關(guān)的問題。我可以為表單的交互、動作欄、后端編寫測試代碼而不用考慮邊界。當(dāng)為表單添加新過濾條件時,可以避免對活動做任何的更改(類似于設(shè)計模式中的開/閉原則)。

對比其他框架(從其他開發(fā)人員的角度來說),Android中數(shù)據(jù)綁定功能很弱。這種設(shè)計似乎還差點什么,因為和Android的類耦合的過于緊 密,依賴于方法的調(diào)用順序(initialize()方法應(yīng)在validate()方法之前調(diào)用)——盡管如此,但我認為對于“所有內(nèi)容混在一起的 Activity”來說是一種改進。

隨著表單模型越來越復(fù)雜,你可能要考慮把驗證邏輯提取到一個單獨的對象中,并且把自定義視圖功能移動到自己的控件中(就像我們例子中的 CurrencyEditText)。此外,為了更好的為用戶服務(wù),也可以考慮把復(fù)雜的表單拆分成為多步驟向?qū)А?/p>

我們發(fā)現(xiàn)這種模式可以成功的清理亂糟糟的表單代碼,建議嘗試一下。我把代碼模式稍微規(guī)范了一下,并創(chuàng)建了一個小的基類,以減少樣板代碼,可以隨意的使用。

譯文鏈接:http://blog.jobbole.com/73195/

原文鏈接:mdswanson

本文鏈接:http://blog.jobbole.com/73195/

翻譯: 伯樂在線 - lum

 

責(zé)任編輯:chenqingxiang 來源: 伯樂在線
相關(guān)推薦

2023-11-29 11:30:17

PDF語言模型

2023-11-15 13:04:30

Python提取表格

2021-05-13 23:54:12

DockerDockerfile鏡像

2022-11-23 10:31:54

2020-07-08 07:54:03

PythonPDF數(shù)據(jù)

2025-02-17 12:00:00

PythonOpenCV提取圖像

2024-05-22 07:57:34

2022-09-29 15:39:10

服務(wù)器NettyReactor

2019-09-29 09:08:41

Python數(shù)據(jù)庫Google

2022-08-24 15:57:17

圖片輪廓

2016-01-26 11:08:54

2021-09-04 23:45:40

機器學(xué)習(xí)語言人工智能

2023-04-27 07:06:09

Categraf夜鶯

2013-04-01 11:14:56

IT大數(shù)據(jù)網(wǎng)絡(luò)信息化

2019-09-04 11:09:38

物聯(lián)網(wǎng)數(shù)據(jù)邊緣

2021-03-15 21:50:22

Linux提取文本GUI工具

2021-03-16 09:00:00

深度學(xué)習(xí)人工智能傳感器

2023-08-16 17:44:38

2021-08-16 11:51:16

微軟Windows 365Azure

2021-03-10 10:20:06

Linux文本命令
點贊
收藏

51CTO技術(shù)棧公眾號