Android-Kotlin VS Flutter-Dart - 自定义控制摇杆

5,318 阅读7分钟
前言

比较Kotlin OR Flutter 谁好, 就像在比较[程序员]和[画家]谁[唱歌]比较好听
Kotlin是语言,完美平复了我对Java的恨铁不成钢的心情。
Flutter是框架,完美实现了我一套代码,六端运行的梦想。

Flutter是和Android一个等级的,它们都是运行在设备上的框架
Kotlin是和Dart一个等级的,它们都是新时代的编程语言  
那谁更香? 别问,问就都香。 

如果你还在Kotlin和Flutter之间犹豫不定  
那我就为你指条路: 去研究[数据结构和算法分析]
研究到想吐的时候再来选择,如果还是在Kotlin和Flutter犹豫不定  
那我就为你指条路: 去研究[数据结构和算法分析]
研究到想吐的时候再来选择,如果还是在Kotlin和Flutter犹豫不定  
那我就为你指条路: 去研究[数据结构和算法分析]
研究到想吐的时候再来选择,如果还是在Kotlin和Flutter犹豫不定  
...
这样最终你就会成为一个[数据结构和算法分析]的大师
而这样的大师拥有同时掌握Kotlin和Flutter的耐力和技术支持

通过本篇想说明: 不是什么技术好不好,而是你能干什么。
就像作文模板、中文汉字你都认识,却无法写出流芳百世的佳作
限制你的并非是语言/框架本身,而是你的思维分析和解决问题的能力

王侯将相宁有种乎? 何必贴上好坏的标签,非争个天下第一? 香不就行了吗!

KotlinFlutter

Android-Kotlin 篇

一、自定义控件

1.类的定义
 [1] 类通过[class]关键字定义,类名[大驼峰]  
 [2] 构造器关键字[constructor],可直接跟在类名后  
 [3] 继承通过 : 指定父类
class HandleView constructor(context: Context, attrs: AttributeSet? = null) : View(context, attrs){

}

2.初始函数与变量定义
[1] 通过[var]关键字指定变量, 
[2] 通过[privite]关键字修饰私有权限
[3] 创建对象[不需要] new 关键字
[4] 一条语句的末尾[不需要] ; 
[5] init代码块内可以盛放数据初始逻辑
[6] 对于对象的get/set方法,可使用简写方式
class HandleView constructor(context: Context, attrs: AttributeSet? = null) : View(context, attrs){

    private var paint:Paint = Paint()

    init {
        paint.color = Color.BLUE
        paint.isAntiAlias=true
        paint.style=Paint.Style.FILL
    }
}

3.方法的覆写
[1] 方法的关键字[fun]
[2] 复写的关键字[override]
[3] 入参格式 [名称:类型 ]
class HandleView constructor(context: Context, attrs: AttributeSet? = null) : View(context, attrs){
   //英雄所见...
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawCircle(100f,100f,50f,paint)
    }
}

4.使用自定义控件

这里先直接放在setContentView里看下效果

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(HandleView(this))
    }
}

二、事件响应

如下图,在移动时会触发事件,根据事件来处理小圆的坐标
当超越区域时,对其进行限定。放手时回到中心


1.变量介绍

zoneR:背景区域大小,即限定摇杆的区域
handleR:摇杆大小 centerX,centerY摇杆中心偏移

private var zoneR: Float = 150f
private var handleR: Float = 80f
priate var centerX: Float = 0f
private var centerY: Float = 0f

2.绘制圆

绘制圆时,移动了一下画布,将画布左上角和中心重合

[1] 当函数返回值只要一行时,可以直接用 = 
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.translate(maxR(), maxR())
    paint.alpha = 100
    canvas.drawCircle(0f, 0f, zoneR, paint)
    paint.alpha = 150
    canvas.drawCircle(centerX, centerY, handleR, paint)
}

fun maxR():Float = zoneR + handleR

3.事件处理

核心就是处理好小圆的圆心在画布坐标系的位置。
分为在圆内和圆外两种情况:
触点在域内,根据触点位置确定摇杆圆心位置
触点在域外,摇杆圆心位置在域的边缘游走

在parser方法里,实现通过atan2获取夹角(图左)
然后转化为通常的坐标系(图左),由于再根据画布坐标系校正90°

[1] when 用于条件分支,相当于switch
override fun onTouchEvent(event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_UP -> reset()
        else -> parser(event)
    }
    invalidate()
    return true
}

private fun reset() {
    centerX = 0f
    centerY = 0f
}

private fun parser(event: MotionEvent) {
    centerX = event.x - maxR()
    centerY = event.y - maxR()
    var rad = atan2(centerX, centerY)
    if(centerX<0){
        rad += 2*PI.toFloat()
    }
    val thta = rad- PI.toFloat()/2  //旋转坐标系90度
    if (sqrt(centerX * centerX + centerY * centerY) > zoneR) {
        centerX =  zoneR * cos(thta)
        centerY = -zoneR * sin(thta)
    }
}

4.监听器回调

现在数据是在View内部的,需要将它们暴露出去,比如旋转的角度,位移百分比
Java中设置监听,还要啰嗦一堆弄接口,校验回调。
由于Kotlin函数也是类型,回调起来就so easy

[1] (rad:Float, offset:Float) -> Unit 代表一种函数类型
lateinit var onHandleListener:(rad:Float, offset:Float) -> Unit

private fun parser(event: MotionEvent) {
    //英雄所见...
    val len = sqrt(centerX * centerX + centerY * centerY)
    onHandleListener(rad,len/zoneR)
}

使用:

[1] Kotlin 的lambda 表达式形式如下
handleView.onHandleListener= {
    rad,offset ->
    Log.e("MainActivity","角度${rad*180/Math.PI},位移:${offset}")        
}

Flutter-Dart 篇

一、自定义组件

1. 继承自StatefulWidget

很明显,移动中需要改变状态,使用基础自继承自StatefulWidget

 [1] 类通过[class]关键字定义,类名[大驼峰]  
 [2] 继承通过 [extends] 指定父类
 [3] final 修饰不可变量
 [4] 构造函数通过 {this.XXX} 初始化变量,可通过具名传参
 [5] 函数只要一行时,通过 => 简写
class HandleWidget extends StatefulWidget {
  final double zoneR;
  final double handleR;

  HandleWidget(
      {this.zoneR = 60.0,
      this.handleR = 30.0});

  @override
  _HandleWidgetState createState() => _HandleWidgetState();
}


2.状态类中操作状态
[1] 覆写可使用 @override 注解
[2] get关键字XXX,可以像一样属性访问XXX
[3] 私有包/类/字段 通过_XXX指定
class _HandleWidgetState extends State<HandleWidget> {
  double zoneR;
  double handleR;
  double centerX=0.0;
  double centerY=0.0;
  
  @override
  void initState() {
    zoneR=widget.zoneR;
    handleR=widget.handleR;
    super.initState();
  }
  
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
          painter: _HandleView(zoneR: zoneR, handleR: handleR, centerX: centerX, centerY: centerY)
          )
    );
  }
  double get maxR => zoneR + handleR;
}

3 画板
[1] ..级联操作,相当于该对象

class _HandleView extends CustomPainter {
  var _paint = Paint();
  var zoneR;
  var handleR;
  var centerX;
  var centerY;
  _HandleView({this.zoneR, this.handleR, this.centerX, this.centerY}) {
    _paint
      ..color = Colors.blue
      ..style = PaintingStyle.fill
      ..isAntiAlias = true;
  }
  double get maxR => zoneR + handleR;
  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(maxR, maxR);
    _paint.color = _paint.color.withAlpha(100);
    canvas.drawCircle(Offset(0, 0), zoneR, _paint);
    _paint.color = _paint.color.withAlpha(150);
    canvas.drawCircle(Offset(centerX, centerY), handleR, _paint);
  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

二、事件处理

通过GestureDetector进行嵌套,即可让任意组件响应事件

@override
Widget build(BuildContext context) {
  return GestureDetector(
    onPanEnd: (d) => reset(),
    onPanUpdate: (d) => parser(d.localPosition),
    child: Container(
      width: MediaQuery.of(context).size.width,
      height: MediaQuery.of(context).size.height,
      child: CustomPaint(
        painter: _HandleView(
            zoneR: zoneR,
            handleR: handleR,
            centerX: centerX,
            centerY: centerY),
      ),
    ),
  );
}

2.处理逻辑

逻辑和上面是一毛一样的,所以,你悟到了什么?

reset() {
  centerX = 0;
  centerY = 0;
  setState(() {});
}

parser(Offset offset) {
  centerX = offset.dx - maxR;
  centerY = offset.dy - maxR;
  var rad = atan2(centerX, centerY);
  if (centerX < 0) {
    rad += 2 * pi;
  }
  var thta = rad - pi / 2; //旋转坐标系90度
  if (sqrt(centerX * centerX + centerY * centerY) > zoneR) {
    centerX = zoneR * cos(thta);
    centerY = -zoneR * sin(thta);
  }
  setState(() {});
}

3.监听器回调

[1] 调用函数对象 Function(double,double) onHandleListener; 
class HandleWidget extends StatefulWidget {
  final double zoneR;
  final double handleR;
  final Function(double,double) onHandleListener;

  HandleWidget({this.zoneR = 60.0, this.handleR = 30.0,this.onHandleListener});
  @override
  _HandleWidgetState createState() => _HandleWidgetState();
}
parser(Offset offset) {
  //英雄所见...
  var len = sqrt(centerX * centerX + centerY * centerY);
  if(widget.onHandleListener!=null) widget.onHandleListener(rad,len/zoneR);
  setState(() {});
}

两种实现的核心是什么?也就那一个解析的过程
如果过程都明白,你管它是Kotlin还是Flutter,就是js也可以在浏览器上画出来
你需要学的从不是使用框架/语言的能力,而是思维分析解决问题的能力
限制你的并非是框架/语言,而是你贫瘠的想象力、控制力、创造力
Flutter 可以用两天就能上手,Kotlin用一天就能了解语法
之后的是你的固有瓶颈,而非框架/语言的问题,知道做什么是最重要的
好了,如果你还在犹豫,你可以去研究[数据结构和算法分析]了,吐了再回来。


尾声

Kotlin和Dart的语法都非常简洁。Android本身的View体系比较臃肿,毕竟放在xml里,沟通起来需要费些劲
Flutter组件出来起来非常灵活,复用非常棒。最好的是属性可以很容易修改
Android里自定义View的属性挺麻烦,增加、删除、修改都费劲
Kotlin无可挑剔,除了移动端,Spring运用,还能玩js
Flutter也无可挑剔,UI写起来非常爽,可以处理数据,管理状态,六端同开,前途无量
还在犹豫Flutter移动开发的人,现在Flutter对于桌面的支持,已经远远超过了你的想象。


@张风捷特烈 2020.01.21 未允禁转
我的公众号:编程之王
联系我--邮箱:1981462002@qq.com --微信:zdl1994328
~ END ~