Kotlin之小试Anko
先声明,因项目需要。本人也是刚刚尝试,是看了大神的资料,才有了这篇文章。代码是自己跟着大神的脚步走的。
第一部分
资料地址
anko是什么
Anko是JetBrains开发的一个强大的库,它主要的目的是用来替代以前XML的方式来使用代码生成UI布局的,它包含了很多的非常有帮助的函数和属性来避免让你写很多的模版代码。
环境配置 (结合kotlin使用)#
项目 build.gradle
buildscript {
ext.kotlin_version = '1.3.11'
ext.anko_version = "0.10.8"
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions: $kotlin_version"
}
}
app build.gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
// Anko Commons
implementation "org.jetbrains.anko:anko-commons:$anko_version"
// Anko Layouts
implementation "org.jetbrains.anko:anko-sdk25:$anko_version" // sdk15, sdk19, sdk21, sdk23 are also available
implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version"
// Coroutine listeners for Anko Layouts
implementation "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
implementation "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version"
// Anko SQLite
implementation "org.jetbrains.anko:anko-sqlite:$anko_version"
}
示例
xml中的控件的属性的设置
xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
/>
</LinearLayout>
activity
tv_home.text = "test_1"
怎么样,就是这么神奇
java中创建布局
private fun showCustomerLayout() {
verticalLayout {
padding = dip(30)
editText {
hint = "Name"
textSize = 24f
}.textChangedListener {
onTextChanged { str, _, _, _ ->
println(str)
}
}
editText {
hint = "Password"
textSize = 24f
}.textChangedListener {
onTextChanged { str, _, _, _ ->
println(str)
}
}
button("跳转到其它界面") {
textSize = 26f
id = BTN_ID
onClick {
// 界面跳转并携带参数
startActivity<IntentActivity>("name" to "小明", "age" to 12)
}
}
button("显示对话框") {
onClick {
makeAndShowDialog()
}
}
button("列表selector") {
onClick {
makeAndShowListSelector()
}
}
}
}
private fun makeAndShowListSelector() {
val countries = listOf("Russia", "USA", "England", "Australia")
selector("Where are you from", countries) { ds, i ->
toast("So you're living in ${countries[i]},right?")
}
}
private fun makeAndShowDialog() {
alert("this is the msg") {
customTitle {
verticalLayout {
imageView(R.mipmap.ic_launcher)
editText {
hint = "hint_title"
}
}
}
okButton {
toast("button-ok")
// 会自行关闭不需要我们手动调用
}
cancelButton {
toast("button-cancel")
}
}.show()
}
效果如下
image对话框按钮点击效果
image列表selector点击效果
image列表条目点击效果
image目标界面效果
image补充:(目标界面代码)
private fun receiveAndShowResult() {
// 数据的获取
val name = intent.extras.getString("name")
val age = intent.extras.getInt("age")
// 界面的定义及展示
verticalLayout {
textView(name) {
textSize = 18f
textColor = Color.BLACK
}
// 布局参数设置
view {
backgroundColor = Color.GRAY
}.lparams(width = wrapContent, height = 1) {
topMargin = dip(5)
}
textView("$age") {
textSize = 18f
textColor = Color.BLACK
}
view {
backgroundColor = Color.GRAY
}.lparams(width = wrapContent, height = 1) {
topMargin = dip(5)
}
button("返回") {
onClick {
finish()
}
}
}
}
高级使用部分
anko-sqlite配置
// Anko SQLite
implementation "org.jetbrains.anko:anko-sqlite:$anko_version"
anko-sqlite的使用
db创建
class MyDb(ctx: Context) : ManagedSQLiteOpenHelper(ctx, "MyDb", null, 1) {
companion object {
private var instance: MyDb? = null
// 构建线程安全的单例
@Synchronized
fun getInstance(ctx: Context): MyDb {
if (instance == null) {
instance = MyDb(ctx)
}
return instance!!
}
}
override fun onCreate(db: SQLiteDatabase?) {
// 创建表
db?.createTable(
"Customer", true,
"id" to INTEGER + PRIMARY_KEY + UNIQUE,
"t_name" to TEXT,
"t_photo" to BLOB
)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
when (newVersion) {
2 -> {
if (oldVersion < newVersion) {
println("newVersion -- $newVersion")
db?.execSQL("ALTER TABLE Person RENAME To tmp_person")
db?.createTable(
"Person", true,
"id" to INTEGER + PRIMARY_KEY + UNIQUE,
"name" to TEXT,
"age" to INTEGER,
"address" to TEXT,
"sex" to INTEGER
)
db?.execSQL("INSERT INTO Person Select id,name,age,address FROM tmp_person")
db?.dropTable("tmp_person")
}
}
}
}
}
db扩展方法
val Context.database: MyDb
get() = MyDb.getInstance(applicationContext)
使用示例
创建表
database.use {
if (attempt {
createTable(
"book", true,
"id" to INTEGER + PRIMARY_KEY + UNIQUE,
"name" to TEXT,
"author" to TEXT,
"price" to INTEGER,
"pubtime" to TEXT
)
toast("表创建成功")
}.isError) {
toast("表创建失败")
}
}
添加数据
database.use {
if (attempt {
insert(
"book",
"name" to "奇迹",
"author" to "未知",
"price" to 32,
"pubtime" to "2012-12-11"
)
insert(
"book",
"name" to "奇迹1",
"author" to "未知",
"price" to 32,
"pubtime" to "2012-12-11"
)
insert(
"book",
"name" to "奇迹2",
"author" to "未知",
"price" to 33,
"pubtime" to "2011-12-11"
)
insert(
"book",
"name" to "金瓶梅",
"author" to "他二爷",
"price" to 25,
"pubtime" to "2010-1-1"
)
insert(
"book",
"name" to "平凡的世界",
"author" to "路遥",
"price" to 15,
"pubtime" to "208-10-15"
)
toast("数据插入成功")
}.isError) {
toast("数据插入成功")
}
}
更新数据
if (attempt {
database.use {
if (attempt {
execSQL("update book set price = 85 where name = '平凡的世界'")
toast("更新数据成功")
}.isError) {
toast("更新数据失败")
}
}
}.isError) {
toast("错误,表不存在")
}
查找数据
单一查找
val whereArgs = select("book", "name", "price", "author")
.whereArgs("(price = {price})", "price" to 33)
// 另一种方式用的是三元元组,这种是自定义式的数据解析器
// parseSingle 和 parseOpt数据都只适用于结果只有一条的时候,如果有多条,会报错
val list = whereArgs.parseSingle(classParser<Book>())
println(list)
Book(name=奇迹2, price=33, author=未知)
查找全部
val whereArgs = select("book", "name", "price", "author")
var parser = rowParser { name: String, price: Int, author: String ->
Triple(name, price, author)
}
val list = whereArgs.parseList(parser)
println(list)
[(奇迹, 32, 未知), (奇迹1, 32, 未知), (奇迹2, 33, 未知), (金瓶梅, 25, 他二爷), (平凡的世界, 85, 路遥)]
条件过滤查找
val whereArgs = select("book", "name", "price", "author")
.whereArgs("(price > {price})", "price" to 30)
var parser = rowParser { name: String, price: Int, author: String ->
Triple(name, price, author)
}
val list = whereArgs.parseList(parser)
println(list)
[(奇迹, 32, 未知), (奇迹1, 32, 未知), (奇迹2, 33, 未知), (平凡的世界, 85, 路遥)]
val whereArgs = select("book", "name", "price", "author")
.whereArgs("(price > {price})", "price" to 30)
// 另一种方式用的是三元元组,这种是自定义式的数据解析器
val list = whereArgs.parseList(classParser<Book>())
println(list)
[Book(name=奇迹, price=32, author=未知), Book(name=奇迹1, price=32, author=未知), Book(name=奇迹2, price=33, author=未知), Book(name=平凡的世界, price=85, author=路遥)]
删除表
database.use {
if (attempt {
dropTable("book")
toast("表删除成功")
}.isError) {
toast("表删除失败")
}
}
再次感谢大神的资料,大神的博客地址
第二部分
Anko的源码解析
目标示例代码
verticalLayout {
textView("注册") {
textSize = dip(22).toFloat()
textColor = Color.BLUE
gravity = Gravity.CENTER_HORIZONTAL
}
editText {
hint = "请输入姓名"
}
// 水平线性布局
linearLayout {
textView("忘记密码")
textView("没有账户重新注册一个")
}
button("确定") {
onClick {
toast("注册用户")
}
}
loadRes()
}
其次给出代码工作流程
1. 创建控件
2. 调用init(),其实就是调用我们传递的lambda表达式
3. addView(),根据传入的上下文执行不同的操作,activity->setContentView,viewManager->addView()
verticalLayout函数
inline fun Activity.verticalLayout(theme: Int = 0): LinearLayout = verticalLayout(theme) {}
inline fun Activity.verticalLayout(theme: Int = 0, init: (@AnkoViewDslMarker _LinearLayout).() -> Unit): LinearLayout {
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}
verticalLayout() 其实是个函数,参数也是个函数。这里就涉及到了“闭包”,简单来说就是:“verticalLayout”这个方法的参数(init)也是个函数,这个参数可以理解为在_LinearLayout类中扩展的匿名方法或者代码块。其中_LinearLayout是LinearLayout的子类,这个咱们后面讲的lparam时候再说。这个方法返回是一个LineaerLayout,咱们先来看看他的代码是怎么生成LinearLayout。
对象的生成
@PublishedApi
internal object `$$Anko$Factories$CustomViews` {
// 单例类的创建
val VERTICAL_LAYOUT_FACTORY = { ctx: Context ->
val view = _LinearLayout(ctx)
view.orientation = LinearLayout.VERTICAL
view
}
}
创建一个单例工厂类,里面有个函数属性:
val VERTICAL_LAYOUT_FACTORY:(Context)-> _LinearLayout
ankoView函数
inline fun <T : View> Activity.ankoView(factory: (ctx: Context) -> T, theme: Int, init: T.() -> Unit): T {
val ctx = AnkoInternals.wrapContextIfNeeded(this, theme)
val view = factory(ctx)
view.init() -->> 传入的lambda表达式
AnkoInternals.addView(this, view) // 先创建AnkoContextImpl对象,然后再调用AnkoContextImpl的addView
return view
}
AnkoInternals.addView函数
fun <T : View> addView(activity: Activity, view: T) {
// 先创建上下文
createAnkoContext(activity,
{
AnkoInternals.addView(this, view)
}, true)
}
createAnkoContext函数
inline fun <T> T.createAnkoContext(
ctx: Context,
init: AnkoContext<T>.() -> Unit,
setContentView: Boolean = false
): AnkoContext<T> {
val dsl = AnkoContextImpl(ctx, this, setContentView)
dsl.init() // 这里其实是调用了我们传入的labmda表达式,调用的是addView方法
return dsl
}
AnkoInternals.addView
fun <T : View> addView(manager: ViewManager, view: T) = when (manager) {
is ViewGroup -> manager.addView(view) // 如果是ViewGroup直接调用addView
is AnkoContext<*> -> manager.addView(view, null) // 如果是AnkoContext直接调用AnkoContextImpl的addView
else -> throw AnkoException("$manager is the wrong parent")
}
AnkoContextImpl
open class AnkoContextImpl<T>(
override val ctx: Context,
override val owner: T,
private val setContentView: Boolean
) : AnkoContext<T> {
private var myView: View? = null
override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
if (view == null) return
if (myView != null) {
alreadyHasView()
}
this.myView = view
if (setContentView) { // 上面传入的是true
doAddView(ctx, view)
}
}
private fun doAddView(context: Context, view: View) {
when (context) {
is Activity -> context.setContentView(view) // 如果是activity,调用setContentView
is ContextWrapper -> doAddView(context.baseContext, view) // 调用addView
else -> throw IllegalStateException("Context is not an Activity, can't set content view")
}
}
open protected fun alreadyHasView(): Unit = throw IllegalStateException("View is already set: $myView")
}
lparams实现原理分析
示例代码
private fun @AnkoViewDslMarker _LinearLayout.test_include() {
include<View>(R.layout.layout_test01) {
backgroundColor = Color.LTGRAY
}.lparams(width = matchParent) {
margin = dip(12)
}
}
T.lparams
inline fun <T: View> T.lparams(
width: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
height: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
init: RelativeLayout.LayoutParams.() -> Unit
): T {
val layoutParams = RelativeLayout.LayoutParams(width, height)
layoutParams.init()
this@lparams.layoutParams = layoutParams
return this
}
Fragment之列表展示
Activity
class UIFragmentAct : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_uifragment)
supportFragmentManager.beginTransaction().replace(R.id.ll_root, MyFragment()).commit()
}
}
Fragment
class MyFragment : Fragment() {
var swipeLaout: SwipeRefreshLayout? = null
var recView: RecyclerView? = null
var dataList: ArrayList<String>? = null
var ctx: Context? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
ctx = context
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return createView()
}
private fun createView(): View {
return UI {
frameLayout {
swipeLaout = swipeRefreshLayout {
setColorSchemeResources(android.R.color.holo_blue_bright)
setOnRefreshListener {
getData()
swipeLaout?.isRefreshing = false
}
recView = recyclerView {
layoutManager = LinearLayoutManager(context)
backgroundColor = Color.parseColor("#f3f3f3")
}
}.lparams(width = matchParent, height = matchParent)
}
}.view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
getData()
recView?.adapter = ListItemAdapter(ctx!!, dataList)
}
private fun getData() {
dataList = ArrayList<String>()
dataList?.add("test01")
dataList?.add("test02")
dataList?.add("test03")
dataList?.add("test04")
dataList?.add("test05")
dataList?.add("test06")
dataList?.add("test07")
dataList?.add("test08")
}
}
Adapter
class ListItemAdapter(var context: Context, var dataList: ArrayList<String>?) :
RecyclerView.Adapter<ListItemAdapter.ViewHolder>() {
var inflater: LayoutInflater = LayoutInflater.from(context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemAdapter.ViewHolder {
// 老方式
// val itemView = inflater.inflate(R.layout.layout_test_list_item, parent, false)
// 新方式创建ViewHolder
return ViewHolder(createItemView(context))
}
private fun createItemView(context: Context): View {
return with(context) {
linearLayout {
lparams(width = matchParent, height = dip(100)) {
bottomMargin = dip(5)
}
cardView {
linearLayout {
gravity = Gravity.CENTER_VERTICAL
imageView(R.mipmap.ic_launcher_round)
.lparams(width = dip(65), height = dip(65)) {
leftMargin = dip(10)
}
textView {
id = R.id.tv_title
textSize = dip(18).toFloat()
textColor = Color.BLUE
}.lparams(width = matchParent, height = wrapContent) {
leftMargin = dip(10)
rightMargin = dip(10)
}
}
}.lparams(width = matchParent, height = matchParent) {
padding = dip(5)
}
}
}
}
override fun getItemCount(): Int {
return dataList?.size ?: 0
}
override fun onBindViewHolder(holder: ListItemAdapter.ViewHolder, position: Int) {
if (dataList != null) {
val txt = dataList?.get(position)
holder.tvTitle.text = txt
holder.itemView.onClick {
context.toast("点击了: $txt")
}
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvTitle: TextView = itemView.findViewById(R.id.tv_title)
}
}
界面效果
image异步任务及界面刷新
async { // 在表达式中执行异步任务
println("ThreadId = ${Thread.currentThread().id}")
Thread.sleep(500)
uiThread { // 任务回调到ui线程
println("ThreadId = ${Thread.currentThread().id}")
tvResult?.text = "www.baidu.com"
}
}