1. 程式人生 > >使用InjectView和findViewById說拜拜

使用InjectView和findViewById說拜拜

Android的findViewById真是太煩人了,模板似的方法,要寫在每個Activity,Fragment,Adapter裡面。宣告 View和findView總是間隔著未知的行距;setOnClickListener之後,總是要尋找對應的onClick方法在何處。

難道Android就不能智慧的把layout中的View相應的與對應field繫結起來?

答案是:Android本身不支援,但是我們可以通過一些hack達到目的。

基於反射的InjectView

findViewById接受一個int的id引數,就可以找到對應的View。通過annotation,我們可以把這個int型別的id宣告的對應的field上面,通過Java的反射,遍歷每個field,找到對應的id,就可以完成對field的賦值。

下面是具體做法:

宣告Annotation

Java程式碼  收藏程式碼
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target({ElementType.FIELD})  
  3. public @interface InjectView {  
  4.     int value();  
  5. }  

測試Annotation

Java程式碼  收藏程式碼
  1. @InjectView(R.id.aView) View aView;  

 Annotation就是一個標識,標識出aView是InjectView關心的欄位。

進行注入

Java程式碼  收藏程式碼
  1. Field[] declaredFields = getClass().getDeclaredFields();  
  2. for (Field field : declaredFields) {  
  3.     InjectView annotation = field.getAnnotation(InjectView.class);  
  4.     if (annotation != null) {  
  5.         int id = annotation.value();  
  6.         View view = findViewById(id);  
  7.         field.setAccessible(true);  
  8.         try {  
  9.             field.set(this, view);  
  10.         } catch (IllegalAccessException e) {  
  11.             e.printStackTrace();  
  12.         }  
  13.     }  
  14. }  

在setContentView之後,我們對所有的field檢查是否包含@InjectView標識,對包含@InjectView標識的field,進行賦值。

通過同樣的方式,我們甚至可以把setContentView省去,也通過注入來實現。假設我們有一個@InjectLayout的Annotation,我們通過這個Annotation來標識setContentView對應的id.

Java程式碼  收藏程式碼
  1. @InjectLayout(R.layout.activity_main)  
  2. public class MainActivity extends FragmentActivity {  
  3.   InjectLayout annotation = getClass().getAnnotation(InjectLayout.class);  
  4.   int id = annotation.value();  
  5.  try {  
  6.     Method setContentView = getClass().getMethod("setContentView"int.class);  
  7.     setContentView.invoke(this, id);  
  8.  } catch (Exception e) {  
  9.     e.printStackTrace();  
  10. }  

把上面的這些程式碼封裝到BaseActivity中,之後或許就再也不用在activity中寫setContentView和findViewById方法了。

同樣的,這樣的方式也適用於Fragment,只是可能需要一些變通。但是不難的。

開源專案

Github上有一個強大開源專案RoboGuice實現了一整套的注入功能,可以閱讀它的官方WIKI進行了解

效能

眾所周知,相對於正常的方法呼叫,反射呼叫在執行效率上會有劣勢,而且反射並不能得到編譯時期的優化,使得效能差距更加明顯。雖然現在 Android手機的平均效能比最開始要好很多,但是在有些時候,效能往往還是需要考慮的一個很重要的一點。這時候可能就會糾結便利的 @InjectView與反射帶來的部分效能損耗,誰更重要。

一種更高效的InjectView – ButterKnife

這是一個開源專案,ButterKnife,官方介紹是通過AnnotationProcessor實現的View Injection, 而不是反射,效能上面不會有什麼顧慮。

使用ButterKnife很簡單,只需要像上面提到的那樣,對View進行標註,然後在setContentView之後(或者對於Fragment在inflateView之後),呼叫適當的ButterKnife.inject(…)方法即可。

此外,ButterKnife的注入不依賴於反射,我們可以放心大膽的在Adapter#getView中使用:

Java程式碼  收藏程式碼
  1.  public View getView(int position, View convertView, ViewGroup parent) {  
  2.         final ViewHolder viewHolder;  
  3.         if (convertView == null) {  
  4.             convertView = layoutInflater.inflate(R.layout.element_picture_event, null);  
  5.             viewHolder = new ViewHolder(convertView);  
  6.             convertView.setTag(viewHolder);  
  7.         } else {  
  8.             viewHolder = (ViewHolder) convertView.getTag();  
  9.         }  
  10.         //do anything with view holder  
  11. }  
  12. ...  
  13. class ViewHolder {  
  14.     @InjectViews({R.id.imageView0, R.id.imageView1, R.id.imageView2, R.id.imageView3}) ImageView[] imageViews;  
  15.     @InjectView(R.id.nicknameView) TextView nicknameView;  
  16.     @InjectView(R.id.infoView) TextView infoView;  
  17.     @InjectView(R.id.avatarView) ImageView avatarView;  
  18.     @InjectView(R.id.statusView) TextView statusView;  
  19.     public ViewHolder(View view) {  
  20.         ButterKnife.inject(this, view);  
  21.     }  
  22. }  

更多的使用方法,可以參考官方說明:

倫理片http://www.dotdy.com/

對IDE進行配置

在IDE裡面使用ButterKnife,需要對IDE進行適當的配置才可以正常的執行程式。

比如說在,IntelliJ IDEA裡面,需要開啟AnnotationProcessor配置。如圖:

image

對於Eclipse,官方也給出了相應的開啟AnnotationProcessor的方式,可以參考下面的連結

對於IDE的自動格式化程式碼,可能會強行將Annotation單獨在一行顯示,比如說這樣:

Java程式碼  收藏程式碼
  1. @InjectView(R.id.ptrLayout)  
  2. PullToRefreshLayout ptrLayout;  

如果不喜歡上面的這種方式,可以在IDE中進行配置,比如說在IDEA中:

image

這樣就可以保證@InjectView和定義View在一行顯示了:

Java程式碼  收藏程式碼
  1. @InjectView(R.id.ptrLayout) PullToRefreshLayout ptrLayout;  

Ant中的配置

因為使用了AnnotationProcessor,在使用一些工具,比如說ant打包的時候,可能需要一些另外的配置,比如說,對於ant,需要加入一個處理Annotation的target. 參考程式碼如下:

Xml程式碼  收藏程式碼
  1. <javac encoding="UTF-8"  
  2.        source="1.5" target="1.5"  
  3.        debug="false" extdirs="" includeantruntime="false"  
  4.        destdir="bin/classes"  
  5.        bootclasspathref="project.target.class.path"  
  6.        verbose="false"  
  7.        classpathref="project.javac.classpath"  
  8.        fork="false">  
  9.     <src path="src"/>  
  10.     <src path="gen"/>  
  11.     <compilerarg line=""/>  
  12.     <compilerarg line="-processorpath libs/butterknife-6.0.0.jar"/>  
  13. </javac>  

對於Maven或者Gradle,相信使用Annotation Processor + Maven或Gradle,就可以谷歌到相應的解決方案。