我的编程空间,编程开发者的网络收藏夹
学习永远不晚

详解Flutter Widget

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详解Flutter Widget

 概述:

所有的一切都可以被称为widget

在开发 Flutter 应用过程中,接触最多的无疑就是Widget,是『描述』 Flutter UI 的基本单元,通过Widget可以做到:

  • 描述 UI 的层级结构 (通过Widget嵌套);
  • 定制 UI 的具体样式 (如:fontcolor等);
  • 指导 UI 的布局过程 (如:paddingcenter等);

Google 在设计Widget时,还赋予它一些鲜明的特点:

  • 声明式 UI —— 相对于传统 Native 开发中的命令式 UI,声明式 UI 有不少优势,如:开发效率显著提升、UI 可维护性明显加强等;
  • 不可变性 —— Flutter 中所有Widget都是不可变的(immutable),即其内部成员都是不可变的(final),对于变化的部分需要通过「Stateful Widget-State」的方式实现;
  • 组合大于继承 —— Widget设计遵循组合大于继承这一优秀的设计理念,通过将多个功能相对单一的Widget组合起来便可得到功能相对复杂的Widget

Widget的本质:

在Widget源码中有这样一段注释:

请添加图片描述

这段注释阐明了Widget的本质:用于配置Element的,Widget本质上是 UI 的配置信息 (附带部分业务逻辑)。

我们通常会将通过Widget描述的 UI 层级结构称之为「Widget Tree」,但与「Element Tree」、「RenderObject Tree」以及「Layer Tree」相比,实质上并不存在「Widget Tree」。为了描述方便,将 Widget 组合描述的 UI 层级结构称之为「Widget Tree」,也未尝不可。

分类:

请添加图片描述

Widget

Widget,所有 Widget 的基类。

请添加图片描述

如上图所示,在 Widget基类中有 3 个重要的方法 (属性):

  • Key key —— 在同一父节点下,用作兄弟节点间的唯一标识,主要用于控制当 Widget 更新时,对应的 Element 如何处理 (是更新还是新建)。若某 Widget 是其「Parent Widget」唯一的子节点时,一般不用设置 key;

GlobalKey 是一类较特殊的 key,在介绍 Element 时会附带介绍。

  • Element createElement() —— 每个Widget都有一个与之对应的Element,由该方法负责创建,createElement可以理解为设计模式中的工厂方法,具体的Element类型由对应的Widget子类负责创建;
  • static bool canUpdate(Widget oldWidget, Widget newWidget) —— 是否可以用 new widget 修改前一帧用 old widget 生成的 Element,而不是创建新的 Element,Widget类的默认实现为:2个WidgetruntimeTypekey都相等时,返回true,即可以直接更新 (key 为 null 时,认为相等)。

上述更新流程,同样在介绍 Element 时会重点分析。

StatelessWidget

无状态-组合型 Widget,由其build方法描述组合 UI 的层级结构。在其生命周期内状态不可变。


/// A widget that does not require mutable state.
///
/// A stateless widget is a widget that describes part of the user interface by
/// building a constellation of other widgets that describe the user interface
/// more concretely. The building process continues recursively until the
/// description of the user interface is fully concrete (e.g., consists
/// entirely of [RenderObjectWidget]s, which describe concrete [RenderObject]s).

具体是两个方法:

  • StatelessElement createElement() ——「Stateless Widget」对应的 Element 为StatelessElement,一般情况下StatelessWidget子类不必重写该方法,即子类对应的 Element 也是StatelessElement

@override
StatelessElement createElement() => StatelessElement(this);
  • Widget build(BuildContext context) —— 算是 Flutter 体系中的核心方法之一

@protected
Widget build(BuildContext context);

以『声明式 UI』的形式描述了该组合式 Widget 的 UI 层级结构及样式信息,也是开发 Flutter 应用的主要工作『场所』。该方法在 3 种情况下被调用:

  • Widget 第一次被加入到 Widget Tree 中 (更准确地说是其对应的 Element 被加入到 Element Tree 时,即 Element 被挂载『mount』时);
  • 「Parent Widget」修改了其配置信息;
  • 该 Widget 依赖的「Inherited Widget」发生变化时。

当「Parent Widget」或 依赖的「Inherited Widget」频繁变化时,build方法也会频繁被调用。因此,提升build方法的性能就显得十分重要,Flutter 官方给出了几点建议:

  • *减少不必要的中间节点,即减少 UI 的层级,*如:对于「Single Child Widget」,没必要通过组合「Row」、「Column」、「Padding」、「SizedBox」等复杂的 Widget 达到某种布局的目标,或许通过简单的「Align」、「CustomSingleChildLayout」即可实现。又或者,为了实现某种复杂精细的 UI 效果,不一定要通过组合多个「Container」,再附加「Decoration」来实现,通过 「CustomPaint」自定义或许是更好的选择;
  • *尽可能使用const Widget,*为 Widget 提供const构造方法;

关于 const constructor 可以看看我这篇文章。

  • 必要时,*可以将「Stateless Widget」重构成「Stateful Widget」,*以便可以使用「Stateful Widget」中一些特定的优化手法,如:缓存「sub trees」的公共部分,并在改变树结构时使用GlobalKey
  • *尽量减小 rebuilt 范围,*如:某个 Widget 因使用了「Inherited Widget」,导致频繁 rebuilt,可以将真正依赖「Inherited Widget」的部分提取出来,封装成更小的独立 Widget,并尽量将该独立 Widget 推向树的叶子节点,以便减小 rebuilt 时受影响的范围。

StatefulWidget

有状态-组合型 Widget,但要注意的是StatefulWidget本身还是不可变的,其可变状态存在于State中。


/// A widget that has mutable state.
///
/// State is information that (1) can be read synchronously when the widget is
/// built and (2) might change during the lifetime of the widget. It is the
/// responsibility of the widget implementer to ensure that the [State] is
/// promptly notified when such state changes, using [State.setState].

具体有两个方法:

  • StatefulElement createElement() ——「Stateful Widget」对应的 Element 为StatefulElement,一般情况下StatefulWidget子类不用重写该方法,即子类对应的Element 也是StatefulElement

@override
StatefulElement createElement() => StatefulElement(this);
  • State createState() —— 创建对应的 State,该方法在StatefulElement的构造方法中被调用。可以简单地理解为当「Stateful Widget」被添加到 Widget Tree 时会调用该方法。

@protected
@factory
State createState(); // ignore: no_logic_in_create_state, this is the original sin

StatefulElement(StatefulWidget widget)
     : _state = widget.createState(),
       super(widget) {
   _state._element = this;
   _state._widget = widget;
 }

实际上是「Stateful Widget」对应的「Stateful Element」被添加到 Element Tree 时,伴随「Stateful Element」的初始化,createState方法被调用。从后文可知一个 Widget 实例可以对应多个 Element 实例 (也就是同一份配置信息 (Widget) 可以在 Element Tree 上不同位置配置多个 Element 节点),因此,createState方法在「Stateful Widget」生命周期内可能会被调用多次。
另外,需要注意的是配有GlobalKey的 Widget 对应的 Element 在整个 Element Tree 中只有一个实例。

State

有状态小部件 的逻辑和Stateful Widget

State 用于处理「Stateful Widget」的业务逻辑以及可变状态
由于其内部状态是可变的,故 State 有较复杂的生命周期:

请添加图片描述

如上图,State 的生命周期大致可以分为 8 个阶段:

  • 在对应的「Stateful Element」被挂载 (mount) 到树上时,通过StatefulElement.constructor –> StatefulWidget.createState创建 State 实例;

StatefulElement.constructor中的_state._element = this;可知,State._emelent指向了对应的 Element 实例,而我们熟知的State.context引用的就是这个_elementBuildContext get context => _element;
State实例与Element实例间的绑定关系一经确定,在整个生命周期内不会再变了 (Element 对应的 Widget 可能会变,但对应的 State 永远不会变),期间,Element可以在树上移动,但上述关系不会变 (即「Stateful Element」是带着状态移动的)。

  • StatefulElement 在挂载过程中接着会调用State.initState,子类可以重写该方法执行相关的初始化操作 (此时可以引用contextwidget属性);
  • 同样在挂载过程中会调用State.didChangeDependencies,该方法在 State 依赖的对象 (如:「Inherited Widget」) 状态发生变化时也会被调用,*子类很少需要重写该方法,*除非有非常耗时不宜在build中进行的操作,因为在依赖有变化时build方法也会被调用;
  • 此时,State 初始化已完成,其build方法此后可能会被多次调用,在状态变化时 State 可通过setState方法来触发其子树的重建;
  • 此时,「element tree」、「renderobject tree」、「layer tree」已构建完成,完整的 UI 应该已呈现出来。此后因为变化,「element tree」中「parent element」可能会对树上该位置的节点用新配置 (Widget) 进行重建,当新老配置 (oldWidget、newWidget)具有相同的「runtimeType」&&「key」时,framework 会用 newWidget 替换 oldWidget,并触发一系列的更新操作 (在子树上递归进行)。同时,State.didUpdateWidget方法被调用,子类重写该方法去响应 Widget 的变化;

上述 3 棵树以及更新流程在后续文章中会有详细介绍

  • 在 UI 更新过程中,任何节点都有被移除的可能,State 也会随之移除,(如上一步中「runtimeType」||「key」不相等时)。此时会调用State.deactivate方法,由于被移除的节点可能会被重新插入树中某个新的位置上,故子类重写该方法以清理与节点位置相关的信息 (如:该 State 对其他 element 的引用)、同时,不应在该方法中做资源清理;

重新插入操作必须在当前帧动画结束之前

  • 当节点被重新插入树中时,State.build方法被再次调用;
  • 对于在当前帧动画结束时尚未被重新插入的节点,State.dispose方法被执行,State 生命周期随之结束,此后再调用State.setState方法将报错。子类重写该方法以释放任何占用的资源。

请添加图片描述

至此,State 中的核心方法基本都已在上述过程中介绍了,下面重点看一下setState方法:


void setState(VoidCallback fn) {
  assert(fn != null);
  assert(() {
    if (_debugLifecycleState == _StateLifecycle.defunct) {
      throw FlutterError.fromParts(<DiagnosticsNode>[...]);
    }
    if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
      throw FlutterError.fromParts(<DiagnosticsNode>[...]);
    }
    return true;
  }());
  final dynamic result = fn() as dynamic;
  assert(() {
    if (result is Future) {
      throw FlutterError.fromParts(<DiagnosticsNode>[...]);
    }
    return true;
  }());
  _element.markNeedsBuild();
}
  • 从上述源码可以看到,关于setState方法有几点值得关注:
  • State.dispose后不能调用setState (第 4 行);
  • 在 State 的构造方法中不能调用setState (第 7 行);
  • setState方法的回调函数 (fn) 不能是异步的 (返回值为Future),原因很简单,因为从流程设计上 framework 需要根据回调函数产生的新状态去刷新 UI (第 14 行);
  • 通过setState方法之所以能更新 UI,是在其内部调用_element.markNeedsBuild()实现的 (具体过程在介绍 Element 时再详细分析)。

关于 State 最后再强调 2 点:

  • 若State.build方法依赖了自身状态会变化的对象,如:ChangeNotifier、Stream或其他可以被订阅的对象,需要确保在initState、didUpdateWidget、dispose

等 3 方法间有正确的订阅 (subscribe) 与取消订阅 (unsubscribe) 的操作:

initState中执行 subscribe;

如果关联的「Stateful Widget」与订阅有关,在didUpdateWidget中先取消旧的订阅,再执行新的订阅;

dispose中执行 unsubscribe。

  • State.initState方法中不能调用BuildContext.dependOnInheritedWidgetOfExactType,但State.didChangeDependencies会随之执行,在该方法中可以调用。

ParentDataWidget

ParentDataWidget以及下面要介绍的InheritedElement都继承自ProxyWidget,由于ProxyWidget作为抽象基类本身没有任何功能,故下面直接介绍ParentDataWidgetInheritedElement


/// Base class for widgets that hook [ParentData] information to children of/// [RenderObjectWidget]s.

ParentDataWidget作为 Proxy 型 Widget,其功能主要是为其他 Widget 提供ParentData信息。虽然其 child widget 不一定是 RenderObejctWidget 类型,但其提供的ParentData信息最终都会落地到 RenderObejctWidget 类型子孙 Widget 上。

ParentData 是『parent renderobject』在 layout『child renderobject』时使用的辅助定位信息,详细信息会在介绍 RenderObject 时介绍。


void attachRenderObject(dynamic newSlot) {
	assert(_ancestorRenderObjectElement == null);
	_slot = newSlot;
	_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
	_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
	final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
	if (parentDataElement != null)  
	                                          _updateParentData(parentDataElement.widget);
}
ParentDataElement<RenderObjectWidget> _findAncestorParentDataElement() {
	Element ancestor = _parent;
	while (ancestor != null && ancestor is! RenderObjectElement) 
	                                          {
		if (ancestor is ParentDataElement<RenderObjectWidget>) 
		                                          return ancestor;
		ancestor = ancestor._parent;
	}
	return null;
}
void _updateParentData(ParentDataWidget<RenderObjectWidget> parentData) 
                                          {
	parentData.applyParentData(renderObject);
}

上面这段代码来自RenderObjectElement,可以看到在其attachRenderObject方法第 6 行从祖先节点找ParentDataElement,如果找到就用其 Widget(ParentDataWidget) 中的 parentData 信息去设置 Render Obejct。在查找过程中如查到RenderObjectElement (第 13 行),说明当前 RenderObject 没有 Parent Data 信息。
最终会调用到ParentDataWidget.applyParentData(RenderObject renderObject),子类需要重写该方法,以便设置对应RenderObject.parentData

来看个例子,通常配合Stack使用的Positioned(继承自ParentDataWidget):


void applyParentData(RenderObject renderObject) {
	assert(renderObject.parentData is StackParentData);
	final StackParentData parentData = renderObject.parentData;
	bool needsLayout = false;
	if (parentData.left != left)
	  {
		parentData.left = left;
		needsLayout = true;
	}
	...  if (parentData.width != width) 
	  {
		parentData.width = width;
		needsLayout = true;
	}
	...  if (needsLayout) {
		final AbstractNode targetParent = renderObject.parent;
		if (targetParent is RenderObject)    
		      targetParent.markNeedsLayout();
	}
}

可以看到,Positioned在必要时将自己的属性赋值给了对应的RenderObject.parentData (此处是StackParentData),并对「parent render object」调用markNeedsLayout(第 19 行),以便重新 layout,毕竟修改了布局相关的信息。


abstract class ParentDataWidget<T extends RenderObjectWidget> extends ProxyWidget

如上所示,ParentDataWidget在定义上使用了泛型<T extends RenderObjectWidget>,其背后的含义是:
从当前ParentDataWidget节点向上追溯形成的祖先节点链(『parent widget chain』)上,在 2 个ParentDataWidget类型的节点形成的链上至少要有一个『RenderObject Widget』类型的节点。因为一个『RenderObject Widget』不能接受来自 2 个及以上『ParentData Widget』的信息。


/// Base class for widgets that efficiently propagate information down the tree.
// To obtain the nearest instance of a particular type of inherited widget from
///a build context, use [BuildContext.dependOnInheritedWidgetOfExactType].

InheritedWidget 用于在树上向下传递数据。
通过BuildContext.dependOnInheritedWidgetOfExactType可以获取最近的「Inherited Widget」,需要注意的是通过这种方式获取「Inherited Widget」时,当「Inherited Widget」状态有变化时,会导致该引用方 rebuild。

具体原理在介绍 Element 时会详细分析。

通常,为了使用方便会「Inherited Widget」会提供静态方法of,在该方法中调用BuildContext.dependOnInheritedWidgetOfExactTypeof方法可以直接返回「Inherited Widget」,也可以是具体的数据。

有时,「Inherited Widget」是作为另一个类的实现细节而存在的,其本身是私有的(外部不可见),此时of方法就会放到对外公开的类上。最典型的例子就是Theme,其本身是StatelessWidget类型,但其内部创建了一个「Inherited Widget」:_InheritedThemeof方法就定义在上Theme上:


static MediaQueryData of(BuildContext context, {
	bool nullOk = false })
	{
		final MediaQuery query = context.dependOnInheritedWidgetOfExactType<MediaQuery>();
		if (query != null)    
		  return query.data;
		if (nullOk)    return null;
	}

of方法返回的是ThemeData类型的具体数据,并在其内部首先调用了BuildContext.dependOnInheritedWidgetOfExactType

我们经常使用的「Inherited Widget」莫过于MediaQuery,同样提供了of方法:

请添加图片描述

  • InheritedElement createElement() ——「Inherited Widget」对应的 Element 为InheritedElement,一般情况下InheritedElement子类不用重写该方法;
  • bool updateShouldNotify(covariant InheritedWidget oldWidget) —— 在「Inherited Widget」rebuilt 时判断是否需要 rebuilt 那些依赖它的 Widget;

如下是MediaQuery.updateShouldNotify的实现,在新老Widget.data 不相等时才 rebuilt 那依赖的 Widget。


bool updateShouldNotify(MediaQuery oldWidget) => data != oldWidget.data;

RenderObjectWidget

真正与渲染相关的 Widget,属于最核心的类型,一切其他类型的 Widget 要渲染到屏幕上,最终都要回归到该类型的 Widget 上。

  • RenderObjectElement createElement() ——「RenderObject Widget」对应的 Element 为RenderObjectElement,由于RenderObjectElement也是抽象类,故子类需要重写该方法;
  • RenderObject createRenderObject(BuildContext context) —— 核心方法,创建 Render Widget 对应的 Render Object,同样子类需要重写该方法。该方法在对应的 Element 被挂载到树上时调用(Element.mount),即在 Element 挂载过程中同步构建了「Render Tree」(详细过程后续文章会详细分析);

@overrideRenderFlex createRenderObject(BuildContext context) {
	return RenderFlex(  
	  direction: direction,    
	  mainAxisAlignment: mainAxisAlignment,    
	  mainAxisSize: mainAxisSize,    
	  crossAxisAlignment: crossAxisAlignment,   
	  textDirection: getEffectiveTextDirection(context),    
	  verticalDirection: verticalDirection,    
	    textBaseline: textBaseline,  
	  )
	;
}

上面是Flex.createRenderObject的源码,真实感受一下 (还是代码更有感觉)。可以看到,用Flex的信息(配置)初始化了RenderFlex

FlexRowColumn的基类,RenderFlex继承自RenderBox,后者继续自RenderObject

  • void updateRenderObject(BuildContext context, covariant RenderObject renderObject) —— 核心方法,在 Widget 更新后,修改对应的 Render Object。该方法在首次 build 以及需要更新 Widget 时都会调用;

@overridevoid updateRenderObject(BuildContext context, covariant RenderFlex renderObject) 
{
	renderObject   
	  ..
	direction = direction   
	  ..mainAxisAlignment = mainAxisAlignment   
	  ..
	mainAxisSize = mainAxisSize   
	  ..
	crossAxisAlignment = crossAxisAlignment  
	  ..
	textDirection = getEffectiveTextDirection(context) 
	  ..
	verticalDirection = verticalDirection   
	  ..
	textBaseline = textBaseline;
}

Flex.updateRenderObject的源码也很简单,与Flex.createRenderObject几乎一一对应,用当前Flex的信息修改renderObject

  • void didUnmountRenderObject(covariant RenderObject renderObject) —— 对应的「Render Object」从「Render Tree」上移除时调用该方法。

RenderObjectWidget的几个子类:LeafRenderObjectWidgetSingleChildRenderObjectWidgetMultiChildRenderObjectWidget只是重写了createElement方法以便返回各自对应的具体的 Element 类实例。

小结


至此,重要的基础型 Widget 基本介绍完了,总结一下:

  • Widget 本质上是 UI 的配置信息 (附加部分业务逻辑),并不存在一颗真实的「Widget Tree」(与「Element Tree」、「RenderObject Tree」以及「Layer Tree」相比);
  • Widget 从功能上可以分为 3 类:「Component Widget」、「Proxy Widget」以及「Renderer Widget」;
  • Widget 与 Element 一一对应,Widget 提供创建 Element 的方法 (createElement,本质上是一个工厂方法);
  • 只有「Renderer Widget」才会参与最终的 UI 生成过程(Layout、Paint),只有该类型的 Widget 才有与之对应的「Render Object」,同样由其提供创建方法(createRenderObject)。

到此这篇关于详解Flutter Widget的文章就介绍到这了,更多相关Flutter Widget内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

详解Flutter Widget

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Flutter基本组件Basics Widget怎么用

这篇文章主要介绍Flutter基本组件Basics Widget怎么用,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1. 概述Basics Widget 并不是 Flutter 的一个专门的Widget类别,而是 F
2023-06-22

用Flutter做APP学习心得:Flutter widget的生命周期

第一次看文章的朋友可以关注我,会不定期发布大厂面试题、Android架构技术知识点及解析等内容,还有Android学习PDF+源码笔记+面试文档+进阶视频分享更多还可以看我的GitHub链接:https://github.com/Meng9
2023-06-04

Flutter交互并使用小工具管理其状态widget的state详解

这篇文章主要为大家介绍了Flutter交互并使用小工具管理其状态widget的state详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-23

Flutter Widget怎么开发Shortcuts快捷键

本篇内容主要讲解“Flutter Widget怎么开发Shortcuts快捷键”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Flutter Widget怎么开发Shortcuts快捷键”吧!正文
2023-07-04

Flutter框架解决盒约束widget和assets里加载资产技术

这篇文章主要为大家介绍了Flutter框架解决盒约束widget和assets里加载资产技术运用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-23

Flutter Widget开发之Focus组件怎么使用

本篇内容介绍了“Flutter Widget开发之Focus组件怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!正文就网络和应用程序而
2023-07-04

FlutterSizedBox布局组件Widget使用示例详解

这篇文章主要为大家介绍了FlutterSizedBox布局组件Widget使用示例详解
2023-02-13

Flutter Widget移动UI框架怎么使用Material和密匙Key

本文小编为大家详细介绍“Flutter Widget移动UI框架怎么使用Material和密匙Key”,内容详细,步骤清晰,细节处理妥当,希望这篇“Flutter Widget移动UI框架怎么使用Material和密匙Key”文章能帮助大家
2023-07-04

Android 桌面Widget开发要点解析(时间日期Widget)

最近需要编写一个日期时间的桌面Widget用来关联日历程序,以前很少写桌面Widget。对这方面技术不是很熟悉,今天花时间重新整理了一下,顺便把编写一个简单时间日期程序过程记录下来。 桌面Widget其实就是一个显示一些信息的工具(现在也有
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录