枚举类的构造函数是给枚举类型使用的,外部不能直接调用枚举类的构造函数
//else -> SeasonName(“??”).name
}
}
}
### []( )密封类
当when语句判断枚举类的时候,末尾例行公事加了else分支,因为when语句不知道有4种枚举,因此以防万一,必须要有else分支。为了解决判断多余分支的问题,Kotlin提出了"密封类"的概念,密封类像是一种更严格的枚举类,它内部有且仅有自身的实例对象,密封类采用了嵌套类的手段,它的嵌套类全部由自身派生而来,仿佛一个家谱。
sealed class SeasonSealed {
//密封类内部的每个嵌套类都必须继承该类
class Spring (var name:String) : SeasonSealed()
class Summer (var name:String) : SeasonSealed()
class Autumn (var name:String) : SeasonSealed()
class Winter (var name:String) : SeasonSealed()
}
有了密封类,外部使用when就不需要else分支了
调用:
btn_class_sealed.setOnClickListener {
var season = when (count++%4) {
0 -> SeasonSealed.Spring(“春天”)
1 -> SeasonSealed.Summer(“夏天”)
2 -> SeasonSealed.Autumn(“秋天”)
else -> SeasonSealed.Winter(“冬天”)
}
//密封类是一种严格的枚举类,它的值是一个有限的集合
//密封类确保条件分支覆盖了所有的枚举类型,因此不再需要else分支
tv_class_secret.text = when (season) {
is SeasonSealed.Spring -> season.name
is SeasonSealed.Summer -> season.name
is SeasonSealed.Autumn -> season.name
is SeasonSealed.Winter -> season.name
}
}
### []( )数据类
即Java中的Bean实体类,Java中的做法:
1.定义字段,构造函数
2.定义get /set方法
3.只想修改对象的某几个字段的值,其他字段也必须修改
4.调试时,得把每个字段的值都打印出来
这些任务都毫无技术含量可言,所以Kotlin有了数据类
只需要在class前面增加data关键字,并声明完整参数的构造函数,即可实现以下功能:
1.自动声明于构造函数入参同名的属性字段
2.自动实现每个属性字段的get/set方法
3.自动提供equals方法,用于比较两个数据对象是否相等
4.自动提供copy方法,允许完整负责数据对象,也可在复制后单独修改某几个字段的值
5.自动提供toString方法,便于打印数据对象中保存的值
下面我们马上定义一个:
//数据类必须有主构造函数,且至少有一个输入参数
//并且要声明与输入参数同名的属性,即输入参数前面添加var或val
//数据类不能是基类,也不能是子类,不能是抽象类,也不能是内部类,更不能是密封类
data class Man(var name:String, var age:Int, var sex:String) {
}
精简的前提是要有规范:
1.数据类必须有主构造函数,且至少有一个输入参数,因为它的属性字段要和输入参数一一对应,如果没有属性字段,那么也就不是数据类了
2.输入参数前面添加var或val,这保证每个入参都会自动声明同名的属性字段
3.只能是独立的类,不能是其他类型的类,否则不同规则之间会产生冲突
var man = Man(“张飞”, “25”, “男”)
//数据类的copy方法不带参数,表示复制一模一样的对象
var man2 = man.copy()
//数据类的copy方法带参数,表示指定参数另外赋值
var man3 = man.copy(name=“诸葛亮”)
//数据类自带equals方法和toString方法
man.equals(man2)
man.toString()
copy,equals,toString方法都是数据类自带的,提高了开发者的编码效率
### []( )模板类(泛型类)
常见的ArrayList,HashMap,AsyncTask都是模板类
举个例子:
计算小河的长度,如果输入数字就以m为单位,如果输入汉字就以米为单位
//在类名后面添加,表示这个是一个模板类
class River (var name:String, var length:T) {
fun getInfo():String {
var unit:String = when (length) {
is String -> “米”
//Int,Long,Float,Double都是数字类型的Number
is Number -> “m”
else -> “”
}
return “ n a m e 的长度是 {name}的长度是 name的长度是length$unit”
}
}
调用的时候,要在类名后面补充<参数类型>,从而动态指定实际的参数类型。
正如声明变量那样,编译器根据初始值判断变量类型,就不用显式指定类型
模板类也有这种偷懒写法,编译器根据入参的值就能知晓参数类型,那么调用的时候,就不用显式指定<参数类型>了
btn_class_generic.setOnClickListener {
var river = when (count++%4) {
//模板类声明对象时,要在模板类的类名后面加上<参数类型>
0 -> River(“小溪”, 100)
//如果编译器根据入参能判断参数类型,那么可以省略
1 -> River(“小溪”, 99.9f)
//当然保守起见,还是按照规矩添加<参数类型>
2 -> River(“??”, 50.5)
//如果你已经是老手了,怎么方便怎么来,Kotlin的涉及初衷就是偷懒
else -> River(“大河”, “一千”)
}
tv_class_secret.text = river.getInfo()
}
[]( )5.5小结
-------------------------------------------------------------------
1.类的定义和主构造函数和二级构造函数
2.类内部定义的成员属性和成员方法,伴生对象的静态属性和静态方法
3.修饰符,继承抽象类,接口,接口代理
4.特殊类:嵌套类,内部类,枚举类,密封类,数据类,模板类
附加:
[Kotlin系列之let、with、run、apply、also函数的使用]( )
[]( )第六章 Kotlin使用简单控件
==============================================================================
[]( )6.1使用按钮控件
-----------------------------------------------------------------------
### []( )按钮button
按钮事件三种Kotlin编码方式:匿名函数,内部类,接口实现
1.匿名函数方式
btn_click_anonymos.setOnClickListener { v ->
//Kotlin变量类型转换使用as
toast(“${(v as Button).text}”)
}
btn_click_anonymos.setOnLongClickListener { v ->
//Kotlin变量类型转换使用as
longToast(“${(v as Button).text}”)
true
}
2.内部类方式
private inner class MyClickListener : View.OnClickListener {
override fun onClick(v: View) {
toast(“${(v as Button).text}”)
}
}
private inner class MyLongClickListener : View.OnLongClickListener {
override fun onLongClick(v: View): Boolean {
longToast(“${(v as Button).text}”)
return true
}
}
调用的时候:
btn_click_inner.setOnClickListener(MyClickListener())
btn_click_inner.setOnLongClickListener(MyLongClickListener())
3.接口实现方式
内部类的话,每个事件都要定义一个内部类,多了也不好,试试接口实现
class ButtonClickActivity : AppCompatActivity(), OnClickListener, OnLon
gClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_button_click)
btn_click_interface.setOnClickListener(this)
btn_click_interface.setOnLongClickListener(this)
}
override fun onClick(v: View) {
if (v.id == R.id.btn_click_interface) {
toast(“${(v as Button).text}”)
}
}
override fun onLongClick(v: View): Boolean {
if (v.id == R.id.btn_click_interface) {
longToast(“${(v as Button).text}”)
}
return true
}
}
### []( )复选框CheckBox
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWZjMzA4NmQ1MjU1NTZkM2YucG5n?x-oss-process=image/format,png)
### []( )单选按钮RadioButton
同Java
[]( )6.2使用页面布局
-----------------------------------------------------------------------
### []( )线性布局LinearLayout
1. Kotlin允许对属性orientation直接赋值,从而取代了setOrientation方法;类似的还有属性gravity取代了setGravity方法;
2. Kotlin使用关键字as进行变量的类型转换;
3. Kolin支持调用dip方法将dip数值转换为px数值,倘若由Java编码则需开发者自己实现一个像素转换的工具类;
因为dip方法来自于Kotlin扩展的Anko库,所以需要在Activity代码头部加上下面一行导入语句:
import org.jetbrains.anko.dip
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTAzNmJhZDFjNGU3MGFiMmMucG5n?x-oss-process=image/format,png)
### []( )相对布局 RelativeLayout
rl\_params.addRule和rl\_params.above
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTIyYzcyMDg2OWM1ZjhkODAucG5n?x-oss-process=image/format,png)
### []( )约束布局 ConstraintLayout
layout\_constraintTop\_toTopOf : 该控件的顶部与另一个控件的顶部对齐
layout\_constraintTop\_toBottompOf : 该控件的顶部与另一个控件的底部对齐
layout\_constraintBottom\_toTopOf : 该控件的底部与另一个控件的顶部对齐
layout\_constraintBottom\_toBottomOf : 该控件的底部与另一个控件的底部对齐
layout\_constraintLeft\_toLeftOf : 该控件的左侧与另一个控件的左侧对齐
layout\_constraintLeft\_toRightOf : 该控件的左侧与另一个控件的右侧对齐
layout\_constraintRight\_toLeftOf : 该控件的右侧与另一个控件的左侧对齐
layout\_constraintRight\_toRightOf : 该控件的右侧与另一个控件的右侧对齐
若要利用代码给约束布局动态添加控件,则可照常调用addView方法,不同之处在于,新控件的布局参数必须使用约束布局的布局参数,即ConstraintLayout.LayoutParams,该参数通过setMargins/setMarginStart/setMarginEnd方法设置新控件与周围控件的间距,至于新控件与周围控件的位置约束关系,则可参照ConstraintLayout.LayoutParams的下列属性说明:
topToTop : 当前控件的顶部与指定ID的控件顶部对齐
topToBottom : 当前控件的顶部与指定ID的控件底部对齐
bottomToTop : 当前控件的底部与指定ID的控件顶部对齐
bottomToBottom : 当前控件的底部与指定ID的控件底部对齐
startToStart : 当前控件的左侧与指定ID的控件左侧对齐
startToEnd : 当前控件的左侧与指定ID的控件右侧对齐
endToStart : 当前控件的右侧与指定ID的控件左侧对齐
endToEnd : 当前控件的右侧与指定ID的控件右侧对齐
[]( )6.3使用图文控件
-----------------------------------------------------------------------
###文本视图TextView
ellipsize+TextUtils.TruncateAt.MARQUEE 跑马灯效果
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTUwMGM3OGVhYjVmNGM0MjAucG5n?x-oss-process=image/format,png)
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTE4YzBlYTRmZWM4Nzk5MDQucG5n?x-oss-process=image/format,png)
Java
tv_marquee.setGravity(Gravity.LEFT | Gravity.CENTER);
Kotlin
tv_marquee.gravity = Gravity.LEFT or Gravity.CENTER
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWQzOTNlMDUyMTY5MDZmYmUucG5n?x-oss-process=image/format,png)
### []( )图像视图 ImageView
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWE2YThhYWU2OTYzYTA5ZGEucG5n?x-oss-process=image/format,png)
### []( )文本编辑框EditText
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWM5MDAyNDViOTYwMzdmYzUucG5n?x-oss-process=image/format,png)
[]( )6.4Activity活动跳转
-----------------------------------------------------------------------------
### []( )传送配对字段数据
Java:
Intent intent = new Intent(MainActivity.this, LinearLayoutActivity.class);
startActivity(intent);
Kotlin anko
startActivity()
带参数的
Java:
Intent intent = new Intent(this, ActSecondActivity.class);
intent.putExtra(“request_time”, DateUtil.getNowTime());
intent.putExtra(“request_content”, et_request.getText().toString());
startActivity(intent);
Kotlin
方式一:用to关键字
startActivity(
“request_time” to DateUtil.nowTime,
“request_content” to et_request.text.toString())
方式二:Pair
startActivity(
Pair(“request_time”, DateUtil.nowTime),
Pair(“request_content”, et_request.text.toString()))
接收方:
val bundle = intent.extras
val request_time = bundle.getString(“request_time”)
val request_content = bundle.getString(“request_content”)
### []( )传送序列化数据
//@Parcelize注解表示自动实现Parcelize接口的相关方法
@Parcelize
data class MessageInfo(val content: String, val send_time: String) : Pa
rcelable {
}
//@Parcelize标记需要在build.gradle设置experimental = true
androidExtensions {
experimental = true
}
//传送序列化数据
val request = MessageInfo(et_request.text.toString(), DateUtil.nowTime)
startActivity(“message” to request)
接收方:
//获得Parcelable的参数
val request = intent.extras.getParcelable(“message”)
${request.content}
### []( )跳转时指定启动模式
anko库取消了intent方法有利有弊,弊端是intent对象的setAction,setData,addCategory,setFlags怎么设置
那么下面这种方式就可以拿到intent对象设置了
val intent = intentFor(
“request_time” to DateUtil.nowTime,
“request_content” to et_request.text.toString())
Android有两种方式设置启动模式:清单文件和代码设置
#### []( )清单文件设置
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTcyNmQ1ZjllZTRiNTZhMjAucG5n?x-oss-process=image/format,png)
#### []( )代码设置
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LWMyZmQwZTUzNjY0ZGI1OWEucG5n?x-oss-process=image/format,png)
anko库仍然简化了代码:startActivity(intent.newTask())
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTM2NDlmNzdkNTJlMWQzNWYucG5n?x-oss-process=image/format,png)
### []( )处理返回数据 startActivityForResult
跳转
val info = MessageInfo(et_request.text.toString(), DateUtil.nowTime)
//ForResult表示需要返回参数
startActivityForResult(0, “message” to info)
下个页面返回数据
btn_act_response.setOnClickListener {
val response = MessageInfo(et_response.text.toString(), DateUtil.no
wTime)
val intent = Intent()
intent.putExtra(“message”, response)
//调用setResult表示参数返回到上个页面
setResult(Activity.RESULT_OK, intent)
finish()
}
收到数据后:
//返回本页面时回调onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data:
Intent?) {
if (data != null) {
//获得应答参数
val response = data.extras.getParcelable
(“message”)
tv_request.text = " r e s p o n s e . s e n d t i m e , {response.send_time}, response.sendtime,{response.content}"
}
}
[]( )6.5实战项目
---------------------------------------------------------------------
略
[]( )6.6总结
-------------------------------------------------------------------
1.按钮控件
2.布局视图
3.图文控件
4.activity跳转
5.anko库对话框
[]( )第七章 Kotlin操纵复杂控件
==============================================================================
[]( )7.1使用视图排列
-----------------------------------------------------------------------
### []( )下拉框spinner
private fun initSpinner() {
val starAdapter = ArrayAdapter(this, R.layout.item_select, starArra
y)
starAdapter.setDropDownViewResource(R.layout.item_dropdown)
//Android 8.0之后的findViewById要求在后面添加""才能进行类型转换
val sp = findViewById(R.id.sp_dialog) as Spinner
sp.prompt = “请选择行星”
sp.adapter = starAdapter
sp.setSelection(0)
sp.onItemSelectedListener = MySelectedListener()
}
private val starArray = arrayOf(“水星”, “水星”, “火星”, “木星”)
internal inner class MySelectedListener : OnItemSelectedListener {
override fun onItemSelected(arg0: AdapterView<*>, arg1: View, arg2:
Int, arg3: Long) {
toast(“${starArray[arg2]}”)
}
override fun onNothingSelected(arg0: AdapterView<*>) {}
}
anko库的简化:
<TextView
android:id=“@+id/tv_spinner”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_toRightOf=“@+id/tv_dialog”
android:gravity=“center”
android:drawableRight=“@drawable/arrow_down”
android:textColor=“@color/black”
android:textSize=“17sp” />
val satellites = listOf(“水星”, “水星”, “火星”, “木星”)
tv_spinner.text = satellites[0]
tv_spinner.setOnClickListener {
selector(“请选择行星”, satellites) { i ->
tv_spinner.text = satellites[i]
toast(“${tv_spinner.text}”)
}
}
主要借助了:import org.jetbrains.anko.selector
anko库里面的selector源码是利用了AlertDialog的setItem方法
###列表视图listview
Kotlin的扩展视图selector
Kotlin要求每个变量都要初始化
lateinit延迟初始化属性,那么修饰的变量无需赋空值,使用的时候也不用加!!
class PlanetListAdapter(private val context: Context, private val planetList: MutableList, private val background: Int) : BaseAdapter() {
override fun getCount(): Int = planetList.size
override fun getItem(position: Int): Any = planetList[position]
override fun getItemId(position: Int): Long = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var view = convertView
val holder: ViewHolder
if (convertView == null) {
view = LayoutInflater.from(context).inflate(R.layout.item_list_view, null)
holder = ViewHolder()
//先声明视图持有者的实例,再依次获取内部的各个控件对象
holder.ll_item = view.findViewById(R.id.ll_item) as LinearLayout
holder.iv_icon = view.findViewById(R.id.iv_icon) as ImageView
holder.tv_name = view.findViewById(R.id.tv_name) as TextView
holder.tv_desc = view.findViewById(R.id.tv_desc) as TextView
view.tag = holder
} else {
holder = view.tag as ViewHolder
}
val planet = planetList[position]
holder.ll_item.setBackgroundColor(background)
holder.iv_icon.setImageResource(planet.image)
holder.tv_name.text = planet.name
holder.tv_desc.text = planet.desc
return view!!
}
//ViewHolder中的属性使用关键字lateinit延迟初始化
inner class ViewHolder {
lateinit var ll_item: LinearLayout
lateinit var iv_icon: ImageView
lateinit var tv_name: TextView
lateinit var tv_desc: TextView
}
}
以上的Kotlin代码总算有点模样了,虽然总体代码还不够精简,但是至少清晰明了,其中主要运用了Kotlin的以下三项技术:
1、构造函数和初始化参数放在类定义的首行,无需单独构造,也无需手工初始化;
2、像getCount、getItem、getItemId这三个函数,仅仅返回简单运算的数值,可以直接用等号取代大括号;
3、对于视图持有者的内部控件,在变量名称前面添加lateinit,表示该属性为延迟初始化属性;
### []( )网格视图
在前面的列表视图一小节中,给出了Kotlin改写后的适配器类,通过关键字lateinit固然避免了麻烦的空校验,可是控件对象迟早要初始化的呀,晚赋值不如早赋值。翻到前面PlanetListAdapter的实现代码,认真观察发现控件对象的获取其实依赖于布局文件的视图对象view,既然如此,不妨把该视图对象作为ViewHolder的构造参数传过去,使得视图持有者在构造之时便能一块初始化内部控件。据此改写后的Kotlin适配器代码如下所示:
class PlanetGridAdapter(private val context: Context, private val planetList: MutableList, private val background: Int) : BaseAdapter() {
override fun getCount(): Int = planetList.size
override fun getItem(position: Int): Any = planetList[position]
override fun getItemId(position: Int): Long = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var view = convertView
val holder: ViewHolder
if (view == null) {
view = LayoutInflater.from(context).inflate(R.layout.item_grid_view, null)
holder = ViewHolder(view)
//视图持有者的内部控件对象已经在构造时一并初始化了,故这里无需再做赋值
view.tag = holder
} else {
holder = view.tag as ViewHolder
}
val planet = planetList[position]
holder.ll_item.setBackgroundColor(background)
holder.iv_icon.setImageResource(planet.image)
holder.tv_name.text = planet.name
holder.tv_desc.text = planet.desc
return view!!
}
//ViewHolder中的属性在构造时初始化
inner class ViewHolder(val view: View) {
val ll_item: LinearLayout = view.findViewById(R.id.ll_item) as LinearLayout
val iv_icon: ImageView = view.findViewById(R.id.iv_icon) as ImageView
val tv_name: TextView = view.findViewById(R.id.tv_name) as TextView
val tv_desc: TextView = view.findViewById(R.id.tv_desc) as TextView
}
}
外部调用
gv_planet.adapter = PlanetGridAdapter(this, Planet.defaultList, Color.W
HITE)
### []( )循环视图RecyclerView
RecyclerView可以实现线性列表,网格列表,瀑布流网格
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8zMTU0ODA4LTIyYmFiZDQ2YjQ2NTY4MzYucG5n?x-oss-process=image/format,png)
布局管理器
LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager
循环适配器
循环适配器和其他适配器的区别
1.自带视图持有者ViewHolder及其重用功能,无需开发者手工重用ViewHolder
2.未带点击和长按功能,需要开发者自己实现
3.增加区分不同列表项的视图类型
4.可单独对个别项进行增删改操作,无须刷新整个列表
Kotlin实现:
//ViewHolder在构造时初始化布局中的控件对象
class RecyclerLinearAdapter(private val context: Context, private val infos: MutableList) : RecyclerView.Adapter(), OnItemClickListener, OnItemLongClickListener {
val inflater: LayoutInflater = LayoutInflater.from(context)
//获得列表项的数目
override fun getItemCount(): Int = infos.size
//创建整个布局的视图持有者
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View = inflater.inflate(R.layout.item_recycler_linear, parent, false)
return ItemHolder(view)
}
//绑定每项的视图持有者
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val vh: ItemHolder = holder as ItemHolder
vh.iv_pic.setImageResource(infos[position].pic_id)
vh.tv_title.text = infos[position].title
vh.tv_desc.text = infos[position].desc
// 列表项的点击事件需要自己实现
vh.ll_item.setOnClickListener { v ->
itemClickListener?.onItemClick(v, position)
}
vh.ll_item.setOnLongClickListener { v ->
itemLongClickListener?.onItemLongClick(v, position)
true
}
}
//ItemHolder中的属性在构造时初始化
inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {
var ll_item = view.findViewById(R.id.ll_item) as LinearLayout
var iv_pic = view.findViewById(R.id.iv_pic) as ImageView
var tv_title = view.findViewById(R.id.tv_title) as TextView
var tv_desc = view.findViewById(R.id.tv_desc) as TextView
}
private var itemClickListener: OnItemClickListener? = null
fun setOnItemClickListener(listener: OnItemClickListener) {
this.itemClickListener = listener
}
private var itemLongClickListener: OnItemLongClickListener? = null
fun setOnItemLongClickListener(listener: OnItemLongClickListener) {
this.itemLongClickListener = listener
}
override fun onItemClick(view: View, position: Int) {
val desc = "您点击了第${position+1}项,标题是${infos[position].title}"
context.toast(desc)
}
override fun onItemLongClick(view: View, position: Int) {
val desc = "您长按了第${position+1}项,标题是${infos[position].title}"
context.toast(desc)
}
}
可是这个循环适配器RecyclerLinearAdapter仍然体量庞大,细细观察发现其实它有着数个与具体业务无关的属性与方法,譬如上下文对象context、布局载入对象inflater、点击监听器itemClickListener、长按监听器itemLongClickListener等等,故而完全可以把这些通用部分提取到一个基类,然后具体业务再从该基类派生出特定的业务适配器类。根据这种设计思路,提取出了循环视图基础适配器,它的Kotlin代码如下所示:
//循环视图基础适配器
abstract class RecyclerBaseAdapter
}
一旦有了这个基础适配器,实际业务的适配器即可由此派生而来,真正需要开发者编写的代码一下精简了不少。下面便是个循环视图的网格适配器,它实现了类似淘宝主页的网格频道栏目,具体的Kotlin代码如下所示:
//把公共属性和公共方法剥离到基类RecyclerBaseAdapter,
//此处仅需实现getItemCount、onCreateViewHolder、onBindViewHolder三个方法,以及视图持有者的类定义
class RecyclerGridAdapter(context: Context, private val infos: MutableList) : RecyclerBaseAdapter<RecyclerView.ViewHolder>(context) {
override fun getItemCount(): Int = infos.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view: View = inflater.inflate(R.layout.item_recycler_grid, parent, false)
return ItemHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val vh = holder as ItemHolder
vh.iv_pic.setImageResource(infos[position].pic_id)
vh.tv_title.text = infos[position].title
}
inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {
var ll_item = view.findViewById(R.id.ll_item) as LinearLayout
var iv_pic = view.findViewById(R.id.iv_pic) as ImageView
var tv_title = view.findViewById(R.id.tv_title) as TextView
}
}
然而基类不过是雕虫小技,Java也照样能够运用,所以这根本不入Kotlin的法眼,要想超越Java,还得拥有独门秘笈才行。注意到适配器代码仍然通过findViewById方法获得控件对象,可是号称在Anko库的支持之下,Kotlin早就无需该方法就能直接访问控件对象了呀,为啥这里依旧靠老牛拉破车呢?其中的缘由是Anko库仅仅实现了Activity活动页面的控件自动获取,并未实现适配器内部的自动获取。不过Kotlin早就料到了这一手,为此专门提供了一个插件名叫LayoutContainer,只要开发者让自定义的ViewHolder继承该接口,即可在视图持有者内部无需获取就能使用控件对象了。这下不管是在Activity代码,还是在适配器代码中,均可将控件名称拿来直接调用了。这么神奇的魔法,快来看看Kotlin的适配器代码是如何书写的:
//利用Kotlin的插件LayoutContainer,在适配器中直接使用控件对象,而无需对其进行显式声明
class RecyclerStaggeredAdapter(context: Context, private val infos: MutableList) : RecyclerBaseAdapter<RecyclerView.ViewHolder>(context) {
override fun getItemCount(): Int = infos.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view: View = inflater.inflate(R.layout.item_recycler_staggered, parent, false)
return ItemHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as ItemHolder).bind(infos[position])
}
//注意这里要去掉inner,否则运行报错“java.lang.NoSuchMethodError: No virtual method _$_findCachedViewById”
class ItemHolder(override val containerView: View?) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(item: RecyclerInfo) {
iv_pic.setImageResource(item.pic_id)
tv_title.text = item.title
}
}
}
还需要在模块的build.gradle增加:
androidExtensions {
experimental = true
}
上面采用了新的适配器插件,似乎已经大功告成,可是依然要书写单独的适配器代码,仔细研究发现这个RecyclerStaggeredAdapter还有三个要素是随着具体业务而变化的,包括:
1、列表项的布局文件资源编码,如R.layout.item\_recycler\_staggered;
2、列表项信息的数据结构名称,如RecyclerInfo;
3、对各种控件对象的设置操作,如ItemHolder类的bind方法;
除了以上三个要素,RecyclerStaggeredAdapter内部的其余代码都是允许复用的,因此,接下来的工作就是想办法把这三个要素抽象为公共类的某种变量。对于第一个的布局编码,可以考虑将其作为一个整型的输入参数;对于第二个的数据结构,可以考虑定义一个模板类,在外部调用时再指定具体的数据类;对于第三个的bind方法,若是Java编码早已束手无策,现用Kotlin编码正好将该方法作为一个函数参数传入。依照三个要素的三种处理对策,进而提炼出来了循环适配器的通用类RecyclerCommonAdapter,详细的Kotlin代码示例如下:
//循环视图通用适配器
//将具体业务中会变化的三类要素抽取出来,作为外部传进来的变量。这三类要素包括:
//布局文件对应的资源编号、列表项的数据结构、各个控件对象的初始化操作
class RecyclerCommonAdapter(context: Context, private val layoutId: Int, private val items: List, val init: (View, T) -> Unit): RecyclerBaseAdapter<RecyclerCommonAdapter.ItemHolder>(context) {
override fun getItemCount(): Int = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view: View = inflater.inflate(layoutId, parent, false)
return ItemHolder<T>(view, init)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val vh: ItemHolder<T> = holder as ItemHolder<T>
vh.bind(items.get(position))
}
//注意init是个函数形式的输入参数
class ItemHolder<in T>(val view: View, val init: (View, T) -> Unit) : RecyclerView.ViewHolder(view) {
fun bind(item: T) {
init(view, item)
}
}
}
有了这个通用适配器,外部使用适配器只需像函数调用那样传入这三种变量就好了,具体调用的Kotlin代码如下所示:
//第二种方式:使用把三类可变要素抽象出来的通用适配器
val adapter = RecyclerCommonAdapter(this, R.layout.item_recycler_staggered, RecyclerInfo.defaultStag,
{view, item ->
val iv_pic = view.findViewById(R.id.iv_pic) as ImageView
val tv_title = view.findViewById(R.id.tv_title) as TextView
iv_pic.setImageResource(item.pic_id)
tv_title.text = item.title
})
rv_staggered.adapter = adapter
最终出炉的适配器仅有十行代码不到,其中的关键技术——函数参数真是不鸣则已、一鸣惊人。至此本节的适配器实现过程终于落下帷幕,一路上可谓是过五关斩六将,硬生生把数十行的Java代码压缩到不到十行的Kotlin代码,经过不断迭代优化方取得如此彪炳战绩。尤其是最后的两种实现方式,分别运用了Kotlin的多项综合技术,才能集Kotlin精妙语法之大成。
[]( )7.2使用材质设计
-----------------------------------------------------------------------
MaterialDesign库提供了协调布局CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout
### []( )协调布局CoordinatorLayout
继承自ViewGroup
对齐方式:layout\_gravity,
子视图位置:app:layout\_anchor?app:layout\_anchorGravity
行为:app:layout\_behavior
FloatingActionButton 悬浮按钮
悬浮按钮会悬浮在其他视图之上
隐藏和显示悬浮按钮时会播放切换动画,hide和show方法
悬浮按钮默认会随着便签条Snackbar的出现或消失动态调整位置
###工具栏Toolbar
Android 5.0之后使用Toolbar代替ActionBar
不过为了兼容老版本,ActionBar仍然保留,可是ActionBar和Toolbar都占着顶部导航栏的位置,所以想引入Toolbar就得关闭ActionBar,具体步骤如下:
1.定义一个style