2024年Android最全《Kotlin从零到精通Android开发》欧阳燊(二),面试阿里巴巴国际站销售会问的问题

实战系列

话不多说,Android实战系列集合都已经系统分类好,由于文章篇幅问题没法过多展示


网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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



  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值