零、版本履历
日期 | 说明 |
---|---|
2020.05.02 | 初稿 |
一、效果
先看最终效果。
最终微聊烂尾了,更确切地说,还没开始就结束了。
二、由来
产品虽然没有最终做出来,但至少聊天气泡打磨的还挺像样的。
说说怎么实现的吧。打磨一个聊天气泡的想法由来已久。
WinForm
最开始用的WInForm,想重绘ListBox或者RichTextBox来做,可是借助万能的度娘也没找到思路,不熟悉GDI+,更不知道怎么去重绘。
Layui
后来看到Layui中的IM组件挺符合我的需求,想到可以借助CefSharp实现客户端和前端的联动,最终由于不熟悉前端,不知道怎么屏蔽边框和拖动,想法又泡汤了。
WPF
直到某天入了WPF的坑,由于它自带界面和逻辑分离的天然优势,完全可以重写Label
的DataTemplate
来实现,特别简单。
三、实现
思路很简单,重写ListView
的模板,针对接收
/发送
两种行为分别定义两个模板,再定义一个模板选择器DataTemplateSelector
判断要应用哪个模板。
定义ChatBubble
考虑到气泡中不仅仅显示文本,还可能会有图片和表情符号,我们可以重写Label的模板,分头像和内容两部分,内容的边框作成气泡的样子。
直接上代码,这是接收的气泡。
<Style TargetType="Label" x:Key="chatRecv">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<Grid Margin="0,5,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Width="35" Height="35" Source="userIcon.jpg" />
<Border x:Name="border1"
Grid.Column="1"
CornerRadius="1"
BorderBrush="#EDEDED"
BorderThickness="1"
VerticalAlignment="Top"
Margin="10,2,0,2"
Background="#FFFFFF"
HorizontalAlignment="Left"
Padding="5"
MinHeight="35"
MinWidth="50">
<ContentPresenter VerticalAlignment="Center" />
</Border>
<Canvas Grid.Column="1"
Width="10"
Height="16"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="3,14,0,0"
Background="Transparent">
<Path x:Name="path1"
Stroke="#EDEDED"
StrokeThickness="1"
Fill="#FFFFFF">
<Path.Data>
<PathGeometry Figures="M 8,0 L 0,6,8,12"/>
</Path.Data>
</Path>
</Canvas>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="path1"
Property="Fill"
Value="#F6F6F6" />
<Setter TargetName="border1"
Property="Background"
Value="#F6F6F6" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
同样的,再定义一个发送的气泡。
<Style TargetType="Label" x:Key="chatSend">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<Grid Margin="0,5,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<Image Grid.Column="1"
Width="35" Height="35"
Source="userIcon.jpg" />
<Border x:Name="border1"
CornerRadius="1"
BorderBrush="#9EEA6A"
BorderThickness="1"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="0,2,10,2"
Background="#9EEA6A"
Padding="5"
MinHeight="35"
MinWidth="50">
<ContentPresenter VerticalAlignment="Center" />
</Border>
<Canvas Width="10"
Height="16"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="5,14,0,0"
Background="Transparent">
<Path x:Name="path1"
Stroke="#9EEA6A"
StrokeThickness="0.5"
Fill="#9EEA6A">
<Path.Data>
<PathGeometry Figures="M 0,12 L 0,0,8,6"/>
</Path.Data>
</Path>
</Canvas>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="path1"
Property="Fill"
Value="#98E165" />
<Setter TargetName="border1"
Property="Background"
Value="#98E165" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
定义TemplateSelector
接下来轮到TemplateSelector
出场,最终气泡要通过ListView
呈现,可以在ItemSource
中指定一个标记位IsSend
来告知UI是接收
还是发送
。
class ChatBubbleSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var u = container as FrameworkElement;
MessageEntity message = item as MessageEntity;
if (message.IsSend)
return u.FindResource("chatSend") as DataTemplate;
else
return u.FindResource("chatRecv") as DataTemplate;
}
}
声明资源
然后在主界窗口中声明这个选择器,以及背景颜色等资源,配色取自微信PC端。
<Color x:Key="background">#F5F5F5</Color>
<SolidColorBrush x:Key="background_brush"
Color="{StaticResource background}"
options:Freeze="True" />
<Color x:Key="icon_btn_normal">#F7F7F7</Color>
<SolidColorBrush x:Key="icon_btn_normal_brush"
Color="{StaticResource icon_btn_normal}"
options:Freeze="True" />
<Color x:Key="message_send_bg">#98E165</Color>
<SolidColorBrush x:Key="message_send_bg_brush"
Color="{StaticResource message_send_bg}"
options:Freeze="True" />
<Color x:Key="message_recv_bg">#F6F6F6</Color>
<SolidColorBrush x:Key="message_recv_bg_brush"
Color="{StaticResource message_recv_bg}"
options:Freeze="True" />
<Color x:Key="green_normal">#07C160</Color>
<SolidColorBrush x:Key="green_normal_brush"
Color="{StaticResource green_normal}"
options:Freeze="True" />
主界面实现
最后在主界面上放一个ListView,用来承载消息。
<ListBox ItemTemplateSelector="{StaticResource chatBubbleSelector}"
ItemsSource="{Binding Messages}"
Background="{DynamicResource background_brush}"
SelectedIndex="{Binding SelectedMessage}"
BorderThickness="0,1,0,1"
BorderBrush="#D3D3D3"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" x:Key="listViewItemStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
在ViewModel中绑定数据源,F5运行,就能看到文章一开始的效果了。
最后的最后,附上本文用到的Demo:https://gitee.com/dswfort/BlogDemos/tree/master/ChartBubbleSample
2020年5月2日星期六