Android Study之findViewById變遷之路
前言
今天我們一塊來聊聊項目常用的findViewById,這個東西可以簡單理解為:初始化控件,實例化控件,方便進行其他操作。一般來說,我們通常這么寫:
- private void initView() {
- TextView tvTest = (TextView) findViewById(R.id.id_test);
- }
上面的例子很簡單,只是初始化一個TextView,但是在實際項目中,每個Activity,F(xiàn)ragment或者Adapter中有n個控件,每個控件都需要我們實例化控件,才能對其進行操作,一次次的findViewById,感覺好煩吶~!
有沒有好辦法呢?當(dāng)然有很多種方式,但是我們要找適合自己項目的,下面將會為大家依次舉例說明~
通過注解方式簡化findViewById
在前幾年,Xutils比較火爆,火爆的原因有很多,簡單列舉下,LZ更看好Xutils使用方便,至少為我們封裝了很多常用的工具,就好比常用的惡心的圖片處理,Xutils有很好的支持,同樣,Xutils也支持注解方式去簡化findViewById,簡單舉例如下:
- // xUtils的view注解要求必須提供id,以使代碼混淆不受影響。@ViewInject(R.id.id_test)
- TextView tvTest ;
比較出名的ButterKnife,之前LZ也對此專門學(xué)習(xí)了下,相關(guān)文章地址如下:
《一篇文章玩轉(zhuǎn)ButterKnife,讓代碼更簡潔》
同理簡單舉例如下:
- @BindView(R.id.id_test)TextView tvTest;
以上簡單為大家列舉倆種,至少是LZ用到過的,當(dāng)讓有關(guān)支持注解方式的好用的還有很多,歡迎大家交流,一起學(xué)習(xí)~
個人封裝findViewById
剛剛在網(wǎng)上搜索,突然看到有一哥兒們經(jīng)過其老師啟發(fā),個人封裝了一個,LZ看到感覺不錯,先試試看看好不好用。
簡單修改之后,通過測試,感覺還不錯,下面為大家附上源碼:
- /**
- * Created by HLQ on 2017/6/25 0025.
- */
- public class FindView {
- private static Activity activity; // 運用了單例模式中的餓漢式
- private static final FindView findView = new FindView();
- /**
- * 獲取Activity實例
- *
- * @param activity
- */
- private static void setActivity(Activity activity) {
- FindView.activity = activity;
- } /**
- * 初始化FindView
- *
- * @param activitys
- * @return
- */
- public static FindView with(Activity activitys) {
- setActivity(activitys);
- return findView;
- } /**
- * 根據(jù)Id獲取View實例
- *
- * @param id
- * @param <T>
- * @return
- */
- public <T extends View> T getView(int id) {
- View view = activity.findViewById(id);
- return (T) view;
- } /**
- * 設(shè)置TextView內(nèi)容
- *
- * @param id
- * @param content
- * @return
- */
- public FindView setTextContent(int id, String content) {
- TextView textView = getView(id);
- textView.setText(content);
- return this;
- } /**
- * 設(shè)置ImageView 資源
- *
- * @param id
- * @param imgResource
- * @return
- */
- public FindView setImageResource(int id, int imgResource) {
- ImageView iv = getView(id);
- iv.setImageResource(imgResource);
- return this;
- }
- }
那么我們該如何使用呢?簡單說下,調(diào)用有如下倆種方式:
- 通過鏈?zhǔn)秸{(diào)用,你可以直接調(diào)用封裝好的setText or setImgResource進行直接賦值;
- 通過鏈?zhǔn)秸{(diào)用getView獲取控件實例,然后進行相應(yīng)操作即可。
還有一點,大家可自行根據(jù)項目進行拓展封裝類。
下面為大家附上具體倆種方式調(diào)用以及運行結(jié)果:
方式一 調(diào)用方式:
- FindView.with(this).setTextContent(R.id.id_test, "Hello").setImageResource(R.id.iv_test,R.mipmap.ic_launcher);
運行結(jié)果:
方式二 調(diào)用方式:
- TextView textView= FindView.with(this).getView(R.id.id_test);
- textView.setText("你好");
運行結(jié)果:
通過泛型來簡化findViewById
一般來說我們會在BaseActivity中定義一個泛型獲取View實例方法,如下:
- public final <E extends View> E getView(int id) {
- try {
- return (E) findViewById(id);
- } catch (ClassCastException ex) {
- Log.e("HLQ_Struggle", "Could not cast View to concrete class." + ex);
- throw ex;
- }
- }
調(diào)用很簡單,如下:
- TextView textView = getView(R.id.id_test);
- textView.setText("你在干嘛");
這個結(jié)果就不必說了吧?
抽取泛型方法為公共類
這里和上一個方法類型,但是上一個方法或有一些缺陷就是,我在非Activtiy中調(diào)用怎么辦呢?難不成要在各個Base中都要Copy一次?個人感覺不是很如意。
- /**
- * 獲取控件實例
- * @param view
- * @param viewId
- * @param <T>
- * @return View
- */
- public static <T extends View> T viewById(View view, int viewId) {
- return (T) view.findViewById(viewId);
- }
這樣一來,調(diào)用就有點惡心了,如下:
在Activitiy中調(diào)用方式如下:
- TextView textView = UIHelper.viewById(selfActivity.getWindow().getDecorView(), R.id.id_test);
Fragment中調(diào)用如下:
- TextView textView= UIHelper.viewById(getActivity().getWindow().getDecorView(), R.id.id_test);
如果喜歡來來回回Copy的同志,這也不妨是一種辦法。前方高能~
谷歌爸爸的DataBinding
話說這玩意LZ也是前幾天才知道,之前從未關(guān)注過,不過簡單了解后,發(fā)現(xiàn)確實很666,想必反射或者注解方式,性能上非常666;
下面引入目前來說比較6的說法:
DataBinding完全超越了findViewById!findViewById本質(zhì)上是一次對view樹的遍歷查找,每次調(diào)用控件都會查找一次,雖然是O(n)的性能,但多次調(diào)用就變成了O(n)x m。但DataBinding則不然,通過一次遍歷把所有的控件查找出來,然后賦值給對應(yīng)的變量,完全不依賴findViewById,在任何情況下,復(fù)雜度都是O(n)。同樣的是生成代碼,但數(shù)據(jù)綁定框架提供了更多的功能,提高工作效率,編寫更安全可靠的代碼。
So,我們還有什么理由拒絕呢?
簡單了解下吧,不然糊里糊涂的。
DataBinding簡介
DataBinding,2015年IO大會介紹的一個框架,字面理解即為數(shù)據(jù)綁定。由于一般的開發(fā)過程中,Activity既需要做實現(xiàn)網(wǎng)絡(luò)請求,又需要實現(xiàn)界面的渲染/用戶之間的交互,如果一個頁面的功能更為復(fù)雜,對后期的項目維護更加艱難。因此,推出該框架有利于簡化功能模塊,盡量將界面的渲染/用戶交互的功能分化在單獨的模塊中。
感受DataBinding魅力
說啥也不如直接擼碼來的實際,方便,快捷。下面通過一個簡單的小例子,開啟DataBinding Study。
1.build中配置開啟DataBinding
- dataBinding {
- enabled = true
- }
2.編寫我們的布局文件,在這里大家要記住以下幾點:
- 布局文件根使用< layout >< /layout >包裹;
- < layout >下只能有一個節(jié)點,這點和ScrollView一樣
基于以上倆點,編寫實現(xiàn)我們的布局文件:
- <?xml version="1.0" encoding="utf-8"?>
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <TextView
- android:id="@+id/id_test"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <ImageView
- android:id="@+id/iv_test"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </LinearLayout></layout>
布局文件ok了之后,我們緊接著實現(xiàn)我們的Activity,話說怎么調(diào)用呢?瞧好吧您吶~
3.Activity中調(diào)用
Build一下項目,之后按照如下方式調(diào)用:
- ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
- binding.idTest.setText("Hi DataBinding~");
- binding.idTest.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Toast.makeText(MainActivity.this, "響應(yīng)點擊事件~", Toast.LENGTH_SHORT).show();
- }
- });
關(guān)于以上內(nèi)容,我們還可以這樣寫,假設(shè),我們要顯示學(xué)生姓名,那么接下來進行三步走。
一步走:定義簡單實體類
- package cn.hlq.test;/**
- * Created by HLQ on 2017/6/26 0026.
- */public class Student {
- public Student(String stuName) {
- this.stuName = stuName;
- } private String stuName;
- public String getStuName() {
- return stuName;
- } public void setStuName(String stuName) {
- this.stuName = stuName;
- }
- }
二步走,修改layout文件:
- <?xml version="1.0" encoding="utf-8"?>
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <variable
- name="stu"
- type="cn.hlq.test.Student" />
- </data>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@{stu.stuName}"/>
- <ImageView
- android:id="@+id/iv_test"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </LinearLayout></layout>
在此為大家簡單說明下:
- < data >< /data >:綁定數(shù)據(jù)源 也就是你的實體類
- < variable >:基本參數(shù)配置
- name:別名 方便直接在控件中賦值
- type:指向?qū)嶓w類地址
三步走,Activity改怎么玩呢?
- ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
- binding.setStu(new Student("賀利權(quán)"));
瞧瞧運行結(jié)果,毛主席曾經(jīng)說過:實踐是檢驗真理的唯一標(biāo)準(zhǔn)
以上為大家簡單舉例了倆種實現(xiàn)方式。其他使用技能就需要大家自己get了。在此為大家提供官方api地址:
https://developer.android.google.cn/reference/android/databinding/package-summary.html
在此,為大家說明下以上Activity調(diào)用內(nèi)容。
Build后會為我們生成layout名稱+Binding的一個文件,我們主要操作就是通過他去實現(xiàn),我們一起相關(guān)調(diào)用類中是如何操作的。
首先要知道DataBinding是基于MVVM思想實現(xiàn)數(shù)據(jù)和UI綁定的框架,那么什么事MVVM,這里簡單了解下即可:
MVVM是Model-View-ViewModel的簡寫,也可以簡單理解為MVC升級,不過沒用過,不知道有什么優(yōu)勢。估摸也是什么解耦和,可拓展吧。
基于之前說過的DataBinding通過一次遍歷把所有的控件查找出來,然后賦值給對應(yīng)的變量,那我們一起去看看DataBindingUtil里面是如何操作的。
1.點進去我們可以清楚的看到它調(diào)用了一個泛型setContentView方法,簡單的翻譯了下。
- /**
- * 設(shè)置Activity的內(nèi)容視圖為給定的布局并返回相關(guān)的綁定。
- * 給定的布局資源不能包含合并的布局。
- *
- * @param activity Activity視圖應(yīng)該改變活動的內(nèi)容。
- * @param layoutId 布局的資源ID是引入,綁定,并設(shè)置為Activity的內(nèi)容。
- * @return 綁定與膨脹的內(nèi)容相關(guān)聯(lián)的視圖。
- */
- public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) { return setContentView(activity, layoutId, sDefaultComponent);
- }
2.繼續(xù)往下看,通過設(shè)置layout,獲取View實例以及查找要進行綁定改內(nèi)容,之后通過bindToAddedViews方法返回。
- /**
- * Set the Activity's content view to the given layout and return the associated binding.
- * The given layout resource must not be a merge layout.
- *
- * @param bindingComponent The DataBindingComponent to use in data binding.
- * @param activity The Activity whose content View should change.
- * @param layoutId The resource ID of the layout to be inflated, bound, and set as the
- * Activity's content.
- * @return The binding associated with the inflated content view.
- */
- public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
- DataBindingComponent bindingComponent) { // 設(shè)置ContentView
- activity.setContentView(layoutId); // 獲取View實例
- View decorView = activity.getWindow().getDecorView(); // 查找內(nèi)容
- ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content); // 返回綁定后添加的View
- return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
- }
3.最后我們會發(fā)現(xiàn)他通過校驗節(jié)點數(shù)量去綁定并添加View返回
- // 方法名鍵明其意 綁定并添加到View
- private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
- ViewGroup parent, int startChildren, int layoutId) { // 獲取到parent下子節(jié)點數(shù)量
- final int endChildren = parent.getChildCount(); // 校驗其下節(jié)點數(shù)量
- final int childrenAdded = endChildren - startChildren; if (childrenAdded == 1) { // 如果只有一個節(jié)點,獲取實例并添加綁定返回即可
- final View childView = parent.getChildAt(endChildren - 1); return bind(component, childView, layoutId);
- } else { // 如果不止一個節(jié)點,需要循環(huán)遍歷添加
- final View[] children = new View[childrenAdded]; for (int i = 0; i < childrenAdded; i++) {
- children[i] = parent.getChildAt(i + startChildren);
- } return bind(component, children, layoutId);
- }
- }
4.不知道大家有沒有注意到DataBindingUtil有一個靜態(tài)常量:
- private static DataBinderMapper sMapper = new DataBinderMapper();
看名字,猜測應(yīng)該是Map類型,具有key value,無可置疑,點擊去繼續(xù)瞅瞅。
- package android.databinding;import cn.hlq.test.BR;class DataBinderMapper { final static int TARGET_MIN_SDK = 24; public DataBinderMapper() {
- } public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId) { switch(layoutId) { case cn.hlq.test.R.layout.activity_main: return cn.hlq.test.databinding.ActivityMainBinding.bind(view, bindingComponent);
- } return null;
- }
- android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View[] views, int layoutId) { switch(layoutId) {
- } return null;
- } int getLayoutId(String tag) { if (tag == null) { return 0;
- } final int code = tag.hashCode(); switch(code) { case 423753077: { if(tag.equals("layout/activity_main_0")) { return cn.hlq.test.R.layout.activity_main;
- } break;
- }
- } return 0;
- } String convertBrIdToString(int id) { if (id < 0 || id >= InnerBrLookup.sKeys.length) { return null;
- } return InnerBrLookup.sKeys[id];
- } private static class InnerBrLookup { static String[] sKeys = new String[]{ "_all"};
- }
- }
5.亂七八糟一堆,但是這里面大家有沒有發(fā)現(xiàn)
- switch(layoutId) { case cn.hlq.test.R.layout.activity_main: return cn.hlq.test.databinding.ActivityMainBinding.bind(view, bindingComponent);
- }
多么熟悉的內(nèi)容,這個不就是我們的layout嗎!通過判斷是哪兒個layout,從而進行綁定相應(yīng)的源。
6.繼續(xù)點擊進去,我們會發(fā)現(xiàn),首先會去view去校驗是否添加了當(dāng)前l(fā)ayout的tag,如果添加則會直接return為我們生成的ActivityMainBinding類中。
- public static ActivityMainBinding bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) { if (!"layout/activity_main_0".equals(view.getTag())) { throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
- } return new ActivityMainBinding(bindingComponent, view);
- }
7.查看為我們生成ActivityMainBinding構(gòu)造方法
- public ActivityMainBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
- super(bindingComponent, root, 0); // 獲取return的綁定View實例
- final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds);
- // 這個就不必說了吧
- this.idTest = (android.widget.TextView) bindings[1];
- this.ivTest = (android.widget.ImageView) bindings[2];
- this.mboundView0 = (android.widget.LinearLayout) bindings[0];
- this.mboundView0.setTag(null);
- setRootTag(root); // 設(shè)置相關(guān)監(jiān)聽 也可理解請求相關(guān)監(jiān)聽
- invalidateAll();
- }
簡單的分析到這,不正之處歡迎大家指正~一起進步~
結(jié)束語
get 技能 運用技能 發(fā)現(xiàn)問題 解決問題 記錄問題
超神之路 挖坑不止 填坑不止
大家加油