ButterKnife的源码解析(2)
2015-09-22 16:47
501 查看
上一篇文章讲解了ButterKnife在编译阶段通过注解生成java文件,今天需要讲解的是ButterKnife绑定上下文。
一般的,绑定上下文的操作十分简单,在activity中通常是这样的:
在Fragment中类似下面这样:
在adapter里的ViewHolder里面使用:
从上文就可以看出,一般Butterknife通过bind方法绑定上下文,下面我们来阅读源码:
bind方法:
在这个方法里面, 通过findViewBinderForClass(targetClass)获取与此activity相对应的我们通过注解在编译阶段生成java类的实例:
这里的核心代码就是
在这里找到Activity对应的生成java文件的class,然后生成它的实例,最后将该实例以class为键,实例为值放进hashMap里,找到该实例后,那么接下来就应该开始绑定了,因为我们生成的class都实现了ViewBinder接口,所以bind方法会跑到具体实现类的方法里面去:
Finder是个Enum类,在这里我们传的是Finder.ACTIVITY,首先调用 finder.findRequiredView方法:
在findOptionalView方法里面调用的findView(source,id)最终会走到这里:
这串代码我们就很熟悉了,就是我们经常使用的findViewById()方法,通过这几个方法ButterKnife终于将我们需要绑定的控件转换成了一个View(没有具体到是什么种类的控件,例如textView,edittext什么的),具体到种类的控件是在第二个方法finder.castView方法实现的,还是以上面的例子说明,target.textTv是TextView,那么castView的返回值就是TextView的具体实例,这个的实现是通过强转实现。
一般的控件绑定就是这样,在这里,还有一个情况就是点击事件的绑定,其实这个也很简单。首先我们一般这样定义点击事件:
那么编译时生成的相应代码就是:
DebouningOnClickListener实现了View.OnClickListener的接口,其实现很简单,源代码如下:
到这里,ButterKnife源码就告一段落了,第一次剖析开源框架的源代码,写的有点粗糙,如果有什么不足的话,欢迎批评指正。
一般的,绑定上下文的操作十分简单,在activity中通常是这样的:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); }
在Fragment中类似下面这样:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_word_detail_display, container, false); ButterKnife.bind(this, view); return view; }
在adapter里的ViewHolder里面使用:
static class ViewHolder { @Bind(R.id.item_icon) ImageView itemIcon; @Bind(R.id.note_label) TextView noteLabel; ViewHolder(View view) { ButterKnife.bind(this, view); } }
从上文就可以看出,一般Butterknife通过bind方法绑定上下文,下面我们来阅读源码:
public static void bind(Activity target) { bind(target, target, Finder.ACTIVITY); }
bind方法:
static void bind(Object target, Object source, Finder finder) { Class<?> targetClass = target.getClass(); try { if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName()); ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass); if (viewBinder != null) { viewBinder.bind(finder, target, source); } } catch (Exception e) { throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e); } }
在这个方法里面, 通过findViewBinderForClass(targetClass)获取与此activity相对应的我们通过注解在编译阶段生成java类的实例:
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) throws IllegalAccessException, InstantiationException { ViewBinder<Object> viewBinder = BINDERS.get(cls); if (viewBinder != null) { if (debug) Log.d(TAG, "HIT: Cached in view binder map."); return viewBinder; } String clsName = cls.getName(); if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(J***A_PREFIX)) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return NOP_VIEW_BINDER; } try { Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX); //noinspection unchecked viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance(); if (debug) Log.d(TAG, "HIT: Loaded view binder class."); } catch (ClassNotFoundException e) { if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); viewBinder = findViewBinderForClass(cls.getSuperclass()); } BINDERS.put(cls, viewBinder); return viewBinder; }
这里的核心代码就是
Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX); //noinspection unchecked viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
在这里找到Activity对应的生成java文件的class,然后生成它的实例,最后将该实例以class为键,实例为值放进hashMap里,找到该实例后,那么接下来就应该开始绑定了,因为我们生成的class都实现了ViewBinder接口,所以bind方法会跑到具体实现类的方法里面去:
@Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 2131492943, "field 'textTv'"); target.textTv = finder.castView(view, 2131492943, "field 'textTv'"); view = finder.findRequiredView(source, 2131492944, "field 'clickBtn'"); target.clickBtn = finder.castView(view, 2131492944, "field 'clickBtn'"); }
Finder是个Enum类,在这里我们传的是Finder.ACTIVITY,首先调用 finder.findRequiredView方法:
public <T> T findRequiredView(Object source, int id, String who) { T view = findOptionalView(source, id, who); if (view == null) { String name = getContext(source).getResources().getResourceEntryName(id); throw new IllegalStateException("Required view '" + name + "' with ID " + id + " for " + who + " was not found. If this view is optional add '@Nullable' annotation."); } return view; } public <T> T findOptionalView(Object source, int id, String who) { View view = findView(source, id); return castView(view, id, who); } @SuppressWarnings("unchecked") // That's the point. public <T> T castView(View view, int id, String who) { try { return (T) view; } catch (ClassCastException e) { if (who == null) { throw new AssertionError(); } String name = view.getResources().getResourceEntryName(id); throw new IllegalStateException("View '" + name + "' with ID " + id + " for " + who + " was of the wrong type. See cause for more info.", e); } }
在findOptionalView方法里面调用的findView(source,id)最终会走到这里:
ACTIVITY { @Override protected View findView(Object source, int id) { return ((Activity) source).findViewById(id); } @Override public Context getContext(Object source) { return (Activity) source; } }
这串代码我们就很熟悉了,就是我们经常使用的findViewById()方法,通过这几个方法ButterKnife终于将我们需要绑定的控件转换成了一个View(没有具体到是什么种类的控件,例如textView,edittext什么的),具体到种类的控件是在第二个方法finder.castView方法实现的,还是以上面的例子说明,target.textTv是TextView,那么castView的返回值就是TextView的具体实例,这个的实现是通过强转实现。
一般的控件绑定就是这样,在这里,还有一个情况就是点击事件的绑定,其实这个也很简单。首先我们一般这样定义点击事件:
@OnClick(R.id.text_tv) void textTvClick(){ Toast.makeText(this,"hello world",Toast.LENGTH_SHORT).show(); }
那么编译时生成的相应代码就是:
View view; view = finder.findRequiredView(source, 2131492943, "field 'textTv' and method 'textTvClick'"); target.textTv = finder.castView(view, 2131492943, "field 'textTv'"); view.setOnClickListener( new butterknife.internal.DebouncingOnClickListener() { @Override public void doClick( android.view.View p0 ) { target.textTvClick(); } });
DebouningOnClickListener实现了View.OnClickListener的接口,其实现很简单,源代码如下:
public abstract class DebouncingOnClickListener implements View.OnClickListener { private static boolean enabled = true; private static final Runnable ENABLE_AGAIN = new Runnable() { @Override public void run() { enabled = true; } }; @Override public final void onClick(View v) { if (enabled) { enabled = false; v.post(ENABLE_AGAIN); doClick(v); } } public abstract void doClick(View v); }
到这里,ButterKnife源码就告一段落了,第一次剖析开源框架的源代码,写的有点粗糙,如果有什么不足的话,欢迎批评指正。
相关文章推荐
- MyEclipse修改默认的Servlet和jsp代码内容
- 限制内容长度(CSS,jQuery)
- JS实现鼠标双击页面向下滚动,单击停止的特效
- [读书笔记-GPU GEMS C1] Effective Water Simulation from Physical Models
- NodeJS + PhantomJS 抓取页面信息以及截图
- Jquery控制文字横向移动
- 《学习日记》jquery/2015/9/22
- nodejs异步代码变量解决方案
- The entity name must immediately follow the '&' in the entity reference
- 什么是 Angularjs
- jQuery过滤性选择器
- wpf动态设置控件样式
- jQuery 基础选择器
- Json解析 Json数据文件 例子
- 并查集(种类并查集)POJ2492A Bug's Life解题报告
- (十九)由一个bug想到的:使用jQuery和angularJS需要注意的事
- jQuery 实现最简单的form表单提交 Loding 功能
- ewebeditor上传文件大小及在线编辑器jsp版使用心得
- 【原创教程】一、Angular教程系列之认识angular
- JSP标签实现分页功能(ORM-mybatis)