股票场内基金交易,没时间盯盘?
这是一个关于 “Butter Knife” 的系列教程,一共由以下六章组成:
- View Bindings
- Resource Bindings
- Listener Bindings
- View Lists, Actions, Setters and Properties
- Better Workflow with Butter Knife Zelezny
- Nuts & Bolts (Upcoming)
引言
要做一个优秀的开发者不仅仅是写好代码那么简单,还要能够处理其它方面的难题。当你发现你自己在反复做着同样的事情或者写着大同小异的”模板”代码,是时候要停下来问问自己 —— “是不是有更好的处理方式?或者其他人是不是已经提供了更好的处理方式?”。这个系列讲的就是这样一种情况。
每个 Android 开发者在会写一个完整功能的 app 之前,都会学到一个叫做 “findViewById(int)” 的方法。这个方法简直是无处不在!它非常有用但有时又让人觉得有点太多了。来看看下面的代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class ProductActivity extends Activity { /********* 这些代码 ****************/ private ImageView productImageView; private TextView nameTextView; private TextView priceTextView; private TextView discountedPriceTextView; private TextView availabilityTextView; private TextView descriptionTextView; private RatingBar productRatingBar; private Button addToCardButton; private Button checkoutButton; /********* 这些代码 ****************/ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_product); /********* 这些代码 ****************/ productImageView = (ImageView) findViewById(R.id.productImageView); nameTextView = (TextView) findViewById(R.id.nameTextView); priceTextView = (TextView) findViewById(R.id.priceTextView); discountedPriceTextView = (TextView) findViewById(R.id.discountedPriceTextView); availabilityTextView = (TextView) findViewById(R.id.avaiabilityTextView); descriptionTextView = (TextView) findViewById(R.id.descriptionTextView); productRatingBar = (RatingBar) findViewById(R.id.productRatingBar); addToCardButton = (Button) findViewById(R.id.addToCardButton); checkoutButton = (Button) findViewById(R.id.checkoutButton); /********* 这些代码 ****************/ } } |
已经发现问题了吧?又敲又复制了这么多代码后,所完成的事却是这么琐碎。这些代码只是为了把我们 layout 文件中那些 view 控件的引用包含进来。我知道,这样的代码不会让人留下什么深刻印象。它足够让你觉得自己做了很多东西,但不足以引起你 boss (或者客户)的注意。
那么, Butter Knife 究竟是在什么地方发挥作用呢?下面就来看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class ProductActivity extends Activity { /********* 这些代码 ****************/ @BindView(R.id.productImageView) ImageView productImageView; @BindView(R.id.nameTextView) TextView nameTextView; @BindView(R.id.priceTextView) TextView priceTextView; @BindView(R.id.discountedPriceTextView) TextView discountedPriceTextView; @BindView(R.id.avaiabilityTextView) TextView availabilityTextView; @BindView(R.id.descriptionTextView) TextView descriptionTextView; @BindView(R.id.productRatingBar) RatingBar productRatingBar; @BindView(R.id.addToCardButton) Button addToCardButton; @BindView(R.id.checkoutButton) Button checkoutButton; /********* 这些代码 ****************/ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_product); ButterKnife.bind(this); //还有这句 } } |
首先明显看到的就是 ButterKnife.bind(this)这个方法取代了那些 findViewById(int) 方法。然而,效果却不会有什么变化,因为”Butter Knife”的程序已经为你生成了那些语句(稍后会进行说明)。
现在,来看看那些成员变量的声明:
1 2 3 4 |
private Button checkoutButton; // 使用 Butter Knife 之前 @BindView(R.id.checkoutButton) Button checkoutButton; // 使用 Butter Knife 之后 |
- 移除了 private 修饰符。”Butter Knife”不允许 private ,是因为这样做的话,成员变量对那些自动生成的代码(取代那些 findViewById(int) 的代码)会变得难以访问。同样它也不允许 static,因为 Jake Wharton 不会允许你在使用他的库时产生内存泄漏。
- @BindView 注解接收那些引用要绑定到成员变量的 view 控件的 ID 。
很整洁,对吧?
安装
你可以用 Gradle 添加”Butter Knife”到你的 Android 项目。写这篇文章时的最新版本是 8.0.1 ,你可以从这里获得最新的版本去引入。
首先,你必须像引入一个依赖项那样把 APT(Annotation Processing Tool) Gradle 插件包含进你项目的 /build.gradle 文件 ( top-level 那个) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.1.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //加这样的一句 // 注意 : 不要把你应用的那些依赖放到这里; //它们是属于单独的 module build.gradle 文件的。 } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } |
如果是有注解处理器在当前的 classpath , AndroidStudio 会自动提取,但其实这里多加一行作为备用也没什么关系,对吧?
然后,采用“Butter Knife”插件并把它的依赖包含到你的 module 构建脚本,也就是 /app/build.gradle 这个文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' //加这样的一句 android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "io.craftedcourses.butterknife" minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' compile 'com.jakewharton:butterknife:8.0.1' //加这样的一句 apt 'com.jakewharton:butterknife-compiler:8.0.1' //加这样的一句 } |
要理解最后那句 apt 做了什么,请看 这里 和 这里 的讨论。
最后,你需要 sync 同步你的项目去抓取那些依赖。在这之后,你就完成了在项目中使用“Butter Knife”的设置了。
用法
“Butter Knife”可用于下面的一些组件: 1. Activity 2. ListView adapter 和 RecyclerView 的 “View Holder” 3. Fragment 4. “自定义 View” 和 “view controllers”
Activity
这篇文章一开始就在 Activity 中示范了如何使用”Butter Knife”,又举一个例子就显得多余了。
View Holder – ListView Adapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public class RestaurantAdapter extends ArrayAdapter<Restaurant> { @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; Restaurant restaurant = getItem(position); ViewHolder holder; if (view != null) { holder = (ViewHolder) view.getTag(); } else { view = inflater.inflate(R.layout.list_item_restaurant, parent, false); holder = new ViewHolder(view); view.setTag(holder); } holder.nameTextView.setText(restaurant.name); holder.addressTextView.setText(restaurant.address); holder.ratingView.setRating(restaurant.rating); return view; } static class ViewHolder { /********* 这些代码 ****************/ @BindView(R.id.nameTextView) TextView nameTextView; @BindView(R.id.addressTextView) TextView addressTextView; @BindView(R.id.ratingView) RatingView ratingView; /********* 这些代码 ****************/ public ViewHolder(View view) { ButterKnife.bind(this, view); // 还有这句,注意这句了哦。 } } } |
最后一句是这个例子中最特别的。它的 bind(Object, View) 方法有两个参数: 1. Object 参数是包含了被注解成员变量的那个实例。在这个例子中,就是当前的 ViewHolder 对象,用 this 来引用它。 2. 第二个 View 参数是包含我们所要用到的那些控件的 view ,通常是一个 ViewGroup (在这个例子中是被填充进 restaurant 列表项的 layout)。
View Holder – RecyclerView
1 2 3 4 5 6 7 8 9 10 11 12 |
public class RestaurantHolder extends RecyclerView.ViewHolder { /********* 这些代码 ****************/ @BindView(R.id.nameTextView) TextView nameTextView; @BindView(R.id.addressTextView) TextView addressTextView; @BindView(R.id.ratingView) RatingView ratingView; /********* 这些代码 ****************/ public MyViewHolder(View view) { super(view); ButterKnife.bind(this, view); //还有这里 } } |
看,你已经知道窍门了吧。”自定义 View” 和 “view controllers” 也是一样的做法。 但是,Fragment的情况还要加个步骤。
Fragment
Fragment 的生命周期跟 Activity 有点不一样。因此,也就不奇怪要在 Fragment 的 onDestroyView() 生命周期方法中注销它那些 view 的引用了。接下来的代码段展示了通常是怎么做的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class NewsArticleFragment extends Fragment { private TextView titleTextView; private TextView summaryTextView; private TextView authorTextView; private TextView contentTextView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fancy_fragment, container, false); titleTextView = (TextView) findViewById(R.id.titleTextView); summaryTextView = (TextView) findViewById(R.id.summaryTextView); authorTextView = (TextView) findViewById(R.id.authorTextView); contentTextView = (TextView) findViewById(R.id.contentTextView); return view; } @Override public void onDestroyView() { super.onDestroyView(); /********* 这些代码 ****************/ titleTextView = null; summaryTextView = null; authorTextView = null; contentTextView = null; /********* 这些代码 ****************/ } } |
而用了”Butter Knife”是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class NewsArticleFragment extends Fragment { @BindView(R.id.titleTextView) TextView titleTextView; @BindView(R.id.summaryTextView) TextView summaryTextView; @BindView(R.id.authorTextView) TextView authorTextView; @BindView(R.id.contentTextView) TextView contentTextView; private Unbinder unbinder; // 注意这里 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_news_article, container, false); unbinder = ButterKnife.bind(this, view); // 注意这里 return view; } @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); // 注意这里 } } |
- 我们声明了一个由”Butter Knife”提供的,灵活的 Unbinder 变量。它是用来注销 view 引用的。
- bind(Object, View) 方法调用后会返回一个 Unbinder 实例,将它赋值给我们的 Unbinder 。避免把 Unbinder 定义为 static,这样你会不经意间引发 Activity 的内存泄漏。
- 然后 unbinder.unbind(); ,用 Unbinder 实例去注销那些 view 的引用。
但,如果我的 view 是缺失的呢?
有些情况下,会提示找不到具体的 view 。如果发生这种情况,应用就会崩溃。这种情况有可能是因为你通过代码或者具体配置的资源文件来操纵 layout ,以达到动态显示 layout 的效果。一个实际的例子就是,分别为手机和平板提供了不同的 layout 资源文件。下面是一个简单的堆栈跟踪信息例子:
原来的例子实在太长,我只截取了有用的这两段。
1 2 |
Caused by: java.lang.RuntimeException: Unable to bind views for io.craftedcourses.butterknife.MainActivity |
上面这段说绑定 view 出了问题,而如果你往下看下面这段,它已经给出了解决的方法。只要用 @Nullable 去注解那个出问题的成员变量就可以了。只要是 “Nullable” 这几个字母就有同样的效果,但还是推荐用 Android support-annotations 库所推荐的 @Nullable 。不需要担心这个库的包含问题,”Butter Knife”本身就是靠这个库进行传递的。
1 2 |
Caused by: java.lang.IllegalStateException: Required view 'textView' with ID 2131492944 for field 'textView' was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation. |
解决方法就是下面这样:
1 2 3 |
@Nullable @BindView(R.id.textView) TextView textView; |
性能
不需要担心”Butter Knife”会产生性能问题,它不是用”反射”去实现绑定的(只有很少部分用于对象实例化的反射)。”Butter Knife”的注解处理器会生成跟你自己手写一样的 findViewById(int) 语句。
总结
- “Butter Knife”消除了手写 findViewById(int) 的必要性。你可以在 Activity、Fragment、View Holder 和其他需要查找和绑定 view 的类中使用它。
- 使用 @BindView(int) 在你的类中注解 View 成员变量。
- ButterKnife.bind(Activity) 和 ButterKnife.bind(Object, View) 方法可以为你执行 view 绑定。其实还有其它的一些 bind 方法,你可以通过 IDE 的自动代码提示或者这个API文档自己了解。
- 用 Unbinder 在你的 Fragment 中注销 view 的引用。
- 有的情况某个 View 不需要加注解的,这时候用 @Nullable 去注解它。
下一章
下一篇文章,我们会探讨用”Butter Knife”进行 Resource bindings 。也请支持原文作者,可订阅他的推送。
最后的最后,大家可以看下> Butter Knife 的 CHANGELOG> ,有提到各种注解的变动,像 > @Bind> 取代了 > @InjectView> 和 > @InjectViews> 。我有些地方也是看了才知道这教程是什么意思 ,特别是 “Any annotation with that name will do” 这句。
想获得去掉 5 元限制的证券账户吗?

如果您想去掉最低交易佣金 5 元限制,使用微信扫描左边小程序二维码,访问微信小程序「优财助手」,点击底部菜单「福利」,阅读文章「通过优财开证券账户无最低交易佣金 5 元限制」,按照文章步骤操作即可获得免 5 元证券账户,股票基金交易手续费率万 2.5。
请注意,一定要按照文章描述严格操作,如错误开户是无法获得免 5 元证券账户的。