1. 程式人生 > >flutter - 點選事件(一) - 自定義一個方便的點選控制元件

flutter - 點選事件(一) - 自定義一個方便的點選控制元件

android中,所有View都可以直接setOnClickListener, RN中也有TouchableHightlight這樣的控制元件可以直接套在外面,ios中也可以有UIControl 這樣的控制元件可以直接新增點選事件.

那麼flutter中有嗎? 答案自然是有. GestureDetector,InkResponse,InkWell, 包括一些琳琅滿目的按鈕,比如FlatButton,MaterialButton,CupertinoButton,IconButton,ImageButton 這些元件都可以達到目的. 那麼自定義的目的是什麼呢?

自定義的優點

最重要的自然就是可控性強,複用性強. 一次修改終身受用.
來看下面的這段程式碼

import 'package:flutter/material.dart';

class MaterialTapWidget extends StatelessWidget {
  final double radius;
  final Function onTap;
  final Widget child;
  final double elevation;
  final Color backgroundColor;
  final Color splashColor;
  final Function onLongTap;

  const MaterialTapWidget
({ Key key, this.radius = 0.0, this.onTap, this.onLongTap, @required this.child, this.splashColor, this.elevation = 0.0, this.backgroundColor = Colors.transparent, }) : super(key: key); @override Widget build(BuildContext context) { Widget w = ClipRRect( borderRadius:
BorderRadius.circular(radius), child: Material( borderRadius: BorderRadius.circular(radius), color: backgroundColor, elevation: 0.0, child: InkWell( child: child, onTap: onTap, onLongPress: onLongTap, ), ), ); if (this.splashColor != null) { return Theme( data: Theme.of(context).copyWith(splashColor: this.splashColor), child: w, ); } return w; } }

一共有下面幾個屬性

  final double radius; //圓角
  final Function onTap; //點選回撥
  final Widget child; // 內部的控制元件
  final double elevation; //陰影"高度"
  final Color backgroundColor; //背景顏色
  final Color splashColor; // 點選的水波紋顏色 
  final Function onLongTap;  //長按回調

這個在日常開發中可以滿足我的需求了,但是有一天我還需要單獨設定其他的呢 比如我需要新增雙擊事件,那麼我只需要修改幾處地方

class MaterialTapWidget extends StatelessWidget {
  final double radius;
  final Function onTap;
  final Widget child;
  final double elevation;
  final Color backgroundColor;
  final Color splashColor;
  final Function onLongTap;
  final Function onDoubleTap;  //新增欄位

  const MaterialTapWidget({
    Key key,
    this.radius = 0.0,
    this.onTap,
    this.onLongTap,
    @required this.child,
    this.splashColor,
    this.elevation = 0.0,
    this.backgroundColor = Colors.transparent,
    this.onDoubleTap, //新增構造方法
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    Widget w = ClipRRect(
      borderRadius: BorderRadius.circular(radius),
      child: Material(
        borderRadius: BorderRadius.circular(radius),
        color: backgroundColor,
        elevation: 0.0,
        child: InkWell(
          child: child,
          onTap: onTap,
          onDoubleTap: onDoubleTap, //新增控制元件回撥
          onLongPress: onLongTap,
        ),
      ),
    );

    if (this.splashColor != null) {
      return Theme(
        data: Theme.of(context).copyWith(splashColor: this.splashColor),
        child: w,
      );
    }

    return w;
  }
}

這樣就完成了雙擊的支援, 同樣的,如果有別的需求也可以往這裡放

比如我們有了特殊需求,希望如果裝置是ios裝置,則不使用Material風格,而使用一個點選背景變色的風格

在整體專案是使用MaterialApp的情況下,可以像下面這樣寫

import 'package:flutter/material.dart';

class PlatformTapWidget extends StatefulWidget {
  final double radius;
  final Function onTap;
  final Widget child;
  final double elevation;
  final Color backgroundColor;
  final Color splashColor;
  final Function onLongTap;

  const PlatformTapWidget({
    Key key,
    this.radius = 0.0,
    this.onTap,
    this.elevation,
    this.backgroundColor = Colors.white,
    this.splashColor,
    this.onLongTap,
    this.child,
  }) : super(key: key);

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

class _PlatformTapWidgetState extends State<PlatformTapWidget> {
  bool isDown = false;

  @override
  Widget build(BuildContext context) {
    Color splashColor = widget.splashColor ?? Colors.grey.withOpacity(0.3);

    if (Theme.of(context).platform == TargetPlatform.iOS) {
      Widget w;

      w = ClipRRect(
        borderRadius: BorderRadius.circular(widget.radius),
        child: GestureDetector(
          behavior: HitTestBehavior.translucent,
          onTap: widget.onTap,
          onTapDown: (d) => setState(() => this.isDown = true),
          onTapUp: (d) => setState(() => this.isDown = false),
          onTapCancel: () => setState(() => this.isDown = false),
          child: AnimatedContainer(
            duration: Duration(milliseconds: 600),
            curve: Curves.easeIn,
            color: isDown ? splashColor : widget.backgroundColor,
            child: widget.child,
          ),
        ),
      );

      return w;
    }

    Widget w = ClipRRect(
      borderRadius: BorderRadius.circular(widget.radius),
      child: Material(
        borderRadius: BorderRadius.circular(widget.radius),
        color: widget.backgroundColor,
        elevation: 0.0,
        child: InkWell(
          child: widget.child,
          onTap: widget.onTap,
          onLongPress: widget.onLongTap,
        ),
      ),
    );

    if (widget.splashColor != null) {
      return Theme(
        data: Theme.of(context).copyWith(splashColor: widget.splashColor),
        child: w,
      );
    }

    return w;
  }
}

這樣就可以達到ios裝置和android裝置不同的方法

而這個也很符合flutter 的設計理念, 組合優於繼承 ,使用flutter自帶的元件 通過組合的方式構建出自己的元件


flutter中可以有很多這樣的組合方式

比如我專案中有大量左圖片,右文字的按鈕,並且按鈕的圖片大小是固定的,字型大小也固定,並且附帶圓角
那麼這種情況下可以自己封裝一個控制元件

import 'package:flutter/material.dart';
import 'package:platform_widget_demo/widgets/platform_tap_widget.dart';

class IconTextButton extends StatelessWidget {
  final IconData icon;
  final String text;
  final Function onTap;

  const IconTextButton({
    Key key,
    this.icon,
    this.text,
    this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PlatformTapWidget(
      onTap: onTap,
      child: Row(
        children: <Widget>[
          Icon(icon),
          Text(text),
        ],
      ),
    );
  }
}
 IconTextButton(
   icon: Icons.scanner,
   text: "掃描",
 ),