前言: 最近项目中需求到一个功能,就是根据后台传过来的数据,在 Android 上进行一个自定义分屏。分屏区域是不定的,分屏区域的大小更是不定的,唯一定的分屏区域的形状肯定是长方形。那这里就想到如同 Excel 合并单元格的模式来进行动态布局。而由于最近 Kotlin 语言大火,本次直接使用到 Kotlin 语言进行实际开发。
动态GridLayout的具体实现方式
一般实现 GridLayout 都是在 xml 文件中进行配置,然后在 GridLayout 对里面的子控件一个一个的写。如果用这个来实现一个简单的计算器布局,估计很累。同样在具体需求下面,这样的每个都进行布局重写根本不符合我们所需要的功能。因此博主就想到,能不能让 GridLayout 子布局动态大小,而且还要为每个子布局设置属性等等,想想就头大不是么,相当复杂了可以说。
这里就想到一个好办法,就是把 GridLayout 看做一个田字格画布,比如说 3x4 的画布,那这个画布上面就有 12 个格子。跟表格类似。就跟在 Excel 上合并单元格一样,只要能合并单元格的区域就可以单独作为一个子区域。那这个单独的子区域上面可以防止任何组件,如:TextView,Button,Fragment….只要你能想到的控件,基本都可以放进去,然后实现深一步的业务。
Kotlin 简介
今天的 Google 大会上,已经正式提出将 Kotlin 语言作为 Android 的开发语言。可以说又是一个 Google 的亲儿子,以后肯定会成为 Android 开发的主流语言。其对于传统语言 Java 的优势这里我就不多累赘了,具体可以看知乎上大神些的回答:Kotlin 作为 Android 开发语言相比传统 Java 有什么优势?
总得来说,用 Kotlin 来开发就会变的很爽。但是毕竟是一个新出语言,市场上,还没有公司正式用该语言进行具体的开发步骤,基本都是大家进行一些个人开发。毕竟很多国内关于 Kotlin 的社区并不是很完善。
本编将在实际操作的过程中顺便介绍 Kotlin 的简单知识,希望对你有所帮助。如遇到不理解的地方,欢迎 Google ,基本上一个 Stackoverflow 社区可以解决你的一些问题(要是访问慢的话试试科学上网吧)。
GridLayout 具体实现步骤
1.动态布局设置
首先我们在布局文件中放入一个 GridLayout 作为主布局,然后在代码中动态设置 GridLayout 的横向纵向数,以及每个格子的控件。
|
|
然后通过代码获取屏幕的长宽,方便后面动态布局设置长宽属性。
|
|
|
|
这里我为了操作方便,是直接把 X 和 Y 轴格子数写死了,就是横向 3 个,竖向 4 个,也就说一共是一个 3 x 4 = 12 的表格。如下图:
这里我们可以根据坐标,已经占用的横纵两向的格子数来合并这些格子。
在手机上,坐标跟普通坐标系是有点反着的,既手机的左上角是坐标原点处,往下是 Y 轴延伸方向,往左是 X 轴延伸方向。请忽略我这个灵魂画手。
那么现在我们只需要传入 4 个参数,既坐标点 x,y 以及各占用的格子数 xn,xn,就可以在这个 12 表格的 GridLayout 上面将布局画出来。
每次都单独取一个布局元素
|
|
分配好这个布局所占用的控件位置,以及占用表格大小
|
|
如果有要在格子间加入线条的需求,可以设置各个子布局间的距离,然后统一设置整个布局背景,就是线条颜色,也可以在子布局中详细设置,这个就见仁见智了。具体需求具体操作。
因为有些格子是靠边的,所以并不需要靠边的一侧空出间隔,所以要进行一个简单的判断。
|
|
接着把设置的布局元素设置为指定的控件,我这里因为功能需要,都统一成 FrameLayout,然后每个 FrameLayout 都设置一个单独的 Fragment ,方便在独立的 Fragment 中进行逻辑处理。
|
|
这里我们把这个设置子布局的内容,单独设立一个方法,方便之后的使用。
|
|
到这里基本就是这个动态设置的思路,需要注意的是,每次添加布局,子布局的大小以及位置不能有冲突,不然子布局会重叠,这个需要添加之前就计算好。
我这边自己手动添加了 2 个集合用来 for循环添加子布局。
|
|
基本到这里整个动态 GridLayout 的添加就算完成了,下面主要介绍下本编中用到的 Kotlin 语言的相关知识。
2.Kotlin 简单语法介绍
Kotlin 变量,常量
变量 var,常量 val 表示,这点比 Java 中简洁多了,跟 JavaScript 类似。
Kotlin 数据初始化
Kotlin 中的数据类型跟 java 中类似,基础数据类型都包含有。
而 Kotlin 对于数据或者对象的初始化可以直接通过 :来代替数据类型,也可以直接用 = 直接表明类型,该类型会直接饮用 = 后面的数据类型。
12private height : Int? = nullprivate var height = 0 //屏幕高度Kotlin 空指针
在Kotlin中空指针异常得到了很好的解决。
- 在类型上的处理,即在类型后面加上?,即表示这个变量或参数以及返回值可以为null,否则不允许为变量参数赋值为null或者返回null
- 对于一个可能是null的变量或者参数,在调用对象方法或者属性之前,需要加上?,否则编译无法通过。
如下面的代码就是Kotlin实现空指针安全的一个例子,而且相对Java实现而言,简直是一行代码搞定的。
这里不单独对空指针问题进行介绍了,毕竟这个属于学习 Kotlin 中的一个复杂点,关于空指针安全原理,可以参考这篇文章研究学习 Kotlin 的一些方法
Kotlin 的类型转换
在实际项目中会经常遇到数据类型转换问题,在 Kotlin 中,会经常遇到比如 Int? 转化为 Int ,就可以直接用 as 直接转化
1y as Int这一串代码直接将 y 重新赋值成 Int 类型,相当于java中的
1y = Integer.valueOf(y)简洁明了。当然 Kotlin 也支持多种转化方式,比如toInt,toString等等。
Kotlin 方法类别
代码中直接用 fun 就能表面是一个方法,甚至都不需要 function。
而且直接设置该方法的返回类型,也只需要用 :来表面,比如
123fun parse(url: String): String {retrun url}那这里就必须返回一个 String 类型。
Kotlin 方法扩展
很多时候,Framework提供给我们的API往往都时比较原子的,调用时需要我们进行组合处理,因为就会产生了一些Util类,一个简单的例子,我们想要更快捷的展示Toast信息,在Java中我们可以这样做。
1234>public static void longToast(Context context, String message) {> Toast.makeText(context, message, Toast.LENGTH_LONG).show();>}>>
但是Kotlin的实现却让人惊奇,我们只需要重写扩展方法就可以了,比如这个longToast方法扩展到所有的Context对象中,如果不去追根溯源,可能无法区分是Framework提供的还是自行扩展的。
12345>fun Context.longToast(message: String) {> Toast.makeText(this, message, Toast.LENGTH_LONG).show()>}>applicationContext.longToast("hello world")>>
注意:Kotlin的方法扩展并不是真正修改了对应的类文件,而是在编译器和IDE方面做得处理。使我们看起来像是扩展了方法。
本次项目中就使用到了该方法,通常在 BaseActivity 中,扩展一些方法比如正常的添加 fragment ,可以直接类似重写一般的扩展:
12345678//Fragment 添加inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) {beginTransaction().func().commit()}fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int) {supportFragmentManager.inTransaction { add(frameId, fragment) }}12fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int) {supportFragmentManager.inTransaction { replace(frameId, fragment) }
那我们在之后继承了 BaseActivity 的 Activity 中都可以直接调用 addFragment 和 replaceFragment 方法:
|
|
Kotlin 单例模式
我们经常要在一些 class 中使用到单例模式,一般好的 java 中都是这么调用
12345678910111213private static MyPicasso myPicasso;private synchronized static void createInstance() {if (myPicasso == null)myPicasso = new MyPicasso();}public static MyPicasso getInstance() {if (myPicasso == null)createInstance();return myPicasso;}通过一个 synchronized 进行一个异步创建单例,但是这样代码很多了就,而且不方便。但是在 Kotlin 中可以直接一个 Object 解决问题。并且可以在这个 object中进行一些具体操作,很方便。我在一个 Fragment 实现单例,并且可以直接传入参数。相当简洁。
1234567object Single {fun getInstance(str: String): EasyFragment {val instance: EasyFragment = EasyFragment()instance.mStr = strreturn instance}}Kotlin 数据序列化
在 java 中一般都要继承 2 个接口 Serializable 或者 Parcelable ,虽然现在有了工具不需要手写 get 和 set,但是代码量在那边摆着呢,显得很麻烦。
而在 Kotlin 中,直接一个 data class 就直接搞定,Kotlin 中自动包含了setter 和 getter。
1234/*** 数据Model*/data class Data(val x: Int, val y: Int, val xn: Int, val yn: Int) {}
总结
通过本次实际程序,基本可以对 Kotlin 的用法有个基本了解了,有不会的地方大可以直接 Google,国外对于该方面的资料还是比国内多很多。欢迎各位的学习讨论。
源码地址
废话不多说,直接上源码:源码地址