Android Study之findViewById变迁之路

前言

今天我们一块来聊聊项目常用的findViewById,这个东西可以简单理解为:初始化控件,实例化控件,方便进行其他操作。一般来说,我们通常这么写:


  1. private void initView() { 
  2.    TextView tvTest = (TextView) findViewById(R.id.id_test); 
  3. }  

上面的例子很简单,只是初始化一个TextView,但是在实际项目中,每个Activity,Fragment或者Adapter中有n个控件,每个控件都需要我们实例化控件,才能对其进行操作,一次次的findViewById,感觉好烦呐~!

有没有好办法呢?当然有很多种方式,但是我们要找适合自己项目的,下面将会为大家依次举例说明~

通过注解方式简化findViewById

在前几年,Xutils比较火爆,火爆的原因有很多,简单列举下,LZ更看好Xutils使用方便,至少为我们封装了很多常用的工具,就好比常用的恶心的图片处理,Xutils有很好的支持,同样,Xutils也支持注解方式去简化findViewById,简单举例如下:


  1. // xUtils的view注解要求必须提供id,以使代码混淆不受影响。@ViewInject(R.id.id_test) 
  2. TextView tvTest ;  

比较出名的ButterKnife,之前LZ也对此专门学习了下,相关文章地址如下:

一篇文章玩转ButterKnife,让代码更简洁

同理简单举例如下:


  1. @BindView(R.id.id_test)TextView tvTest; 

以上简单为大家列举俩种,至少是LZ用到过的,当让有关支持注解方式的好用的还有很多,欢迎大家交流,一起学习~

个人封装findViewById

刚刚在网上搜索,突然看到有一哥儿们经过其老师启发,个人封装了一个,LZ看到感觉不错,先试试看看好不好用。

简单修改之后,通过测试,感觉还不错,下面为大家附上源码:


  1. /** 
  2.  * Created by HLQ on 2017/6/25 0025. 
  3.  */ 
  4. public class FindView {     
  5.      private static Activity activity;    // 运用了单例模式中的饿汉式 
  6.     private static final FindView findView = new FindView();     
  7.        /** 
  8.      * 获取Activity实例 
  9.      * 
  10.      * @param activity 
  11.      */ 
  12.     private static void setActivity(Activity activity) { 
  13.         FindView.activity = activity; 
  14.     }    /** 
  15.      * 初始化FindView 
  16.      * 
  17.      * @param activitys 
  18.      * @return 
  19.      */ 
  20.     public static FindView with(Activity activitys) { 
  21.         setActivity(activitys);         
  22.            return findView; 
  23.     }    /** 
  24.      * 根据Id获取View实例 
  25.      * 
  26.      * @param id 
  27.      * @param <T> 
  28.      * @return 
  29.      */ 
  30.     public <T extends View> T getView(int id) { 
  31.         View view = activity.findViewById(id);         
  32.            return (T) view; 
  33.     }    /** 
  34.      * 设置TextView内容 
  35.      * 
  36.      * @param id 
  37.      * @param content 
  38.      * @return 
  39.      */ 
  40.     public FindView setTextContent(int id, String content) { 
  41.         TextView textView = getView(id); 
  42.         textView.setText(content);         
  43.            return this; 
  44.     }    /** 
  45.      * 设置ImageView 资源 
  46.      * 
  47.      * @param id 
  48.      * @param imgResource 
  49.      * @return 
  50.      */ 
  51.     public FindView setImageResource(int id, int imgResource) { 
  52.         ImageView iv = getView(id); 
  53.         iv.setImageResource(imgResource);         
  54.            return this; 
  55.     } 
  56.  
  57. }  

那么我们该如何使用呢?简单说下,调用有如下俩种方式:

  1. 通过链式调用,你可以直接调用封装好的setText or setImgResource进行直接赋值;
  2. 通过链式调用getView获取控件实例,然后进行相应操作即可。

还有一点,大家可自行根据项目进行拓展封装类。

下面为大家附上具体俩种方式调用以及运行结果:

方式一 调用方式:


  1. FindView.with(this).setTextContent(R.id.id_test, "Hello").setImageResource(R.id.iv_test,R.mipmap.ic_launcher); 

运行结果:

方式二 调用方式:


  1. TextView textView= FindView.with(this).getView(R.id.id_test); 
  2. textView.setText("你好");  

运行结果:

通过泛型来简化findViewById

一般来说我们会在BaseActivity中定义一个泛型获取View实例方法,如下:


  1. public final <E extends View> E getView(int id) {         
  2.     try {             
  3.         return (E) findViewById(id); 
  4.         } catch (ClassCastException ex) { 
  5.             Log.e("HLQ_Struggle", "Could not cast View to concrete class." + ex);             
  6.     throw ex; 
  7.         } 
  8.     }  

调用很简单,如下:


  1. TextView textView = getView(R.id.id_test); 
  2. textView.setText("你在干嘛");  

这个结果就不必说了吧?

抽取泛型方法为公共类

这里和上一个方法类型,但是上一个方法或有一些缺陷就是,我在非Activtiy中调用怎么办呢?难不成要在各个Base中都要Copy一次?个人感觉不是很如意。


  1. /** 
  2.      * 获取控件实例 
  3.      * @param view 
  4.      * @param viewId 
  5.      * @param <T> 
  6.      * @return View 
  7.      */ 
  8.     public static <T extends View> T viewById(View view, int viewId) {         
  9.         return (T) view.findViewById(viewId); 
  10.     }  

这样一来,调用就有点恶心了,如下:

在Activitiy中调用方式如下:


  1. TextView textView = UIHelper.viewById(selfActivity.getWindow().getDecorView(), R.id.id_test); 

Fragment中调用如下:


  1. TextView textView= UIHelper.viewById(getActivity().getWindow().getDecorView(), R.id.id_test); 

如果喜欢来来回回Copy的同志,这也不妨是一种办法。前方高能~

谷歌爸爸的DataBinding

话说这玩意LZ也是前几天才知道,之前从未关注过,不过简单了解后,发现确实很666,想必反射或者注解方式,性能上非常666;

下面引入目前来说比较6的说法:

DataBinding完全超越了findViewById!findViewById本质上是一次对view树的遍历查找,每次调用控件都会查找一次,虽然是O(n)的性能,但多次调用就变成了O(n)x m。但DataBinding则不然,通过一次遍历把所有的控件查找出来,然后赋值给对应的变量,完全不依赖findViewById,在任何情况下,复杂度都是O(n)。同样的是生成代码,但数据绑定框架提供了更多的功能,提高工作效率,编写更安全可靠的代码。

So,我们还有什么理由拒绝呢?

简单了解下吧,不然糊里糊涂的。

DataBinding简介

DataBinding,2015年IO大会介绍的一个框架,字面理解即为数据绑定。由于一般的开发过程中,Activity既需要做实现网络请求,又需要实现界面的渲染/用户之间的交互,如果一个页面的功能更为复杂,对后期的项目维护更加艰难。因此,推出该框架有利于简化功能模块,尽量将界面的渲染/用户交互的功能分化在单独的模块中。

感受DataBinding魅力

说啥也不如直接撸码来的实际,方便,快捷。下面通过一个简单的小例子,开启DataBinding Study。

1.build中配置开启DataBinding


  1. dataBinding { 
  2.       enabled = true 
  3.   }  

2.编写我们的布局文件,在这里大家要记住以下几点:

  • 布局文件根使用< layout >< /layout >包裹;
  • < layout >下只能有一个节点,这点和ScrollView一样

基于以上俩点,编写实现我们的布局文件:


  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"> 
  3.     <LinearLayout 
  4.         android:layout_width="match_parent" 
  5.         android:layout_height="match_parent" 
  6.         android:orientation="vertical"> 
  7.  
  8.         <TextView 
  9.             android:id="@+id/id_test" 
  10.             android:layout_width="wrap_content" 
  11.             android:layout_height="wrap_content"/> 
  12.  
  13.         <ImageView 
  14.             android:id="@+id/iv_test" 
  15.             android:layout_width="wrap_content" 
  16.             android:layout_height="wrap_content"/> 
  17.     </LinearLayout></layout>  

布局文件ok了之后,我们紧接着实现我们的Activity,话说怎么调用呢?瞧好吧您呐~

3.Activity中调用

Build一下项目,之后按照如下方式调用:


  1. ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); 
  2.         binding.idTest.setText("Hi DataBinding~"); 
  3.         binding.idTest.setOnClickListener(new View.OnClickListener() {             
  4.              @Override 
  5.             public void onClick(View view) { 
  6.                 Toast.makeText(MainActivity.this, "响应点击事件~", Toast.LENGTH_SHORT).show(); 
  7.             } 
  8.         });  

关于以上内容,我们还可以这样写,假设,我们要显示学生姓名,那么接下来进行三步走。

一步走:定义简单实体类


  1. package cn.hlq.test;/** 
  2.  * Created by HLQ on 2017/6/26 0026. 
  3.  */public class Student {     
  4.     public Student(String stuName) {         
  5.         this.stuName = stuName; 
  6.     }    private String stuName;     
  7.             public String getStuName() {         
  8.                 return stuName; 
  9.     }    public void setStuName(String stuName) {         
  10.             this.stuName = stuName; 
  11.     } 
  12. }  

二步走,修改layout文件:


  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"> 
  3.     <data> 
  4.         <variable 
  5.             name="stu" 
  6.             type="cn.hlq.test.Student" /> 
  7.     </data> 
  8.     <LinearLayout 
  9.         android:layout_width="match_parent" 
  10.         android:layout_height="match_parent" 
  11.         android:orientation="vertical"> 
  12.  
  13.         <TextView 
  14.             android:layout_width="wrap_content" 
  15.             android:layout_height="wrap_content" 
  16.             android:text="@{stu.stuName}"/> 
  17.  
  18.         <ImageView 
  19.             android:id="@+id/iv_test" 
  20.             android:layout_width="wrap_content" 
  21.             android:layout_height="wrap_content"/> 
  22.     </LinearLayout></layout>  

在此为大家简单说明下:

  • < data >< /data >:绑定数据源 也就是你的实体类
  • < variable >:基本参数配置
    • name:别名 方便直接在控件中赋值
    • type:指向实体类地址

三步走,Activity改怎么玩呢?


  1. ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); 
  2. binding.setStu(new Student("贺利权"));  

瞧瞧运行结果,毛主席曾经说过:实践是检验真理的唯一标准

以上为大家简单举例了俩种实现方式。其他使用技能就需要大家自己get了。在此为大家提供官方api地址:

https://developer.android.google.cn/reference/android/databinding/package-summary.html

在此,为大家说明下以上Activity调用内容。

Build后会为我们生成layout名称+Binding的一个文件,我们主要操作就是通过他去实现,我们一起相关调用类中是如何操作的。

首先要知道DataBinding是基于MVVM思想实现数据和UI绑定的框架,那么什么事MVVM,这里简单了解下即可:

MVVM是Model-View-ViewModel的简写,也可以简单理解为MVC升级,不过没用过,不知道有什么优势。估摸也是什么解耦和,可拓展吧。

基于之前说过的DataBinding通过一次遍历把所有的控件查找出来,然后赋值给对应的变量,那我们一起去看看DataBindingUtil里面是如何操作的。

1.点进去我们可以清楚的看到它调用了一个泛型setContentView方法,简单的翻译了下。


  1. /** 
  2.      * 设置Activity的内容视图为给定的布局并返回相关的绑定。 
  3.      * 给定的布局资源不能包含合并的布局。 
  4.      * 
  5.      * @param activity Activity视图应该改变活动的内容。 
  6.      * @param layoutId 布局的资源ID是引入,绑定,并设置为Activity的内容。 
  7.  
  8.      * @return 绑定与膨胀的内容相关联的视图。 
  9.      */ 
  10.     public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) {        return setContentView(activity, layoutId, sDefaultComponent); 
  11.     } 

2.继续往下看,通过设置layout,获取View实例以及查找要进行绑定改内容,之后通过bindToAddedViews方法返回。


  1. /** 
  2.      * Set the Activity's content view to the given layout and return the associated binding. 
  3.      * The given layout resource must not be a merge layout. 
  4.      * 
  5.      * @param bindingComponent The DataBindingComponent to use in data binding. 
  6.      * @param activity The Activity whose content View should change. 
  7.      * @param layoutId The resource ID of the layout to be inflated, bound, and set as the 
  8.      *                 Activity's content. 
  9.      * @return The binding associated with the inflated content view. 
  10.      */ 
  11.     public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId, 
  12.             DataBindingComponent bindingComponent) {        // 设置ContentView 
  13.         activity.setContentView(layoutId);        // 获取View实例 
  14.         View decorView = activity.getWindow().getDecorView();        // 查找内容 
  15.         ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);        // 返回绑定后添加的View 
  16.         return bindToAddedViews(bindingComponent, contentView, 0, layoutId); 
  17.     }  

3.最后我们会发现他通过校验节点数量去绑定并添加View返回


  1. // 方法名键明其意 绑定并添加到View 
  2.     private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component, 
  3.             ViewGroup parent, int startChildren, int layoutId) {        // 获取到parent下子节点数量 
  4.         final int endChildren = parent.getChildCount();        // 校验其下节点数量 
  5.         final int childrenAdded = endChildren - startChildren;        if (childrenAdded == 1) { // 如果只有一个节点,获取实例并添加绑定返回即可 
  6.             final View childView = parent.getChildAt(endChildren - 1);            return bind(component, childView, layoutId); 
  7.         } else { // 如果不止一个节点,需要循环遍历添加 
  8.             final View[] children = new View[childrenAdded];            for (int i = 0; i < childrenAdded; i++) { 
  9.                 children[i] = parent.getChildAt(i + startChildren); 
  10.             }            return bind(component, children, layoutId); 
  11.         } 
  12.     }  

4.不知道大家有没有注意到DataBindingUtil有一个静态常量:


  1. private static DataBinderMapper sMapper = new DataBinderMapper(); 

看名字,猜测应该是Map类型,具有key value,无可置疑,点击去继续瞅瞅。


  1. package android.databinding;import cn.hlq.test.BR;class DataBinderMapper  {    final static int TARGET_MIN_SDK = 24;    public DataBinderMapper() { 
  2.     }    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); 
  3.         }        return null; 
  4.     } 
  5.     android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View[] views, int layoutId) {        switch(layoutId) { 
  6.         }        return null; 
  7.     }    int getLayoutId(String tag) {        if (tag == null) {            return 0; 
  8.         }        final int code = tag.hashCode();        switch(code) {            case 423753077: {                if(tag.equals("layout/activity_main_0")) {                    return cn.hlq.test.R.layout.activity_main; 
  9.                 }                break; 
  10.             } 
  11.         }        return 0; 
  12.     }    String convertBrIdToString(int id) {        if (id < 0 || id >= InnerBrLookup.sKeys.length) {            return null; 
  13.         }        return InnerBrLookup.sKeys[id]; 
  14.     }    private static class InnerBrLookup {        static String[] sKeys = new String[]{            "_all"}; 
  15.     } 

5.乱七八糟一堆,但是这里面大家有没有发现


  1. switch(layoutId) { case cn.hlq.test.R.layout.activity_main: return cn.hlq.test.databinding.ActivityMainBinding.bind(view, bindingComponent); 
  2.  
  3. }  

多么熟悉的内容,这个不就是我们的layout吗!通过判断是哪儿个layout,从而进行绑定相应的源。

6.继续点击进去,我们会发现,首先会去view去校验是否添加了当前layout的tag,如果添加则会直接return为我们生成的ActivityMainBinding类中。


  1. 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()); 
  2.        }        return new ActivityMainBinding(bindingComponent, view); 
  3.    } 

7.查看为我们生成ActivityMainBinding构造方法


  1. public ActivityMainBinding(android.databinding.DataBindingComponent bindingComponent, View root) {         
  2.           super(bindingComponent, root, 0);        // 获取return的绑定View实例 
  3.         final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds);         
  4.               // 这个就不必说了吧 
  5.             this.idTest = (android.widget.TextView) bindings[1];         
  6.               this.ivTest = (android.widget.ImageView) bindings[2];         
  7.               this.mboundView0 = (android.widget.LinearLayout) bindings[0];         
  8.               this.mboundView0.setTag(null); 
  9.         setRootTag(root);        // 设置相关监听 也可理解请求相关监听 
  10.         invalidateAll(); 
  11.     }  

简单的分析到这,不正之处欢迎大家指正~一起进步~

结束语

get 技能 运用技能 发现问题 解决问题 记录问题

超神之路 挖坑不止 填坑不止

大家加油

作者:静心Study

来源:51CTO

时间: 2024-07-31 00:53:06

Android Study之findViewById变迁之路的相关文章

Android Vector曲折的兼容之路

Android Vector曲折的兼容之路 两年前写书的时候,就在研究Android L提出的Vector,可研究下来发现,完全不具备兼容性,相信这也是它没有被广泛使用的一个原因,经过Google的不懈努力,现在Vector终于迎来了它的春天. 在文章后面,会给出本文的Demo和效果图,并开源在Github Vector Drawable Android 5.0发布的时候,Google提供了Vector的支持.Vector Drawable相对于普通的Drawable来说,有以下几个好处: Ve

Android开发中findViewById()函数用法与简化

Android中FindViewById()是一个非常常用的函数,位于android.app.Activity包中.该函数利用我们在XML文件中定义的View的id属性来获取相应的View对象.findViewById()属于API Level 1, 对应的android版本是android1.0, 由此,可以看出,该函数是android早期版本中就有的.顺便说一下, android目前市场上已商用的版本及其对应的API Level如下: android 1.0API Level 1 andro

Android高效率编码-findViewById()的蜕变-注解,泛型,反射

Android高效率编码-findViewById()的蜕变-注解,泛型,反射 Android的老朋友findViewById()篇! 先看看他每天是在干什么 //好吧,很多重复的,只不过想表达项目里确实有很多控件 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerList = (ListView) findViewById(R.id.laft_drawer); mDrawerLayout = (Dra

Android Study之跳转自启动管理页

前言 话说,最近项目要求软件自启动,脑子一想,静态注册广播,监听用户开机不就得了么.按照思路编写好代码,却发现怎么都监听不到这个开机权限.LZ很是郁闷.经过几天的咨询度娘和各种脑洞大开的测试后,发现有个东东叫做自启动管理,经过简单测试后发现,用户如果给定软件自启动权限后,我们只需要静态注册开机广播就可以监听到用户开机,并可以针对这一情况做相关操作. 问题延伸 但是问题又来了,那么我们怎么知道用户是否给定软件自启动的权限呢?找了好久,没找到可以解决的办法,倒是有以下几个办法可以曲线救国,让我们一块

android studio-萌新的安卓之路,跪求老司机降临

问题描述 萌新的安卓之路,跪求老司机降临 萌新刚踏上安卓之路,但是这个世界好陌生,故此番前来向老司机请教,请收下我的膝盖.萌新就想问一下,我现在对安卓开发什么都不会,并且想使用Android studio作为集成开发环境,我该怎么办,去哪找学习资料来搭建好我的第一个hellow world,以及指导后面的进阶之路,跪求,我现在无从下手 恩,我学过java,想在Windows上用Android studio开发,我同学给过我一些学习资料,诸如 Android15天之类的视频,但是那个是基于ecli

视频网站数据平台变迁之路(一)

一.数据系统架构V1 优酷早在2007年便采用php语言自主开发了一套数据系统.系统分为数据采集.数据存储.数据分析.报表平台,四个模块.整体架构如下: 这套架构至今在一些需要自己搭建数据平台的小公司而言也是足够的,在没有海量数据之前可以不使用Hadoop之类的开源框架,WebServer日志和一些自定义的日志已经足够日常数据分析的需求了,通过Linux上的一些命令已经可以分析很多数据指标了. 1.数据采集与数据存储 根据用户行为不同,数据采集上也有多种方式,最初在移动端没兴起的时候,数据的采集

Android通讯录联系人的读取、更新、插入、删除等方法

TestContacts.java: package waga.android.study.TestContacts; import android.app.Activity; import android.content.ContentResolver; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.provider.Contacts

ym——android源码大放送(实战开发必备)

文件夹 PATH 列表 卷序列号为 000A-8F50 E:. │  javaapk.com文件列表生成工具.bat │  使用说明.txt │  免费下载更多源码.url │  目录列表.txt │   ├─android web应用 │      jqmDemo_static.zip │      jqmMobileDemo-master.zip │      jqmMobileDemo1_1-master.zip │      Location1014.rar │ ├─anko │    

Android Moveview滑屏移动视图类完整实例_Android

本文示例所述程序为一个Android Moveview移动视图类.其主要实现主屏左右滑屏拖动功能,并适时显示拖动时候屏幕的显示情况,该代码中还包括完整的逻辑.其完整代码如下: import android.study.shift.ItemView; import android.study.shift.MainView; import android.study.shift.Moveview; import android.content.Context; import android.os.H