用kotlin已经写过不少东西了,kotlin带给开发效率的提升真的是来自方方面面,尤其是再也不用写findViewById这类代码了,也不用再类里面写很多控件的成员变量了,因为有kotlin_android_extensions插件帮我们做了找控件的操作,我们只需要引用xml文件里面控件的id,就能引用这个控件做我们想做的事了。在activity里面使用的时候没什么问题,可是在fragment里面使用的时候却出了问题,我们来看一下在fragment里面的代码
class DemoFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_demo, container)
tv_1.text = "demo fragment"
return view
}
}
复制代码
报错信息:Caused by: java.lang.IllegalStateException: tv_1 must not be null,tv_1是空的,证明没有找到tv_1这个控件,但为什么activity就可以啊,到了fragment却不行了,带着问题我们来探究一下kotlin_android_extensions插件是怎么实现找控件的操作的?我们还是先来看看activity吧
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pv1.setOnClickListener { }
}
}
复制代码
还是直接使用xml文件里面的id(pv1)来设置点击事件,那就让我们看看为什么直接用id就可以直接调用控件的方法,我们先show kotlin bytecode然后在decomple,看一下编译之后的代码
private HashMap _$_findViewCache;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
((PracticeView1)this._$_findCachedViewById(id.pv1)).setOnClickListener((OnClickListener)null.INSTANCE);
}
public View _$_findCachedViewById(int var1) {
if(this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
if(var2 == null) {
var2 = this.findViewById(var1);
this._$_findViewCache.put(Integer.valueOf(var1), var2);
}
return var2;
}
复制代码
可以看到pv1,被编译后其实是调用了_$_findCachedViewById这个方法,其实就是先从cache里面找view,没有的话就调用findViewById找到控件,然后缓存起来,就是这样,那我们看看fragment为什么就会报错,还是通过之前的方法来查看编译后的java代码
public final class DemoFragment extends Fragment {
private HashMap _$_findViewCache;
@Nullable
public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Intrinsics.checkParameterIsNotNull(inflater, "inflater");
View view = inflater.inflate(2131296284, container);
TextView var10000 = (TextView)this._$_findCachedViewById(id.tv_1);
Intrinsics.checkExpressionValueIsNotNull(var10000, "tv_1");
var10000.setText((CharSequence)"demo fragment");
return view;
}
public View _$_findCachedViewById(int var1) {
if(this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
if(var2 == null) {
View var10000 = this.getView();
if(var10000 == null) {
return null;
}
var2 = var10000.findViewById(var1);
this._$_findViewCache.put(Integer.valueOf(var1), var2);
}
return var2;
}
public void _$_clearFindViewByIdCache() {
if(this._$_findViewCache != null) {
this._$_findViewCache.clear();
}
}
// $FF: synthetic method
public void onDestroyView() {
super.onDestroyView();
this._$_clearFindViewByIdCache();
}
}
复制代码
kotlin代码tv_1.text = "demo fragment"这一句被编译后被翻译成
TextView var10000 = (TextView)this._$_findCachedViewById(id.tv_1);
Intrinsics.checkExpressionValueIsNotNull(var10000, "tv_1");
var10000.setText((CharSequence)"demo fragment");
复制代码
第一句,还是通过_$findCachedViewById来找控件,第二句验证找到的控件非空,第三句才是设置text,那第二句验证非空出问题了,那么就是找控件的时候有问题了,看一下$_findCachedViewById,从cache里面找没有的话,再从getview里面找,但是getview为空时直接返回了null,那我们继续看看getview
/**
* Get the root view for the fragment's layout (the one returned by {@link #onCreateView}),
* if provided.
*
* @return The fragment's root view, or null if it has no layout.
*/
@Nullable
public View getView() {
return mView;
}
复制代码
这个返回的mView是onCreateView返回的,所以在onCreateView返回之前mView都是null,真相一目了然了,所以我们在fragment里面使用的时候需要等fragment的view创建之后,这样就能避免报错
kotlin_android_extensions插件其他拓展功能
kotlin_android_extensions插件还可以在自定义view、viewholder里面使用,但是这些功能并不是最终的版本,所以你需要将它们添加到build.gradle中来启动它们:
androidExtensions {
experimental = true
}
复制代码
@Parcelize注释,你可以以非常简单的方式使任何类都能实现Parcelable
@Parcelize
class Model(val title: String, val amount: Int) : Parcelable
复制代码
在这组实验中包含的新功能是一个新注释@ContainerOptions。这允许你以自定义方式构建缓存,甚至阻止类创建它。 默认情况下,它将用Hashmap,如我们之前看到的那样。但是,这可以用Android框架的SparseArray来改变,这在某些情况下可能会更有效率。如果由于某种原因,你不需要一个类的缓存,你也可以使用该选项。
@ContainerOptions(CacheImplementation.SPARSE_ARRAY)
class MainActivity : AppCompatActivity() {}
复制代码
目前,已有的选择是:
public enum class CacheImplementation {
/** Use [android.util.SparseArray] as a backing store for the resolved views. */
SPARSE_ARRAY,
/** Use [HashMap] as a backing store for the resolved views (default). */
HASH_MAP,
/** Do not cache views for this layout. */
NO_CACHE;
companion object {
/** The default cache implementation is [HASH_MAP]. */
val DEFAULT = HASH_MAP
}
}
复制代码
恩,这么多好用的功能,赶紧用起来吧!!!