大家好啊!我是你们的羊哥。今天给大家带来一个特别实用的.NET库 - MediatR。它能帮我们解决代码耦合度高、维护困难的问题。通过使用中介者模式,MediatR让我们的代码结构更清晰,组件之间的通信更加优雅。让我们一起来看看这个强大的工具吧!什么是MediatR?MediatR是.NET平台上实现中介者模式的一个轻量级库。它的作用就像一个"中间人",负责协调各个组件之间的通信。比如说,咱们去房产中介找房子,不需要一个个去联系房东,而是通过中介来统一协调,这就是中介者模式的典型例子。安装和配置首先,我们需要通过NuGet包管理器安装MediatR:powershell
Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
然后在Startup.cs或Program.cs中注册MediatR服务:csharp
builder.Services.AddMediatR(cfg =>; cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
基本使用方式MediatR主要有两种消息类型:
请求/响应模式(IRequest;)
通知模式(INotification)
让我们先来看一个简单的请求/响应例子:csharp
// 1. 创建请求类
public class GetUserQuery : IRequest<;UserDto>;
{
public int UserId { get; set; }
}
// 2. 创建响应DTO
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
// 3. 创建处理器
public class GetUserQueryHandler : IRequestHandler<;GetUserQuery, UserDto>;
{
public async Task<;UserDto>; Handle(GetUserQuery request, CancellationToken cancellationToken)
{
// 这里实现具体的用户查询逻辑
return new UserDto
{
Id = request.UserId,
Name = "张三"
};
}
}
在控制器中使用:csharp
public class UserController : ControllerBase
{
private readonly IMediator _mediator;
public UserController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet("{id}")]
public async Task<;ActionResult<;UserDto>;>; GetUser(int id)
{
var query = new GetUserQuery { UserId = id };
var result = await _mediator.Send(query);
return Ok(result);
}
}
通知模式示例有时我们需要实现一对多的消息通知,这时就可以使用通知模式:csharp
// 1. 创建通知
public class UserCreatedNotification : INotification
{
public string UserName { get; set; }
}
// 2. 创建通知处理器
public class EmailHandler : INotificationHandler<;UserCreatedNotification>;
{
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
// 发送邮件通知
await SendEmailAsync(notification.UserName);
}
}
public class LogHandler : INotificationHandler<;UserCreatedNotification>;
{
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
// 记录日志
await LogUserCreationAsync(notification.UserName);
}
}
使用通知:csharp
await _mediator.Publish(new UserCreatedNotification { UserName = "李四" });
小贴士
处理器类名建议以Handler结尾,这是一种约定俗成的命名方式
可以使用MediatR的管道行为(Pipeline Behavior)来实现横切关注点,比如日志记录、性能监控等
注意区分Send和Publish方法:Send用于请求/响应模式,Publish用于通知模式
实际应用场景MediatR特别适合以下场景:
进阶用法 - 管道行为csharp
public class LoggingBehavior<;TRequest, TResponse>; : IPipelineBehavior<;TRequest, TResponse>;
{
private readonly ILogger<;LoggingBehavior<;TRequest, TResponse>;>; _logger;
public LoggingBehavior(ILogger<;LoggingBehavior<;TRequest, TResponse>;>; logger)
{
_logger = logger;
}
public async Task<;TResponse>; Handle(TRequest request, RequestHandlerDelegate<;TResponse>; next, CancellationToken cancellationToken)
{
_logger.LogInformation($"Handling {typeof(TRequest).Name}");
var response = await next();
_logger.LogInformation($"Handled {typeof(TRequest).Name}");
return response;
}
}
小伙伴们,今天的.NET学习之旅就到这里啦!记得动手敲代码,有问题随时在评论区问羊哥哦。通过MediatR,我们可以写出更加清晰、可维护的代码。祝大家学习愉快,.NET学习节节高!
ConstraintLayout属性详解和Chain的使用
今日科技快讯
近日有网友爆料:在百度地图搜索"深圳市儿童 ",搜索结果却将莆田系的远东妇儿 的地址链接到了下面,而且放到第二位,导致许多家长被误导。对此百度地图官微称:百度地图的标注是完全免费的,不存在商业售卖标注。本次事件为我们的疏忽,返回了错误的结果,已经第一时间修正了。
作者简介
早上好!新的一周开始啦,很高兴又跟大家见面了!
本篇是老司机张旭童 的第九篇投稿了!细致地分析了ConstraintLayout,同时对我之前有关ConstraintLayout文章中没详细说的chains部分进行了补充,希望对大家有所帮助!
张旭童 的博客地址:
概述
说实话这篇文章写的算是比较晚了,距离ConstraintLayout出现至今已经有一年了。
且自AS2.3起创建新的Activity,默认的layout根布局就是ConstraintLayout。所以再不学习就真的晚了。我也是正式开始学习的道路,先说一下我的学习过程:
ConstraintLayout官方文档
Guideline官方文档
使用前的准备
引入也有坑,无力吐槽。先放上截止至20170524,最新版本1.0.1:
compile 'com.android.support.constraint:constraint-layout:1.0.1'
坑是啥?因为我使用的是最新的 release版 AndroidStudio2.3.2,新建 Activity 后,自动帮我引入的是 1.0.8-alpha版本
开始我就这么愉快的学习了,可是当我学习到 Chain 相关姿势时,特码的,他居然报错。说找不到属性:
ok,那我百度,显然搜不到的,ok,那我再 google,特么的居然也搜不到。
震惊,于是机智的我去看源码,发现我使用的1.0.8-alpha版本的源码里根本没有Chain相关属性的支持,所以我就觉得一定是引入的版本有问题,于是我用 google 搜索 ”ConstraintLayout last version”,发现诶~官方有说最新版链接如下:
按照这个链接提示,最新版是 1.0.2,嗯哼,当我换成 1.0.2 后,发现无法 download….
不知道是网络问题还是什么问题,提示我无法下载,具体的错误记不清了。反正就是无法获取到这个版本。
特么的机智的我又直接去AndroidStudio的Library Dependency里去搜索,发现居然搜不到“ConstraintLayout “的库。再次懵逼。
后来我进行最后的一次尝试,因为我看google官方上1.0.2版本的上一个版本是1.0.1.于是我修改版本号,sync gradle,居然成功了。
总结踩坑历程:
1. 最新 Release版 AndroidStudio模板自带的是 1.0.8alpha版 ConstraintLayout
2. 使用 Chain 相关属性报错
3. 发现该版本源码没有 Chain 相关属性
4. 官网说的最新版 1.0.2 我无法下载
5. AndroidStudio 自带的 Library Dependency 搜不到 ConstraintLayout
6. 修改版本号为 1.0.1 下载
对此,我只能说“惊不惊喜! 意不意外!”
ConstraintLayout是什么
先概况一下,它是一个为了解决布局嵌套和模仿前端 flexible 布局的一个新布局。
从字面上理解,ConstraintLayout 是约束布局。在我理解,这是一个 RelativeLayout 的升级版。
而当初推出 RelativeLayout 的目的是为了在减少多层嵌套布局,推出 ConstraintLayout 也是同样的目的,尽可能的使布局 宽而短,而不是 窄而长。而 ConstraintLayout 更加强大,很多需要多层嵌套的布局,使用 ConstraintLayout 只需要一层即可解决。
它的 Chain 几种 style 方式,和前端的 flexbox 布局风格一致,官方文档中也说了它是 flexible 方式布局控件的东西。
A ConstraintLayout is a ViewGroup which allows you to position and size widgets in a flexible way.
而且搭配可视化的操作,使得布局也变得更轻松。
Google 官方推荐所有操作都在 ”Design” 区域搞定,即通过可视化拖拖拽拽生成布局大致的样子,然后针对具体属性、约束 精细修改。
甚至可以这么说,你完全不需要知道 ConstraintLayout 的具体属性值分别是什么,只通过拖拽和鼠标点击就可以实现一些布局。
那么本文的意义何在呢?
我觉得首先是要知其然知其所以然,那些拖拽点击生成的代码属性到底是什么意思?通过本文可以了解。
另外 虽然大部分操作可以在“Design”区域完成,但是保不齐的需要你切换至“Text”区域,写上一两行属性代码,了解 这些属性 总是有益无害的。
而且,有一些属性是无法简单通过拖拽点击完成的,例如 Margins when connected to a GONE widget。
刚才提到 RelativeLayout,其实 RelativeLayout 也是通过约束来布局 子View 的呀。以前RelativeLayout的约束有两种:
1.子控件和子控件之间的约束(如 android:layout_below="@id/title")
2.子控件和父控件的约束(如android:layout_alignParentTop="true")
现在 ConstraintLayout 也是类似的,只不过除了以上两种约束,还多了一种
3. 子控件和 Guideline 的约束
其实关于和 Guideline 的约束,也可以理解成 约束1,因为 Guideline 其实就是一个在屏幕上不显示的 View 罢了。稍后讲到 Guideline 会带大家看看它巨简单的源码。
下面开始正文,开始属性的讲解.
相对定位(Relative positioning)
这一节的属性和相对布局的很像,值得注意的是参数取值是ID(@id/button1)代表 约束1、3, 或者字符串"parent"代表 约束2:
属性都形如 layout_constraintXXX_toYYYOf,这里我的理解,constraintXXX 里的 XXX 代表是这个子控件自身的哪条边(Left、Right、Top、Bottom、Baseline),
而 toYYYOf 里的 YYY 代表的是和约束控件的 哪条边发生约束(取值同样是Left、Right、Top、Bottom、Baseline)。
当 XXX 和 YYY 相反时,表示控件自身的 XXX 在约束控件的 YYY 的一侧,例如 app:layout_constraintLeft_toRightOf="@id/button1",表示的是控件自身的左侧在 button1 的右侧。
当 XXX 和 YYY 相同时,表示控件自身的 XXX 和约束控件的 YYY 的一侧对齐,例如:app:layout_constraintBottom_toBottomOf="parent",表示控件自身底端和父控件底端对齐。代码为:
Margins
margin 和以往的使用一致,注意margin不能为负值即可。在上图中也顺便展示了 margin 的使用。
当约束的widget为GONE时的Margins
Margins when connected to a GONE widget
举例,当 A控件 约束在 B控件 的左边,B控件 GONE了,此时 A 会额外拥有一个 margin 的能力,来“补充” B 消失的导致的“位移”。这就是本节的属性。
这一节的属性开始我并没有理解,后来是通过写了一些Demo实验才明白。奈何官方文档惜字如金,只有一句话,并没有 Demo 展示:
When a position constraint target’s visibility is View.GONE, you can also indicates a different margin value to be used using the following attributes:
先看属性:
再看Demo:
当给 button4 隐藏GONE掉以后:
会发现 Button5 纹丝不动,并没有受到 Button4 消失的影响。
这里我们再仔细看看 button4 的 android:layout_width="100dp",而 button5 的 android:layout_marginRight="10dp",app:layout_goneMarginRight="110dp"
110 = 100 +10 , 这是一道小学计算题。
什么意思?几个注意事项:
居中定位和倾向(Centering positioning and bias)
约束布局一个有用的地方是它如何处理“不可能”的约束。比如你定义如下:
按照我们第一小节讲的属性值,这个定义的意思是,Button 的左边和父控件的左边对齐,Button 的右边和父控件的右边对齐。
可是控件是 wrap_content 的,它如果不铺满父控件要如何能满足这两个约束呢?实际效果如下:
控件会居中显示,因为这两个约束作用 类似于 水平方向上,有相反的力去拉控件,最终控件会居中显示。
搭配bias,能使约束偏向某一边,默认是0.5,有以下属性:
比如上个Demo,我加入 app:layout_constraintHorizontal_bias="0.9",则会在水平方向上向右偏移至90%
对可见性的处理(Visibility behavior)
这一节是对前一节 goneMargin 的补充。重点是 Gone 隐藏掉的控件,会被解析成一个点,并忽略 margin。
ConstraintLayout 能为 View.Gone 的 View 特殊处理。
通常,GONE 的控件不会被显示,并且不是布局本身的一部分(即如果标记为 GONE,则其实际尺寸并不会更改)。但是在布局计算方面,GONE 的 View 仍然是其中的一个重要区别:
对于布局传递,它们的维度将被视为零(基本上它们将被解析为一个点)。如果他们对其他小部件有约束力,那么他们仍然会受到尊重,但任何 margin 都将等于零。
拿上个Demo改一下,为 A 加上一个 android:layout_marginRight="10dp",为了使 A 隐藏后,B 仍能纹丝不动,则 B 的 app:layout_goneMarginRight="120dp"。B goneMarginRight120 = A宽度100 + A marginRight10 +B marginRight10
尺寸约束(Dimensions constraints)
ConstraintLayout的最小尺寸 (Minimum dimensions on ConstraintLayout)
可以为 ConstraintLayout自身定义最小的尺寸,他会在ConstraintLayout 为 WRAP_CONTENT 时起作用。
控件尺寸约束(Widgets dimension constraints)
控件的宽高有三种方式为其设置:
有些人可能有疑问,为什么不用 MATCH_PARENT 了。官方文档如是说:
MATCH_PARENT is not supported for widgets contained in a ConstraintLayout, though similar behavior can be defined by using MATCH_CONSTRAINT with the corresponding left/right or top/bottom constraints being set to “parent”.
意思是 MATCH_PARENT 不再被支持了,通过 MATCH_CONSTRAINT 替代。我们写个 Demo 看一下三种方式设置的效果吧:
有些人是不是要说,你特么逗我,不是说好的 0dp 等于 MATCH_CONSTRAINT,应该是撑满屏幕的呀!OK ,把刀放下。让我们仔细看这个 MATCH_CONSTRAINT 属性。它 match 的是约束。
而这里第三个按钮的约束是第二个按钮,所以它的宽度设为 MATCH_CONSTRAINT时,是和它的约束按钮,即第二个按钮一样宽。
注意,此时,竖直方向上没有约束,所以不能使用 MATCH_CONSTRAINT 属性.
我们仅仅将第三个按钮的属性修改为
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
则它宽度会撑满屏幕:
我们再修改 Demo,分别为后两个按钮加上 margin:
最后,记住一句话约束要和 0dp 的 方向一致。否则无效。
比例(Ratio)
只有一个方向约束
可以以比例去定义 View 的宽高。为了做到这一点,需要将至少一个约束维度设置为 0dp(即MATCH_CONSTRAINT)并将属性 layout_constraintDimentionRatio 设置为给定的比例。例如:
比例值有两种取值:
当约束多于一个(宽高都被约束了)
如果两个维度均设置为 MATCH_CONSTRAINT(0dp),也可以使用比例。 在这种情况下,系统会使用满足所有约束条件和比率的最大尺寸。
如果需要根据一个维度的尺寸去约束另一个维度的尺寸。则可以在比率值的前面添加 W 或者 H 来分别约束宽度或者高度。
例如,如果一个尺寸被两个目标约束(比如宽度为0,在父容器中居中),可以使用 W 或H 来指定哪个维度被约束。
这里用“H”表示以高度为约束,高度的最大尺寸就是父控件的高度,“2:1”表示高:宽 = 2 : 1.则宽度为高度的一半:
链条(Chains)
链条在同一个轴上(水平或者垂直)提供一个类似群组的统一表现。另一个轴可以单独控制。
创建链条(Creating a chain)
如果一组小部件通过双向连接(见图,显示最小的链,带有两个小部件),则将其视为链条。
链条头(Chain heads)
链条由在链的第一个元素(链的“头”)上设置的属性控制:
头是水平链最左边的 View,或垂直链最顶端的 View。
链的margin(Margins in chains)
如果在连接上指定了边距,则将被考虑在内。例如:
通过 app:layout_constraintRight_toLeftOf="@+id/buttonB" 和 app:layout_constraintLeft_toRightOf="@+id/buttonA" 就建立了链条,(我中有你,你中有我)。
然后它们两个成了一个整体,所以链条左边设置 app:layout_constraintLeft_toLeftOf="parent"使得和父控件左对齐,右边设置 app:layout_constraintRight_toRightOf="parent"使得和父控件右对齐,这样整个链条就居中了,最后对左控件设置了 margin,相当于整个链条左边有了 margin。效果:
链条样式(Chain Style)
当在链的第一个元素上设置属性layout_constraintHorizontal_chainStyle 或 layout_constraintVertical_chainStyle时,链的行为将根据指定的样式(默认为CHAIN_SPREAD)而更改。
看图这里就很像js里的 flexible 有木有。因为 ConstraintLayout 就是模仿 flexible 做的。
取值如下:
拿加权链举个例子:
加权链(Weighted chains)
和LinearLayout的weight类似。
链的默认行为是在可用空间中平均分配元素。 如果一个或多个元素使用 MATCH_CONSTRAINT,它们将使用剩余的空白空间(在它们之间相等)。 属性 layout_constraintHorizontal_weight 和 layout_constraintVertical_weight 将决定这些都设置了 MATCH_CONSTRAINT 的 View 如何分配空间。
例如,在包含使用 MATCH_CONSTRAINT 的两个元素的链上,第一个元素使用权重为 2,第二个元素的权重为 1,第一个元素占用的空间将是第二个元素的两倍
最后关于链条,再给大家看一个关于 margin 的 demo:
一图胜千言,可以看到虽然他们的 weight 相等,但是 margin 是被计算在约束里的,所以左边的按钮宽度比右边的小。
Guideline
Guideline 只能用于 ConstraintLayout 中,是一个工具类,不会被显示,仅仅用于辅助布局。它可以是 horizontal 或者vertical 的。(例如:android:orientation="vertical")
定位 Guideline 有三种方式:
一个栗子一看便知:
Guideline源码:
public static final int GONE = 0x00000008;
源码就这么点,这货的源码和 ViewStub 有点像啊,可以看出
总结
这篇文章我写了整整一天,每个例子我都边写边跑了一遍,也看了几篇别人的文章,有些人简单的翻译了官方文档,但是对文档中一些没有举例, 不那么好理解的地方也没有说明,于是便有了此文。
关于可视化操作,建议直接看我写的
动态图解&实例 ConstraintLayout Chain
文中代码地址在我的Demo合集中: