用kotlin打造简化版本的ButterKnife

简介: 用kotlin打造简化版本的ButterKnife

大名鼎鼎的 ButterKnife 库相信很多 android 开发者都听过,在 Github 上star的数目已经快15k了,而且很多知名的app都在使用。


ButterKnife 可以简化像 findViewById、setOnClickListener 这种代码,让开发者摆脱一些繁琐的细节,更加关注于业务代码的开发。


既然 ButterKnife 已经足够强大了,为何还要再造一个轮子呢?你说好代码相见恨晚,我说造轮子你不够勇敢。_ 其实这个库更加轻量级只做了几个最常用的注解,并且它是完全基于Kotlin进行开发的。


下载安装:



在根目录下的build.gradle中添加

buildscript {
     repositories {
         jcenter()
     }
     dependencies {
         classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
     }
 }


在app 模块目录下的build.gradle中添加

apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
    compile 'com.safframework.injectview:saf-injectview:1.0.0'
    apt 'com.safframework.injectview:saf-injectview-compiler:1.0.0'
    ...
}


demo的演示


image.png


demo和库的地址:


https://github.com/fengzhizi715/SAF-Kotlin-InjectView


整个库的设计:



整个库包括三个模块:


  • injectview:android library,包括Injector类和ViewBinder接口。用于在Activity、Fragment、View、Dialog中进入注入。
  • injectview-annotations:java library,用于存放各个注解,比如@InjectView。
  • injectview-compiler:java library,使用apt来生成代码image.pngimage.png


  1. injectview module

import android.app.Activity
import android.app.Dialog
import android.support.v4.app.Fragment
import android.view.View
import java.lang.reflect.Field
/**
 * Created by Tony Shen on 2017/1/24.
 */
object Injector {
    enum class Finder {
        DIALOG {
            override fun findById(source: Any, id: Int): View {
                return (source as Dialog).findViewById(id)
            }
        },
        ACTIVITY {
            override fun findById(source: Any, id: Int): View {
                return (source as Activity).findViewById(id)
            }
            override fun getExtra(source: Any, key: String, fieldName: String): Any? {
                val intent = (source as Activity).intent
                if (intent != null) {
                    val extras = intent.extras
                    var value: Any? = extras?.get(key)
                    var field: Field? = null
                    try {
                        field = source.javaClass.getDeclaredField(fieldName)
                    } catch (e: NoSuchFieldException) {
                        e.printStackTrace()
                    }
                    if (field == null) return null;
                    if (value == null) {
                        when {
                            field.type.name == Int::class.java.name || field.type.name == "int" -> value = 0
                            field.type.name == Boolean::class.java.name || field.type.name == "boolean" -> value = false
                            field.type.name == java.lang.String::class.java.name -> value = ""
                            field.type.name == Long::class.java.name || field.type.name == "long" -> value = 0L
                            field.type.name == Double::class.java.name || field.type.name == "double" -> value = 0.0
                        }
                    }
                    if (value != null) {
                        try {
                            field.isAccessible = true
                            field.set(source, value)
                            return field.get(source)
                        } catch (e: IllegalAccessException) {
                            e.printStackTrace()
                        }
                    }
                }
                return null
            }
        },
        FRAGMENT {
            override fun findById(source: Any, id: Int): View {
                return (source as View).findViewById(id)
            }
        },
        VIEW {
            override fun findById(source: Any, id: Int): View {
                return (source as View).findViewById(id)
            }
        };
        abstract fun findById(source: Any, id: Int): View
        open fun getExtra(source: Any, key: String, fieldName: String): Any? {
            return null
        }
    }
    /**
     * 在Activity中使用注解
     * @param activity
     */
    @JvmStatic fun injectInto(activity: Activity) {
        inject(activity, activity, Finder.ACTIVITY)
    }
    /**
     * 在fragment中使用注解
     * @param fragment
     * @param v
     *
     * @return
     */
    @JvmStatic fun injectInto(fragment: Fragment, v: View) {
        inject(fragment, v, Finder.FRAGMENT)
    }
    /**
     * 在dialog中使用注解
     * @param dialog
     *
     * @return
     */
    @JvmStatic fun injectInto(dialog: Dialog) {
        inject(dialog, dialog, Finder.DIALOG)
    }
    /**
     * 在view中使用注解
     * @param obj
     * @param v
     *
     * @return
     */
    @JvmStatic fun injectInto(obj: Object, v: View) {
        inject(obj, v, Finder.VIEW)
    }
    private fun inject(host: Any, source: Any,finder: Finder) {
        val className = host.javaClass.name
        try {
            val finderClass = Class.forName(className+"\$\$ViewBinder")
            val viewBinder = finderClass.newInstance() as ViewBinder<Any>
            viewBinder.inject(host, source, finder)
        } catch (e: Exception) {
            // throw new RuntimeException("Unable to inject for " + className, e);
            println("Unable to inject for " + className)
        }
    }
}


枚举Finder中的方法getExtra()默认是final的,需要标记成open,Kotlin 要求使用open显式标注成员可被覆写。


由于ViewBinder接口里包含泛型,所以在inject方法中需要写成Any

val viewBinder = finderClass.newInstance() as ViewBinder<Any>
viewBinder.inject(host, source, finder)


否则会无法编译通过的。


  1. injectview-annotations module


Kotlin 可以简化annotation类,例如@InjectView


在Java版本是这样的

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * Created by Tony Shen on 2016/12/6.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface InjectView {
    int value() default 0;
}


Kotlin版本是这样的

import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
/**
 * Created by Tony Shen on 2017/1/24.
 */
@Target(AnnotationTarget.FIELD)
@Retention(RetentionPolicy.CLASS)
annotation class InjectView(val value: Int = 0)


看上去经过 Kotlin 改写会更加直观和简单。


  1. injectview-compiler module


所有的注解都是编译时的注解类型,比如Activity中在使用时,会生成一个相同的类名+$$ViewBinder的类。


image.png


基于apt生成的TestViewActivity$$ViewBinder类

import com.safframework.app.ui.TitleView;
import com.safframework.injectview.Injector.Finder;
import com.safframework.injectview.ViewBinder;
import java.lang.Object;
import java.lang.Override;
public class TestViewActivity$$ViewBinder implements ViewBinder<TestViewActivity> {
  @Override
  public void inject(final TestViewActivity host, Object source, Finder finder) {
    host.titleView = (TitleView)(finder.findById(source, 2131427422));
  }
}


整个库的使用方法:



  1. @InjectView


@InjectView可以简化组件的查找注册,包括android自带的组件和自定义组件。在使用@InjectView之前,我们会这样写代码

public class MainActivity extends Activity {
                ImageView imageView;
                @Override
                protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.activity_main);
                  imageView = (ImageView) findViewById(R.id.imageview);
                }
           }

在使用@InjectView之后,会这样写代码

public class MainActivity extends Activity {
                @InjectView(R.id.imageView)
                ImageView imageView;
                @Override
                protected void onCreate(Bundle savedInstanceState) {
                   super.onCreate(savedInstanceState);
                   setContentView(R.layout.activity_main);
                   Injector.injectInto(this);
                }
          }


目前,@InjectView可用于Activity、Dialog、Fragment中。在Activity和Dialog用法相似,在Fragment中用法有一点区别。

public class DemoFragment extends Fragment {
                   @InjectView(R.id.title)
                   TextView titleView;
                   @InjectView(R.id.imageview)
                   ImageView imageView;
                   @Override
                   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
                          View v = inflater.inflate(R.layout.fragment_demo, container, false);
                          Injector.injectInto(this,v); // 和Activity使用的区别之处在这里
                          initViews();
                          initData();
                          return v;
                   }
                  ......
           }


  1. @InjectViews

public class MainActivity extends Activity {
                @InjectViews(ids={R.id.imageView1,R.id.imageView2})
                ImageView[] imageviews;
                @Override
                protected void onCreate(Bundle savedInstanceState) {
                   super.onCreate(savedInstanceState);
                   setContentView(R.layout.activity_main);
                   Injector.injectInto(this);
                }
          }


  1. @InjectExtra

/**
          * MainActivity传递数据给SecondActivity
          * Intent i = new Intent(MainActivity.this,SecondActivity.class);                                               
          * i.putExtra("test", "saf");
          * i.putExtra("test_object", hello);
          * startActivity(i);
          * 在SecondActivity可以使用@InjectExtra注解
          *
          * @author Tony Shen
          *
          */
         public class SecondActivity extends Activity{
               @InjectExtra(key="test")
               String testStr;
               @InjectExtra(key="test_object")
               Hello hello;
               protected void onCreate(Bundle savedInstanceState) {
                   super.onCreate(savedInstanceState);
                   Injector.injectInto(this);
                   Log.i("++++++++++++","testStr="+testStr);
                   Log.i("++++++++++++","hello="+SAFUtil.printObject(hello)); // 该方法用于打印对象
              }
          }
  1. @OnClick


@OnClick 可以在Activity、Fragment、Dialog、View中使用,也支持多个组件绑定同一个方法。

public class AddCommentFragment extends BaseFragment {
         @Override
         public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
             View v = inflater.inflate(R.layout.fragment_add_comment, container, false);
             Injector.injectInto(this, v);
             initView();
             return v;
        }
        @OnClick(id={R.id.left_menu,R.id.btn_comment_cancel})
        void clickLeftMenu() {
            popBackStack();
        }
        @OnClick(id=R.id.btn_comment_send)
        void clickCommentSend() {
            if (StringHelper.isBlank(commentEdit.getText().toString())) {
               ToastUtil.showShort(mContext, R.string.the_comment_need_more_character);
            } else {
               AsyncTaskExecutor.executeAsyncTask(new AddCommentTask(showDialog(mContext)));
            }
        }
        ....
    }


总结:



它只实现了 ButterKnife 的几个注解功能,不过都是一些最常用的注解。有一点遗憾是,目前在 ListView 和 RecyclerView 上还有一些问题需要解决。这个库在未来还有很多可以优化的地方。


相关文章
|
前端开发 NoSQL Java
使用 Kotlin + WebFlux/RxJava 2 实现响应式以及尝试正式版本的协程
使用 Kotlin + WebFlux/RxJava 2 实现响应式以及尝试正式版本的协程
824 0
使用 Kotlin + WebFlux/RxJava 2 实现响应式以及尝试正式版本的协程
|
缓存 API 数据库
Reading:一款不错的Material Desgin风格的Kotlin版本的开源APP
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/81946831 Reading https://github.com/Hankkin/Reading 简介 很久不见,重新拾起博客吧,今天分享一个开源APP-Reading,项目的初衷是因为自己平时翻文章,但是公众号又经常忘记看,自己索性搞一个APP吧,顺便练一下Kotlin。
1397 0
|
1月前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
20 1
|
2月前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
76 1
|
3月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
61 4
|
4月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
152 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
4月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
59 8
|
4月前
|
安全 Java Android开发
探索Android应用开发中的Kotlin语言
【7月更文挑战第19天】在移动应用开发的浩瀚宇宙中,Kotlin这颗新星以其简洁、安全与现代化的特性,正迅速在Android开发者之间获得青睐。从基本的语法结构到高级的编程技巧,本文将引导读者穿梭于Kotlin的世界,揭示其如何优化Android应用的开发流程并提升代码的可读性与维护性。我们将一起探究Kotlin的核心概念,包括它的数据类型、类和接口、可见性修饰符以及高阶函数等特性,并了解这些特性是如何在实际项目中得以应用的。无论你是刚入门的新手还是寻求进阶的开发者,这篇文章都将为你提供有价值的见解和实践指导。
|
4月前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
60 6
|
4月前
|
存储 前端开发 测试技术
Android Kotlin中使用 LiveData、ViewModel快速实现MVVM模式
使用Kotlin实现MVVM模式是Android开发的现代实践。该模式分离UI和业务逻辑,借助LiveData、ViewModel和DataBinding增强代码可维护性。步骤包括创建Model层处理数据,ViewModel层作为数据桥梁,以及View层展示UI。添加相关依赖后,Model类存储数据,ViewModel类通过LiveData管理变化,而View层使用DataBinding实时更新UI。这种架构提升代码可测试性和模块化。
186 2