主頁 > 移動端開發 > Flutter 專案實戰(Dio+MVP+FutureBuilder )五

Flutter 專案實戰(Dio+MVP+FutureBuilder )五

2022-01-02 08:49:07 移動端開發

/ 沒有感情萬千 、只有默默無聞 /

2022年跨年了,又漲了一歲,隨著時光的流逝,作業多年的我還是在堅持些代碼,互聯網都有所謂的大齡危機,我對此毫無畏懼,不要因為社會存在一些大齡危機的恐慌,產生了很多心理上的負擔 ,我雖然不再年少輕狂,但激情依舊,

你需要懂的法則就是 : 適者生存,優勝劣汰 ,

你朝思暮想的結果就是 : 冰凍三尺,非一日之寒 ,

你想太多的結果就是 : 還是在原點 , 人老了 , 留下的不是遺憾就是回憶 ,

................

/ 異步 UI 更新FutureBuilder /

當我們打開app在網路暢通的情況下從服務器獲取資料 , 同時獲取資料需要時間 ,這時我們需要一個可以體現資料請求程序的進度條 , 獲取到資料后渲染頁面 ,

FutureBuilder 是繼承自 StatefulWidget builder必須要有回傳值 , 不能為空

 const FutureBuilder({
    Key? key,
    this.future,
    this.initialData,
    required this.builder,
  }) : assert(builder != null),
       super(key: key);

final Future<T>? future;

/// The asynchronous computation to which this builder is currently connected,
  /// possibly null.
  ///
  /// If no future has yet completed, including in the case where [future] is
  /// null, the data provided to the [builder] will be set to [initialData].

builder 與異步請求建立的連接 (connected) 可能為空 (即future引數可能為空) ,如果異步請求沒有完成 , 包括future引數為空 ,那么就提供默認資料(initialData) 給 builder使用 ,

當future 不為空時

class _MyHomePageState extends State<MyHomePage> {
  var _futBuiHom;

  void _handleGetNetData() async {
    await _getNetData();
    setState(() {});
  }

  Future<String> _getNetData() async {
    return await Future.delayed(Duration(seconds: 2), () => "從互聯網上獲取的資料111111");
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _futBuiHom = _getNetData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<dynamic>(
          future: _futBuiHom,
          initialData: '初始化的資料',
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
            print('\n獲取到到資料: State${snapshot.connectionState} \n data${snapshot.data}');
            switch (snapshot.connectionState) {
              case ConnectionState.none:
              case ConnectionState.waiting:
                return const Text('資料加載中.....'); //加載中
              default: //如果_calculation執行完畢
                if (snapshot.hasError) {
                  //若_calculation執行出現例外
                  return Text('Error: ${snapshot.hasError}');
                } else {
                  //若_calculation執行正常完成
                  return Text('Error: ${snapshot.data}');
                }
            }
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _handleGetNetData,
        tooltip: '模擬從網路獲取資料',
        child: Icon(Icons.add),
      ),
    );
  }
}

當Future 為空時

final AsyncWidgetBuilder<T> builder;

  /// The build strategy currently used by this builder.
  ///
  /// The builder is provided with an [AsyncSnapshot] object whose
  /// [AsyncSnapshot.connectionState] property will be one of the following
  /// values:
  ///
  ///  * [ConnectionState.none]: [future] is null. The [AsyncSnapshot.data] will
  ///    be set to [initialData], unless a future has previously completed, in
  ///    which case the previous result persists.
  ///
  ///  * [ConnectionState.waiting]: [future] is not null, but has not yet
  ///    completed. The [AsyncSnapshot.data] will be set to [initialData],
  ///    unless a future has previously completed, in which case the previous
  ///    result persists.
  ///
  ///  * [ConnectionState.done]: [future] is not null, and has completed. If the
  ///    future completed successfully, the [AsyncSnapshot.data] will be set to
  ///    the value to which the future completed. If it completed with an error,
  ///    [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be
  ///    set to the error object.
  ///
  /// This builder must only return a widget and should not have any side
  /// effects as it may be called multiple times.

builder提供了一個AsyncSnapshot 物件 , 該物件可以獲取異步請求的狀態 ,

必須回傳一個Widget , 因為builder 函式會被呼叫多次 ,

awaiting狀態下顯示異步請求進度條 ,

done 狀態下 , 如果hasError 為false , 就重新渲染Widget ,

final T? initialData

/// The data that will be used to create the snapshots provided until a
  /// non-null [future] has completed.
  ///
  /// If the future completes with an error, the data in the [AsyncSnapshot]
  /// provided to the [builder] will become null, regardless of [initialData].
  /// (The error itself will be available in [AsyncSnapshot.error], and
  /// [AsyncSnapshot.hasError] will be true.)

FutureBuilder 初始化資料 . 當future引數不為空 (存在異步請求時) , 異步請求完成后 builder 函式將會被回呼 .

/ FutureBuilder 原始碼講解/

開始訂閱 (異步獲取資料)

  void _subscribe() {
    if (widget.future != null) {
      final Object callbackIdentity = Object();
      _activeCallbackIdentity = callbackIdentity;
      widget.future!.then<void>((T data) {
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
            _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
          });
        }
      }, one rror: (Object error, StackTrace stackTrace) {
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
            _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
          });
        }
      });
      _snapshot = _snapshot.inState(ConnectionState.waiting);
    }
  }

當父視圖呼叫 setState 函式時,子視圖的 didUpdateWidget 函式被呼叫 . 如果 future 的值直接參考獲取資料的異步函式, 那么builder 對應的函式要被呼叫多次 .

class _MyHomePageState extends State<MyHomePage> {
  var _futBuiHom;

  void _handleGetNetData() async {
    await _getNetData();
    setState(() {});
  }

  Future<String> _getNetData() async {
    return await Future.delayed(Duration(seconds: 2), () => "從互聯網上獲取的資料111111");
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _futBuiHom = _getNetData();
  }

  @override
  void didUpdateWidget(covariant MyHomePage oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    print('didUpdateWidget');
  }

  @override
  Widget build(BuildContext context) {

    print('build(BuildContext context)');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<dynamic>(
          future: _getNetData(),
          initialData: '初始化的資料',
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
            print('\n獲取到到資料: State${snapshot.connectionState} \n data${snapshot.data}');
            switch (snapshot.connectionState) {
              case ConnectionState.none:
              case ConnectionState.waiting:
                return const Text('資料加載中.....'); //加載中
              default: //如果_calculation執行完畢
                if (snapshot.hasError) {
                  //若_calculation執行出現例外
                  return Text('Error: ${snapshot.hasError}');
                } else {
                  //若_calculation執行正常完成
                  return Text('Error: ${snapshot.data}');
                }
            }
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _handleGetNetData,
        tooltip: '模擬從網路獲取資料',
        child: Icon(Icons.add),
      ),
    );
  }
}

在FutureBuilder 狀態管理類 _FutureBuilderState 的 didUpdateWidget 列印 當前future和上一次的future實體是否一樣 .

結果顯示不一樣

所以為了讓每次異步請求的實體是同一個 future ,需要在Widget 創建時在initState 函式里面初始化異步請求的future , 每次FutureBuilder 重新構建時都從原來的future異步請求中獲取資料.

 var _futBuiHom;

  Future<String> _getNetData() async {
    return await Future.delayed(Duration(seconds: 2), () => "從互聯網上獲取的資料111111");
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _futBuiHom = _getNetData();
  }

每次呼叫setState重繪界面

 void _handleGetNetData() async {
    await _getNetData();
    setState(() {});
  }

/ FutureBuilder + Dio +MVP 實作網路請求 /

Flutter 專案實戰 Dio網路請求 四

import 'dart:collection';
import 'dart:core';

import 'package:connectivity/connectivity.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

import 'http_error.dart';

///http請求成功回呼
typedef HttpSuccessCallback<T> = void Function(dynamic data);

///失敗回呼
typedef HttpFailureCallback = void Function(HttpError data);

///資料決議回呼
typedef T JsonParse<T>(dynamic data);

class HttpManager {
  ///同一個CancelToken可以用于多個請求,當一個CancelToken取消時,
  ///所有使用該CancelToken的請求都會被取消,一個頁面對應一個CancelToken,
  Map<String, CancelToken> _cancelTokens = Map<String, CancelToken>();

  ///超時時間
  static const int CONNECT_TIMEOUT = 30000;
  static const int RECEIVE_TIMEOUT = 30000;

  /// http request methods
  static const String GET = 'get';
  static const String POST = 'post';

  Dio? _client;

  static final HttpManager _instance = HttpManager._internal();

  factory HttpManager() => _instance;

  Dio get client => _client!;

  /// 創建 dio 實體物件
  HttpManager._internal() {
    if (_client == null) {
      /// 全域屬性:請求前綴、連接超時時間、回應超時時間
      BaseOptions options = BaseOptions(
        connectTimeout: CONNECT_TIMEOUT,
        receiveTimeout: RECEIVE_TIMEOUT,
      );
      _client = Dio(options);
    }
  }

  ///初始化公共屬性
  /// [baseUrl] 地址前綴
  /// [connectTimeout] 連接超時趕時間
  /// [receiveTimeout] 接收超時趕時間
  /// [interceptors] 基礎攔截器
  void init(
      {String? baseUrl,
      int? connectTimeout,
      int? receiveTimeout,
      List<Interceptor>? interceptors}) {
    _client!.options = _client!.options.copyWith(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
      receiveTimeout: receiveTimeout,
    );
    if (interceptors != null && interceptors.isNotEmpty) {
      _client!.interceptors..addAll(interceptors);
    }
  }

  ///Get網路請求
  ///[url] 網路請求地址不包含域名
  ///[params] url請求引數支持restful
  ///[options] 請求配置
  ///[successCallback] 請求成功回呼
  ///[errorCallback] 請求失敗回呼
  ///[tag] 請求統一標識,用于取消網路請求
  get({
    String? url,
    Map<String, dynamic>? params,
    Options? options,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
    String? tag,
  }) async {
    return _request(
      url: url,
      params: params,
      method: GET,
      options: options,
      successCallback: successCallback!,
      errorCallback: errorCallback!,
      tag: tag!,
    );
  }

  ///post網路請求
  ///[url] 網路請求地址不包含域名
  ///[data] post 請求引數
  ///[params] url請求引數支持restful
  ///[options] 請求配置
  ///[successCallback] 請求成功回呼
  ///[errorCallback] 請求失敗回呼
  ///[tag] 請求統一標識,用于取消網路請求
  post({
    String? url,
    data,
    Map<String, dynamic>? params,
    Options? options,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
    @required String? tag,
  }) async {
    return _request(
      url: url!,
      data: data,
      method: POST,
      params: params!,
      options: options!,
      successCallback: successCallback!,
      errorCallback: errorCallback!,
      tag: tag!,
    );
  }

  ///統一網路請求
  ///[url] 網路請求地址不包含域名
  ///[data] post 請求引數
  ///[params] url請求引數支持restful
  ///[options] 請求配置
  ///[successCallback] 請求成功回呼
  ///[errorCallback] 請求失敗回呼
  ///[tag] 請求統一標識,用于取消網路請求
  _request({
    String? url,
    String? method,
    data,
    Map<String, dynamic>? params,
    Options? options,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
    @required String? tag,
  }) async {
    //檢查網路是否連接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      if (errorCallback != null) {
        errorCallback(HttpError(HttpError.NETWORK_ERROR, "網路例外,請稍后重試!"));
      }
      return;
    }
    //設定默認值
    params = params ?? {};
    method = method ?? 'GET';
    options?.method = method;
    options = options ??
        Options(
          method: method,
        );

    ///請求頭
    options.headers = await _headers();
    try {
      CancelToken cancelToken;
      cancelToken =
          (_cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag])!;
      _cancelTokens[tag!] = cancelToken;
      Response response = await _client!.request(url!,
          data: data,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken);
      var _responseData = response.data;
      print('相應資料:$_responseData');
      return _responseData;
      /*int statusCode = _responseData["code"];
      if (statusCode == 200) {
        //成功
        successCallback!(_responseData["data"]);
      } else {
        //失敗
        String message = _responseData["msg"].toString();
        errorCallback!(HttpError('$statusCode', message));
      }*/
    } on DioError catch (e, s) {
      if (e.type != DioErrorType.cancel) {
        errorCallback!(HttpError.dioError(e));
      }
    } catch (e, s) {
      errorCallback!(HttpError(HttpError.UNKNOWN, "未知錯誤,請稍后重試!"));
    }
  }

  ///取消網路請求
  void cancel(String tag) {
    if (_cancelTokens.containsKey(tag)) {
      if (!_cancelTokens[tag]!.isCancelled) {
        _cancelTokens[tag]!.cancel();
      }
      _cancelTokens.remove(tag);
    }
  }

  ///請求頭
  Future<Map<String, String>> _headers() async {
    Map<String, String> _headers = new HashMap();
    String _token = '';
    _headers.addAll({"token": _token});
    return _headers;
  }
}

MVP 模式 - 模型層 - 基礎類

創建IModel

import 'package:flutter_dio_mvp_futurebuilder/base/http/http_error.dart';

abstract class IModel {
  ///釋放網路請求
  void dispose();
}

typedef SuccessCallback<T> = void Function(dynamic data);

typedef FailureCallback = void Function(HttpError error);

AbstractModel

import 'IModel.dart';
abstract class AbstractModel implements IModel {
  String? _tag;

  String? get tag => _tag;

  AbstractModel() {
    _tag = '${DateTime.now().millisecondsSinceEpoch}';
  }

}

MVP模式 - Presenter基礎類

IPresenter

import 'package:flutter_dio_mvp_futurebuilder/base/view/IView.dart';

abstract class IPresenter<V extends IView> {

  void attachView(V view);


  void detachView();
}

AbstractPresenter

import 'package:flutter_dio_mvp_futurebuilder/base/model/IModel.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/view/IView.dart';

import 'IPresenter.dart';

abstract class AbstractPresenter<V extends IView , M extends IModel >
    implements IPresenter {
  M? _model;
  V? _view;

  @override
  void attachView(IView  view) {
    this._model = createModel() as M?;
    this._view = view as V?;
  }

  @override
  void detachView() {
    if (_view != null) {
      _view = null;
    }
    if (_model != null) {
      _model!.dispose();
      _model = null;
    }
  }

  V? get view {
    return _view;
  }

//  V get view => _view;

  M? get model => _model;

  IModel  createModel();

}

IView

class IView {
  ///開始加載
  void startLoading() {}

  ///加載成功
  void showLoadSuccess() {}

  ///加載失敗
  void showLoadFailure(String code, String message) {}

}

BaseView

import 'package:flutter/material.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/presenter/IPresenter.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/view/IView.dart';

abstract class BaseView extends StatefulWidget {
  BaseView({Key? key}) : super(key: key);

  @override
  BaseViewState createState() => getState();

  ///子類實作
  BaseViewState getState();
}

abstract class BaseViewState<P extends IPresenter, V extends BaseView>
    extends State<V> with IView {
  P? presenter;

  @override
  void initState() {
    super.initState();

    presenter = createPresenter();
    if (presenter != null) {
      presenter?.attachView(this);
    }
  }

  P? createPresenter() {}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ///導航欄
      appBar: buildAppBar(),

      ///內容區域
      body: buildWidget(),

      ///內容區域背景顏色
      backgroundColor: buildBodyColor(),
    );
  }

  buildWidget();

  buildAppBar() => null;

  Color buildBodyColor() {
    return Color(0xff00FFFFFF);
  }
}

創建用于業務邏輯處理的callback

import 'package:flutter_dio_mvp_futurebuilder/base/model/IModel.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/presenter/IPresenter.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/view/IView.dart';

abstract class CDataModel extends IModel {
  ///
  loadData(Map<String, dynamic> p, SuccessCallback s, FailureCallback f);
}

abstract class CDataPresenter extends IPresenter {
  ///
  loadData(Map<String, dynamic> p);
}

abstract class CDataView extends IView {
  ///
  loadData(d);
}

模型層(Model)、Presenter、視圖層(View) 的具體實作

模型層(Model) 實作

import 'package:flutter_dio_mvp_futurebuilder/base/http/http_manager.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/model/AbstractModel.dart';
import 'package:flutter_dio_mvp_futurebuilder/busmer/mvp_callback.dart';

class MData extends AbstractModel implements CDataModel {
  @override
  void dispose() {
    // TODO: implement dispose
    HttpManager().cancel(tag!);
  }

  @override
  loadData(Map<String, dynamic> p, s, f) async{
    return HttpManager().get(
        ///網路請求地址
        url: '/f',
        tag: tag!,
        successCallback: (data) {
          s(data);
        },
        errorCallback: (data) {
          f(data);
        },
        params: p);
  }
}

Presenter 實作

import 'package:flutter_dio_mvp_futurebuilder/base/model/IModel.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/presenter/AbstractPresenter.dart';
import 'package:flutter_dio_mvp_futurebuilder/busmer/mvp_callback.dart';
import 'package:flutter_dio_mvp_futurebuilder/model/m_data.dart';

class PData extends AbstractPresenter<CDataView, CDataModel>
    implements CDataPresenter {
  @override
  IModel createModel() {
    return MData();
  }

  @override
  loadData(Map<String, dynamic> p) async{
    // TODO: implement loadData
    view?.startLoading();
   return model?.loadData(p, (data) {
      view?.showLoadSuccess();
      view?.loadData(data);
      model?.dispose();
    }, (error) {
      view?.showLoadFailure(error.code!, error.message!);
    });

  }
}

視圖層(View) 實作

class MyHomePage extends BaseView {
  @override
  BaseViewState<IPresenter<IView>, BaseView> getState() {
    // TODO: implement getState
    return _MyHomePageState();
  }
}

class _MyHomePageState extends BaseViewState<PData, MyHomePage>
    implements CDataView {
  var _futBuiHom;

  void _handleGetNetData() async {
    await _getNetData();
    setState(() {});
  }

  _getNetData() async {
    return await presenter!
        .loadData({'ie': 'utf-8', 'kw': '大佬', 'fr': 'search'});
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _futBuiHom = _getNetData();
  }

  @override
  loadData(d) {}

  @override
  PData? createPresenter() {
    // TODO: implement createPresenter
    return PData();
  }

  @override
  Color buildBodyColor() {
    // TODO: implement buildBodyColor
    return Color(0xffFFFFFF);
  }

  @override
  buildWidget() {
    // TODO: implement buildWidget
    print('build(BuildContext context)');

    return Column(
      children: [
        AppBar(
          title: Text('FutureBuilder MVP Dio'),
        ),
        OutlinedButton(
          onPressed: _handleGetNetData,
          child: Text('模擬從網路獲取資料'),
        ),
        Expanded(
          flex: 1,
          child: Center(
            child: FutureBuilder<dynamic>(
              future: _futBuiHom,
              initialData: '初始化的資料',
              builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
                print(
                    '\n獲取到到資料: State${snapshot.connectionState} \n data${snapshot.data}');
                switch (snapshot.connectionState) {
                  case ConnectionState.none:
                  case ConnectionState.waiting:
                    return const Text('資料加載中.....'); //加載中
                  default: //如果_calculation執行完畢
                    if (snapshot.hasError) {
                      //若_calculation執行出現例外
                      return Text('Error: ${snapshot.hasError}');
                    } else {
                      //若_calculation執行正常完成
                      return Text('Error: ${snapshot.data}');
                    }
                }
              },
            ),
          ),
        ),
      ],
    );
  }
}

main() 函式初始化網路請求 (網路請求需要的域名baseUrl)

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  await SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);

  ///網路請求管理初始化
  HttpManager().init(baseUrl: 'https://tieba.baidu.com');
  runApp(MyApp());
}

發起了網路請求

總結

FutureBuilder 用于顯示異步請求的資料, 避免資料請求多次呼叫builder 對應的函式 , 我們需要在Widget 創建時 State 函式 initState 里面進行異步請求的初始化 .

Future+Dio+MVP 案例下載

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/400601.html

標籤:其他

上一篇:【安卓開發】Android平臺的記賬本app(全部代碼+room框架操作資料庫+設計報告)

下一篇:【Fix Bug】針對 Google Photos 回傳的圖片Uri,裁剪照片失敗

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more