使用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程式碼- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.FIELD})
- public @interface InjectView {
- int value();
- }
測試Annotation
Java程式碼- @InjectView(R.id.aView) View aView;
Annotation就是一個標識,標識出aView是InjectView關心的欄位。
進行注入
Java程式碼-
Field[] declaredFields = getClass().getDeclaredFields();
- for (Field field : declaredFields) {
- InjectView annotation = field.getAnnotation(InjectView.class);
- if (annotation != null) {
- int id = annotation.value();
- View view = findViewById(id);
- field.setAccessible(true);
- try {
-
field.set(this, view);
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
在setContentView之後,我們對所有的field檢查是否包含@InjectView標識,對包含@InjectView標識的field,進行賦值。
通過同樣的方式,我們甚至可以把setContentView省去,也通過注入來實現。假設我們有一個@InjectLayout的Annotation,我們通過這個Annotation來標識setContentView對應的id.
Java程式碼- @InjectLayout(R.layout.activity_main)
- public class MainActivity extends FragmentActivity {
- InjectLayout annotation = getClass().getAnnotation(InjectLayout.class);
- int id = annotation.value();
- try {
- Method setContentView = getClass().getMethod("setContentView", int.class);
- setContentView.invoke(this, id);
- } catch (Exception e) {
- e.printStackTrace();
- }
把上面的這些程式碼封裝到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程式碼- public View getView(int position, View convertView, ViewGroup parent) {
- final ViewHolder viewHolder;
- if (convertView == null) {
- convertView = layoutInflater.inflate(R.layout.element_picture_event, null);
- viewHolder = new ViewHolder(convertView);
- convertView.setTag(viewHolder);
- } else {
- viewHolder = (ViewHolder) convertView.getTag();
- }
- //do anything with view holder
- }
- ...
- class ViewHolder {
- @InjectViews({R.id.imageView0, R.id.imageView1, R.id.imageView2, R.id.imageView3}) ImageView[] imageViews;
- @InjectView(R.id.nicknameView) TextView nicknameView;
- @InjectView(R.id.infoView) TextView infoView;
- @InjectView(R.id.avatarView) ImageView avatarView;
- @InjectView(R.id.statusView) TextView statusView;
- public ViewHolder(View view) {
- ButterKnife.inject(this, view);
- }
- }
更多的使用方法,可以參考官方說明:
倫理片http://www.dotdy.com/
對IDE進行配置
在IDE裡面使用ButterKnife,需要對IDE進行適當的配置才可以正常的執行程式。
比如說在,IntelliJ IDEA裡面,需要開啟AnnotationProcessor配置。如圖:
對於Eclipse,官方也給出了相應的開啟AnnotationProcessor的方式,可以參考下面的連結
對於IDE的自動格式化程式碼,可能會強行將Annotation單獨在一行顯示,比如說這樣:
Java程式碼- @InjectView(R.id.ptrLayout)
- PullToRefreshLayout ptrLayout;
如果不喜歡上面的這種方式,可以在IDE中進行配置,比如說在IDEA中:
這樣就可以保證@InjectView和定義View在一行顯示了:
Java程式碼- @InjectView(R.id.ptrLayout) PullToRefreshLayout ptrLayout;
Ant中的配置
因為使用了AnnotationProcessor,在使用一些工具,比如說ant打包的時候,可能需要一些另外的配置,比如說,對於ant,需要加入一個處理Annotation的target. 參考程式碼如下:
Xml程式碼- <javac encoding="UTF-8"
- source="1.5" target="1.5"
- debug="false" extdirs="" includeantruntime="false"
- destdir="bin/classes"
- bootclasspathref="project.target.class.path"
- verbose="false"
- classpathref="project.javac.classpath"
- fork="false">
- <src path="src"/>
- <src path="gen"/>
- <compilerarg line=""/>
- <compilerarg line="-processorpath libs/butterknife-6.0.0.jar"/>
- </javac>
對於Maven或者Gradle,相信使用Annotation Processor + Maven或Gradle,就可以谷歌到相應的解決方案。