一个完全摆脱findViewById的自动绑定库
问题
先来看一个正常的写法:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="TEXT_VIEW"
android:textSize="22sp"/>
<Button
android:id="@+id/btn_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BUTTON"/>
</LinearLayout>
public class StartActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tv_test;
private Button btn_test;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start);
// init view
tv_test = findViewById(R.id.tv_test);
btn_test = findViewById(R.id.btn_test);
// init listener
btn_test.setOnClickListener(this);
tv_test.setText("Text changed!");
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_test:
Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
break;
}
}
}
以上的那些变量的定义、findViewById、setOnClickListener等等,写起来重复繁琐,我们下面就是要解决这些问题。
流行的解决方法
一、DataBinding
- 没有解决
setOnClickListener的问题,解决了也是很繁琐,需要设置各种Handler
public class StartActivity extends AppCompatActivity implements View.OnClickListener {
private ActivityStartBinding mBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_start);
mBinding.tvTest.setText("Text changed!");
mBinding.btnTest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_test:
Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
break;
}
}
}
二、ButterKnife
- 还是要定义各种变量,当界面比较复杂的时候,代码看起来就没那么好看了
- 在子module中使用会非常繁琐
public class StartActivity extends AppCompatActivity {
@BindView(R.id.tv_test) TextView tv_test;
@BindView(R.id.btn_test) Button btn_test;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start);
ButterKnife.bind(this);
tv_test.setText("Text changed!");
}
@OnClick(R.id.btn_test)
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_test:
Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
break;
}
}
}
三、直接使用 android:onClick="onClick"
- 在
Fragment中设置此属性无效 - 支持库中的组件如
AppCompatButton等等,设置此属性无效
还有很多其它解决方法,这里就不一一列举了。
我的解决方法
- 凡是设置了id的view,直接使用即可,变量名为id名
- 凡是设置了
android:onClick="onClick",在Activity中实现onClick方法即可
@BindActivity(value = R.layout.activity_main, layoutFilename = "activity_main", extendsClass = "android.support.v7.app.AppCompatActivity")
public class MainActivity extends MainActivity_ {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv_test.setText("Text changed!");
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_test:
Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
break;
}
}
}
实现过程
原理是配置一些注解,在编译期间生成相应文件,那些繁琐的代码就放在相应文件中了。
- 定义注解
传入三个参数:布局文件ID、布局文件名、需要继承的类
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface BindActivity {
/**
* layoutResID
*/
int value();
String layoutFilename();
String extendsClass();
}
- 继承
AbstractProcessor类,在编译期间会回调到此类的process方法,在此方法中获取注解,生成相应文件
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Project project = Project.init();
Set<? extends Element> bindActivityElements = roundEnvironment.getElementsAnnotatedWith(BindActivity.class);
for (Element element : bindActivityElements) {
BindActivityHandler handler = new BindActivityHandler(mFiler, mElementUtils);
handler.bind(project, element);
}
Set<? extends Element> bindFragmentElements = roundEnvironment.getElementsAnnotatedWith(BindFragment.class);
for (Element element : bindFragmentElements) {
BindFragmentHandler handler = new BindFragmentHandler(mFiler, mElementUtils);
handler.bind(project, element);
}
return false;
}
读取
settings.gradle文件,获取所有module,在遍历module下面的所有布局文件解释布局文件,获取所有设置了id和设置了onclick的view
private void parserElement(Element element) {
IdView view = null;
for (Iterator<Attribute> it = element.attributeIterator(); it.hasNext(); ) {
Attribute attribute = it.next();
switch (attribute.getQualifiedName()) {
case "android:id":
view = new IdView();
view.setType(element.getName());
view.name = attribute.getValue().substring(5);
idViews.add(view);
break;
case "android:onClick":
// must set android:onClick="onClick"
if (view != null && "onClick".equalsIgnoreCase(attribute.getValue())) {
view.hasOnClick = true;
}
break;
}
}
if (view != null) System.out.println(view.toString());
}
- 根据注解找到对应的布局文件,生成对应的类文件
后会生成一个文件名类似MainActivity_的文件,和MainActivity在同一个目录,大致为build/generated/source/apt/debug/包名
@Override
public void bind(Project project, Element element) {
String _package = getPackageName(element);
String _class = getClassName(element);
BindActivity bindActivity = element.getAnnotation(BindActivity.class);
int layoutResID = bindActivity.value();
String layoutFilename = bindActivity.layoutFilename();
String extendsClass = bindActivity.extendsClass();
Layout layout = project.getLayout(layoutFilename);
if (layout != null) {
try {
JavaFileObject jfo = mFiler.createSourceFile(_package + "." + _class, new Element[]{});
Writer writer = jfo.openWriter();
StringBuilderHelper sb = new StringBuilderHelper();
sb.line("package " + _package + ";");
sb.line();
sb.line("import android.os.Bundle;");
sb.line("import android.support.annotation.Nullable;");
sb.line("import android.view.View;");
sb.line();
sb.line("// Generated code, dot not edit!!!");
sb.line();
if (layout.hasOnClick()) {
sb.line("public abstract class " + _class + " extends " + extendsClass + " implements View.OnClickListener {");
} else {
sb.line("public abstract class " + _class + " extends " + extendsClass + " {");
}
sb.line();
sb.line____("protected final int layoutResID = " + layoutResID + ";");
sb.line();
sb.line____("protected View rootView;");
for (IdView idView : layout.idViews) {
sb.line____("protected " + idView.getType() + " " + idView.name + ";");
}
sb.line();
sb.line____("@Override");
sb.line____("protected void onCreate(@Nullable Bundle savedInstanceState) {");
sb.line________("super.onCreate(savedInstanceState);");
sb.line________("setContentView(layoutResID);");
sb.line();
for (IdView idView : layout.idViews) {
sb.line________(idView.name + " = findViewById(R.id." + idView.name + ");");
}
sb.line();
initOnClick(layout, sb);
sb.line____("}");
sb.line("}");
writer.write(sb.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用方法
比如类为
MainActivity,继承AppCompatActivity,布局文件为R.layout.activity_main
- 设置类注解
@BindActivity(value = R.layout.activity_main,
layoutFilename = "activity_main",
extendsClass = "android.support.v7.app.AppCompatActivity")
- 布局文件设置id属性和onClick属性
<Button
android:id="@+id/btn_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="BUTTON"/>
MakeProject- 修改
MainActivity继承MainActivity_即可
文件结构图

演示效果图

运行代码可能出现的问题
compileSdkVersion 27,可以改成你电脑中存在的SDK版本。
这里用的是 gradle-4.4-all.zip,如果你用的是其它版本,那么可能会下载超级慢,建议改成你电脑中存在的gradle版本,改文件PermissionHelper/gradle/wrapper/gradle-wrapper.properties即可。
其它问题可以直接联系我。一个完全摆脱findViewById的自动绑定库
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
一个完全摆脱findViewById的自动绑定库的更多相关文章
- 一个可以将 json 字符串 直接绑定到 view 上的Android库
android-data-binding 这是一个可以将 json 字符串 直接绑定到 view 上的库, 不用先将 json 转换为 model 类. 传送门(https://github.com/ ...
- At.js – 用于 Web 应用程序的自动完成库
At.js 是一个自动完成库,用来实现自动完成提示,表情等,就像你在 Github 或 Twitter 上看到的一样.它支持 HTML5 contentEditable 元素.你可以听任何字符,而不仅 ...
- XamarinAndroid 自动绑定View变量
Android 编程时我们少不了使用FindIdByView函数,在Xamarin Android开发时也需要如此.这个工作很无聊且烦人.在常规Android开发中,人们已经发明了一些方法免除这项工作 ...
- [开源] gnet: 一个轻量级且高性能的 Golang 网络库
Github 主页 https://github.com/panjf2000/gnet 欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦. 简介 gnet 是一个基于 Ev ...
- Kelp.Net是一个用c#编写的深度学习库
Kelp.Net是一个用c#编写的深度学习库 基于C#的机器学习--c# .NET中直观的深度学习 在本章中,将会学到: l 如何使用Kelp.Net来执行自己的测试 l 如何编写测试 l ...
- FlexiGrid 使用 全选、自动绑定
1.介绍 Flexigrid是一个类似于Ext Gird,但基于jQuery开发的Grid.它具有的功能包括:可以调整列宽,合并列标题,分页,排序,显示/隐藏表格等.Flexigrid显示的数据能够通 ...
- php 自动绑定di容器实现
<?php class Bim { public function doSth() { echo __METHOD__.PHP_EOL; } } class Bar { protected $b ...
- Xamarin.Android绑定库分享
使用Xamarin.Android时,会用到各种第三方库,而这些库基本上是java编写的,要在Xamarin.Android中使用这些库,就需要通过Android Binding Project绑定对 ...
- paip.提升效率---request自动绑定domain object
paip.提升效率---request自动绑定domain object #.keyword,subtitle关键字,子标题 ------------------------- 复制request属性 ...
随机推荐
- 【位运算】【BFS】移动玩具
1054: [HAOI2008]移动玩具 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 2246 Solved: 1246[Submit][Stat ...
- BZOJ 4939 [Ynoi2016]掉进兔子洞(莫队+bitset)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=4939 [题目大意] 给出一个数列,每个询问给出三个区间,问除去三个区间共有的数字外, ...
- 【线段树】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem J. Jedi Training
题意:给你一个序列,支持两种操作:单点修改:询问一个区间中所有相邻位置下标奇偶性均不同的子序列中,和最大的是多少. 线段树每个结点维护四个值: 以奇数下标开始到奇数下标结束的最大子序列和: 以偶数下标 ...
- 给lnmp一键包中的nginx安装openresty的lua扩展
lnmp一键包(https://lnmp.org)本人在使用之后发现确实好用,能帮助我们快速搭建起lnmp.lamp和lnmpa的web生产环境,因此推荐大家可以多试试.但有的朋友可能需要使用open ...
- (原创)Stanford Machine Learning (by Andrew NG) --- (week 3) Logistic Regression & Regularization
coursera上面Andrew NG的Machine learning课程地址为:https://www.coursera.org/course/ml 我曾经使用Logistic Regressio ...
- boost_1.63.0编译VS2013
编译 boost 库的步骤: 从官网下载最新版本的 boost 库(目前最新版本为:boost_1_63_0):下载地址:http://www.boost.org/users/download/ 解压 ...
- Java中的文件操作(一)RandomAccessFile
今天,学到的是java中的文件操作. Java.IO.File Java中操作文件用到RandomAccessFile类,既可以读取文件内容,也可以向文件输出数据,但不同与普通输入/输出流的是Rand ...
- memcache注意点
Memcached存储单个item最大数据是在1MB内,如果数据超过1M,存取set和get是都是返回false,而且引起性能的问题. 我们之前对排行榜的数据进行缓存,由于排行榜在我们所有sql se ...
- STL迭代器及迭代器失效问题
迭代器失效: 典型的迭代器失效. 首先对于vector而言,添加和删除操作可能使容器的部分或者全部迭代器失效.那为什么迭代器会失效呢?vector元素在内存中是顺序存储,试想:如果当前容器中已经存在了 ...
- Windows下生成 公钥 私钥以及 配置 Filezilla中的 SFTP的私钥
Win下需要使用到 PuTTYgen.exe来生成公钥私钥,可以参考youtube的这篇文章: 为 SFTP 保管箱生成 Secure Shell (SSH) 密钥对 PuTTYgen.exe的下载 ...