Flutter|頁面換膚實踐

本文介紹了一次換膚需求經(jīng)歷屎飘。

背景

產品想要在一些大型活動時在社區(qū)里換膚够滑,換換背景圖蛤高、背景色這些蚣旱,其實和之前適配多業(yè)務視覺風格有相似之處,當時也把通用資源整理歸類戴陡,直接同名同類型替換即可塞绿。

在這個場景下雖然可以無需開發(fā)通過熱更來替換資源,但之后每次活動上線都要開發(fā)配合發(fā)熱更包恤批,可能還要支持除圖以外的其他配置項异吻,在后期也會有隱形開發(fā)成本。所以最好前端提前預埋配置項,之后運營直接在管理端配置诀浪,可一勞永逸棋返。

方案

和產品梳理發(fā)現(xiàn)配置項高達40+個,時間原因先做一部分雷猪,如此在前期就要考慮好如何設計方便后續(xù)快速開發(fā)上線睛竣。

首先需要新增一個皮膚配置管理器SkinConfigMgr用來拉取和保存配置內容,由于配置拉取是異步的求摇,為了能及時刷新界面這里配置都是用ValueNotifier持有射沟,例如有這樣幾個配置項:

class SkinConfigMgr {
  static SkinConfigMgr? _instance;
  SkinConfigMgr._();
  static SkinConfigMgr get instance => _instance ??= SkinConfigMgr._();

  ValueNotifier<String> mainNavBgColor = ValueNotifier(""); // 首頁頂部導航欄背景色
  ValueNotifier<String> mainBgImg = ValueNotifier(""); // 首頁背景圖
  ValueNotifier<String> mainBgColor = ValueNotifier(""); // 首頁背景色

  // 初始化
  void initSkinConfigs(ClientThemeSetting settings) {
    mainNavBgColor.value = settings.mainNavBgColor;
    mainBgImg.value = settings.mainBgImg;
    mainBgColor.value = settings.mainBgColor;
  }
}

因為是全局配置,要在app啟動時就拉取請求月帝,然后調用初始化即可SkinConfigMgr.instance.initSkinConfigs(apiReply.themeSetting)躏惋,避免并發(fā)多次請求考慮和其他請求做合并。

然后需要適配圖片組件嚷辅,這里盡可能保持組件的通用性以及降低外部使用成本簿姨,兼容了各種默認圖樣式,外部只要多傳入一個圖片配置項和是否需要開啟換膚開關即可簸搞。換膚圖片組件SkinImageView 具體如下:

class SkinImageView extends StatelessWidget {
  final ValueNotifier<String> imgNotifier; // from SkinConfigMgr
  final String defImgUrl; // 原圖片路徑
  final double size; // 圖片尺寸扁位,這里填 width,若 height 和 width 不一致還需要給下 height
  final bool fromNetwork; // 是否網(wǎng)絡圖
  final double? height;
  final String? package;
  final BoxFit? fit;
  final IconData? icon;
  final bool supportSkinConfig;

  SkinImageView(
    this.imgNotifier,
    this.defImgUrl,
    this.size,
    this.fromNetwork, {
    this.height,
    this.package,
    this.fit,
    this.icon,
    this.supportSkinConfig = true,
  });

  Widget _getDefView() {
    if (defImgUrl.isEmpty) {
      return SizedBox.shrink();
    }
    // icon圖標
    if (icon != null) {
      return Icon(icon, size: size);
    }
    // 網(wǎng)絡圖
    if (fromNetwork) {
      return ImageView(
        url: defImgUrl,
        width: size,
        height: height ?? size,
        fit: fit,
      );
    }
    // 本地svg
    if (defImgUrl.toLowerCase().endsWith("svg")) {
      return SvgPicture.asset(
        defImgUrl,
        width: size,
        height: height ?? size,
        package: package,
        fit: fit ?? BoxFit.contain,
      );
    }
    // 本地其他圖
    return Image.asset(
      defImgUrl,
      width: size,
      height: height ?? size,
      package: package,
      fit: fit,
    );
  }

  @override
  Widget build(BuildContext context) {
    Widget defView = _getDefView();
    if (!supportSkinConfig) {
      return defView;
    }
    return ValueListenableBuilder<String>(
      valueListenable: imgNotifier,
      builder: (BuildContext context, String value, Widget? widget) {
        return value.isNotEmpty
            ? ImageView(
                url: value,
                width: size,
                height: height ?? size,
                fit: fit,
              )
            : defView;
      },
    );
  }
}

接著繼續(xù)適配顏色組件趁俊,由于原來大部分是通過Container組件實現(xiàn)顏色的域仇,這里還是參考這種方式,相當于在Container基礎上又包裝了一層寺擂,外部只要多傳入配置項和換膚開關即可暇务。換膚顏色組件SkinColorContainer 具體如下:

class SkinColorContainer extends StatelessWidget {
  final ValueNotifier<String> colorNotifier; // from SkinConfigMgr
  final Color? color; // 原顏色要放在 color 屬性而不是 decoration
  final AlignmentGeometry? alignment;
  final EdgeInsetsGeometry? padding;
  final Decoration? decoration;
  final Decoration? foregroundDecoration;
  final double? width;
  final double? height;
  final BoxConstraints? constraints;
  final EdgeInsetsGeometry? margin;
  final Matrix4? transform;
  final AlignmentGeometry? transformAlignment;
  final Widget? child;
  final Clip clipBehavior;
  final bool supportSkinConfig;

  SkinColorContainer(
    this.colorNotifier,
    this.color, {
    this.alignment,
    this.padding,
    this.decoration,
    this.foregroundDecoration,
    this.width,
    this.height,
    this.constraints,
    this.margin,
    this.transform,
    this.transformAlignment,
    this.child,
    this.clipBehavior = Clip.none,
    this.supportSkinConfig = true,
  });

  static Color? getColor(String colorStr, Color? defColor,
      {bool supportSkinConfig = true}) {
    if (!supportSkinConfig ||colorStr.isEmpty) {
      return defColor;
    }
    try {
      colorStr = colorStr.toLowerCase().replaceAll("#", "");
      if (colorStr.length == 6) {
        colorStr = "ff" + colorStr;
      }
      return Color(int.parse(colorStr, radix: 16));
    } catch (e) {
      return defColor;
    }
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<String>(
      valueListenable: colorNotifier,
      builder: (BuildContext context, String value, Widget? widget) {
        return Container(
          alignment: alignment,
          padding: padding,
          color: decoration != null || foregroundDecoration != null
              ? null
              : getColor(value, color, supportSkinConfig: supportSkinConfig),
          // decoration 和 color 不能同時設置,優(yōu)先用 decoration
          decoration: decoration,
          foregroundDecoration: foregroundDecoration,
          width: width,
          height: height,
          constraints: constraints,
          margin: margin,
          transform: transform,
          transformAlignment: transformAlignment,
          child: child,
          clipBehavior: clipBehavior,
        );
      },
    );
  }
}

可以看到實際替換代碼就非常簡單了:

總結

對于不同的換膚需求提供不同的方案怔软,在后面擴展時也能提供更多的選擇垦细。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挡逼,隨后出現(xiàn)的幾起案子括改,更是在濱河造成了極大的恐慌,老刑警劉巖家坎,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘱能,死亡現(xiàn)場離奇詭異,居然都是意外死亡虱疏,警方通過查閱死者的電腦和手機惹骂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來做瞪,“玉大人析苫,你說我怎么就攤上這事。” “怎么了衩侥?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵国旷,是天一觀的道長。 經(jīng)常有香客問我茫死,道長跪但,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任峦萎,我火速辦了婚禮屡久,結果婚禮上,老公的妹妹穿的比我還像新娘爱榔。我一直安慰自己被环,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布详幽。 她就那樣靜靜地躺著筛欢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唇聘。 梳的紋絲不亂的頭發(fā)上版姑,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音迟郎,去河邊找鬼剥险。 笑死,一個胖子當著我的面吹牛宪肖,可吹牛的內容都是我干的表制。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼控乾,長吁一口氣:“原來是場噩夢啊……” “哼么介!你這毒婦竟也來了?” 一聲冷哼從身側響起阱持,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎魔熏,沒想到半個月后衷咽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡蒜绽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年镶骗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躲雅。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡鼎姊,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情相寇,我是刑警寧澤慰于,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站唤衫,受9級特大地震影響婆赠,放射性物質發(fā)生泄漏。R本人自食惡果不足惜佳励,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一休里、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赃承,春花似錦妙黍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至筒繁,卻和暖如春噩凹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背毡咏。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工驮宴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呕缭。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓堵泽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恢总。 傳聞我的和親對象是個殘疾皇子迎罗,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內容