【Flutter 專題】109 圖解自定義 ACERadio 單選框

      Radio選框在平常應用中很常見,Flutter 提供的單選框與 Android 提供的略有不一樣,和尚簡單瞭解一下並對其進行部分擴展;
web


Radio

      Radio 單選框是在一組選項中,互斥的選擇單個選項;canvas

源碼分析

class Radio<Textends StatefulWidget {
  const Radio({
    Key key,
    @required this.value,       // 當前單選框設置的值
    @required this.groupValue,  // 當前單選框選定狀態的值
    @required this.onChanged,   // 選中回調
    this.activeColor,           // 選中狀態顏色
    this.focusColor,            // 獲取焦點時顏色
    this.hoverColor,            // 高亮時顏色
    this.materialTapTargetSize, // 點擊範圍最小大小
    this.focusNode,
    this.autofocus = false,
  })
}

      簡單分析源碼可得,Radio 是一個有狀態的 StatefulWidget 小組件;Radio 單選框自己不保持任何狀態,經過 onChanged 回調,來判斷當前 value 是否與 groupValue 選項組中對應的 item 是否一致,來判斷選中狀態;通常經過調用 State.setState() 更新單選按鈕的 groupValue 從而響應 onChanged 回調;微信

案例嘗試

onChanged

      Radio 單選框通常分爲三個狀態,分別爲未選中狀態、選中狀態和不可選中狀態;onChanged 爲單選框選中的回調,根據 valuegroupValue 匹配是否爲選中狀態;當 onChangednull 時,單選框爲不可選中狀態;源碼分析

return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
  Row(children: <Widget>[
    Radio(value: GenderType.MALE, groupValue: _groupValue,
        onChanged: (val) { print('---onChanged---$val');
          setState(() => _groupValue = val);
        }),
    Text('男')
  ]),
  Row(children: <Widget>[
    Radio(value: GenderType.FEMALE, groupValue: _groupValue,
        onChanged: (val) { print('---onChanged---$val');
          setState(() => _groupValue = val);
        }),
    Text('女')
  ]),
  Row(children: <Widget>[
    Radio(value: GenderType.FEMALE, groupValue: _groupValue, onChanged: null),
    Text('不可選中')
  ])
]);

materialTapTargetSize

      materialTapTargetSize 爲默認 Radio 可選中點擊的最小範圍;主要分爲 paddedshrinkWrap 兩種狀態,分析源碼能夠看到二者尺寸相差 8.0,所以 Radio 所在的範圍是不可變動的,這也是和尚準備自定義 ACERadio 擴展方向之一;ui

switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
  case MaterialTapTargetSize.padded:
    size = const Size(2 * kRadialReactionRadius + 8.02 * kRadialReactionRadius + 8.0);
    break;
  case MaterialTapTargetSize.shrinkWrap:
    size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius);
    break;
}
return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
  Row(children: <Widget>[
    Text('padded'),
    Container( color: Colors.grey.withOpacity(0.4),
        child: Radio( value: GenderType.MALE, groupValue: _groupValue,
            materialTapTargetSize: MaterialTapTargetSize.padded,
            onChanged: (val) { print('---onChanged---$val');
              setState(() => _groupValue = val);
            })),
  ]),
  SizedBox(width: 10),
  Row(children: <Widget>[
    Container( color: Colors.grey.withOpacity(0.4),
        child: Radio( value: GenderType.FEMALE, groupValue: _groupValue,
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
            onChanged: (val) { print('---onChanged---$val');
              setState(() => _groupValue = val);
            })),
    Text('shrinkWrap')
  ])
]);

activeColor

      activeColor 爲單選框選中狀態時繪製的顏色;若未設置,默認爲 ThemeData.toggleableActiveColor 對應顏色;this

return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
  Row(children: <Widget>[
    Radio( value: GenderType.MALE, groupValue: _groupValue,
        activeColor: Colors.green,
        onChanged: (val) { print('---onChanged---$val');
          setState(() => _groupValue = val);
        }),
    Text('男', style: TextStyle( color: _groupValue == GenderType.MALE ? Colors.green : Colors.black))
  ]),
  Row(children: <Widget>[
    Radio( value: GenderType.FEMALE, groupValue: _groupValue,
        activeColor: Colors.red,
        onChanged: (val) { print('---onChanged---$val');
          setState(() => _groupValue = val);
        }),
    Text('女', style: TextStyle( color: _groupValue == GenderType.FEMALE ? Colors.red : Colors.black))
  ])
]);

focusColor & hoverColor

      focusColor / hoverColor 分別對應獲取焦點時的顏色與點擊高亮顏色;但和尚嘗試了屢次效果並不明顯,因需求場景較少,暫不作處理;spa

未選中顏色 & 不可選顏色

      Radio 並未提供未選中狀態和不可選中狀態按鈕顏色;和尚分析源碼,發現 未選中狀態ThemeData.unselectedWidgetColor 顏色對應,不可選中狀態ThemeData.disabledColor 對應;若須要動態修改這兩種顏色狀態,能夠在對應的 Radio 外設置 ThemeData 對應的顏色狀態便可;.net

return Theme(
    data: ThemeData(unselectedWidgetColor: Colors.deepPurple, disabledColor: Colors.brown),
    child:
        Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
      Row(children: <Widget>[
        Radio( value: GenderType.MALE, groupValue: _groupValue,
            activeColor: Colors.green,
            onChanged: (val) { print('---onChanged---$val');
              setState(() => _groupValue = val);
            }),
        Text('男', style: TextStyle( color: _groupValue == GenderType.MALE ? Colors.green : Colors.black))
      ]),
      Row(children: <Widget>[
        Radio( value: GenderType.FEMALE, groupValue: _groupValue,
            activeColor: Colors.red,
            onChanged: (val) { print('---onChanged---$val');
              setState(() => _groupValue = val);
            }),
        Text('女', style: TextStyle( color: _groupValue == GenderType.FEMALE ? Colors.red : Colors.black))
      ]),
      Row(children: <Widget>[
        Radio( value: GenderType.FEMALE, groupValue: _groupValue, onChanged: null),
        Text('不可選中')
      ])
    ]));

ACERadio

      爲了更靈活的應用 Radio 單選框,和尚準備在此基礎上擴展以下幾個方面:3d

  • 動態設置 未選中狀態顏色code

  • 動態設置 不可選中狀態顏色

  • 動態設置 選中框按鈕尺寸

  • 添加狀態 取消按鈕外邊距

源碼擴展

      和尚自定義了三種 ACEMaterialTapTargetSize 尺寸,增長了 zero 類型取消按鈕外邊距;

enum ACEMaterialTapTargetSize { padded, shrinkWrap, zero }

double radius = widget.radioSize ?? kRadialReactionRadius;
switch (widget.materialTapTargetSize ?? ACEMaterialTapTargetSize.padded) {
  case ACEMaterialTapTargetSize.padded:
    size = Size(2 * radius + 8.02 * radius + 8.0);
    break;
  case ACEMaterialTapTargetSize.shrinkWrap:
    size = Size(2 * radius, 2 * radius);
    break;
  case ACEMaterialTapTargetSize.zero:
    size = Size(radius, radius);
    break;
}

      和尚優先判斷添加的未選中狀態顏色和不可選中狀態顏色;若未設置以 ThemeData 爲準;

Color _getInactiveColor(ThemeData themeData) {
  return enabled ? widget.unCheckedColor ?? themeData.unselectedWidgetColor
      : widget.disabledColor ?? themeData.disabledColor;
}

      和尚添加一個 radioSize 屬性,在繪製按鈕時,按比例動態繪製按鈕尺寸;

// Outer circle
final Paint paint = Paint()
  ..color = Color.lerp(inactiveColor, radioColor, position.value)..style = PaintingStyle.stroke
  ..strokeWidth = radioSize / 4 ?? 2.0;
canvas.drawCircle(center, radioSize ?? _kOuterRadius, paint);

// Inner circle
if (!position.isDismissed) {
  paint.style = PaintingStyle.fill;
  canvas.drawCircle(center,
      (radioSize != null ? radioSize * 4.5 / 8 : _kInnerRadius) * position.value, paint);
}

案例嘗試

取消按鈕外邊距

      Radio 默認提供了兩種最小可點擊範圍,但和尚想取消按鈕總體外邊距,因而添加一種 ACEMaterialTapTargetSize.zero 方式來僅設置按鈕尺寸;

return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
  Row(children: <Widget>[
    Text('padded'),
    Container(color: Colors.grey.withOpacity(0.4),
        child: ACERadio(value: GenderType.MALE, groupValue: _groupValue,
            materialTapTargetSize: ACEMaterialTapTargetSize.padded,
            onChanged: (val) => setState(() => _groupValue = val))),
  ]),
  Row(children: <Widget>[
    Container(color: Colors.grey.withOpacity(0.4),
        child: ACERadio(value: GenderType.FEMALE, groupValue: _groupValue,
            materialTapTargetSize: ACEMaterialTapTargetSize.shrinkWrap,
            onChanged: (val) => setState(() => _groupValue = val))),
    Text('shrinkWrap')
  ]),
  Row(children: <Widget>[
    Container(color: Colors.grey.withOpacity(0.4),
        child: ACERadio(value: GenderType.FEMALE, groupValue: _groupValue,
            materialTapTargetSize: ACEMaterialTapTargetSize.zero,
            onChanged: null)),
    Text('zero')
  ])
]);

未選中狀態 & 不可選中狀態

      未選中狀態 & 不可選中狀態 能夠經過 ThemeData 來動態修改,和尚爲了方便,添加了 unCheckedColor & disabledColor 可直接進行設置;

return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
  Row(children: <Widget>[
    ACERadio(
        value: GenderType.MALE, groupValue: _groupValue,
        activeColor: Colors.green, unCheckedColor: Colors.deepPurple,
        onChanged: (val) { print('---onChanged---$val');
          setState(() => _groupValue = val);
        }),
    Text('男', style: TextStyle( color: _groupValue == GenderType.MALE ? Colors.green : Colors.black))
  ]),
  Row(children: <Widget>[
    ACERadio(
        value: GenderType.FEMALE, groupValue: _groupValue,
        activeColor: Colors.red, unCheckedColor: Colors.deepPurple,
        onChanged: (val) { print('---onChanged---$val');
          setState(() => _groupValue = val);
        }),
    Text('女', style: TextStyle( color: _groupValue == GenderType.FEMALE ? Colors.red : Colors.black))
  ]),
  Row(children: <Widget>[
    ACERadio(
        value: GenderType.FEMALE, groupValue: _groupValue,
        disabledColor: Colors.brown, unCheckedColor: Colors.deepPurple,
        onChanged: null),
    Text('不可選中')
  ])
]);

選中框按鈕尺寸

      Radio 單選框尺寸是固定的,和尚爲了更方便的修改,添加了 radioSize 尺寸來動態修改按鈕尺寸,且在動態設置按鈕尺寸以後依舊支持最小點擊範圍的三種樣式;

return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
  Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
    ACERadio(radioSize: 6.0, value: SizeType.SIZE_6, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
    Text('Size:6.0*6.0')
  ]),
  Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
    ACERadio(radioSize: 8.0, value: SizeType.SIZE_8, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
    Text('Size:8.0*8.0')
  ]),
  Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
    ACERadio(radioSize: 10.0, value: SizeType.SIZE_10, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
    Text('Size:10.0*10.0')
  ]),
  Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
    ACERadio(radioSize: 14.0, value: SizeType.SIZE_14, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
    Text('Size:14.0*14.0')
  ]),
  Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
    ACERadio(radioSize: 18.0, value: SizeType.SIZE_18, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
    Text('Size:18.0*18.0')
  ]),
]);


      ACERadio 案例源碼


      和尚對底層源碼還不夠深刻,只是對 Radio 單選框的一點小擴展;若有錯誤,請多多指導!

來源:阿策小和尚

本文分享自微信公衆號 - 阿策小和尚(gh_8297e718c166)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索