[MAUI]在.NET MAUI中复刻苹果Cover Flow

@

目录
  • 原理
    • 3D旋转
    • 平行变换
  • 创建3D变换控件
    • 绘制封面图片
    • 应用3D旋转
    • 应用平行变换
    • 绘制倒影
    • 创建绑定属性
  • 创建绑定数据
  • 创建布局
    • 计算位置
    • 计算3D旋转
  • 创建动效
  • 项目地址

Cover Flow是iTunes和Finder中的一个视图选项,允许用户使用水平滚动的图像查看他们的音乐库或文件。

2007年9月5日iPod classic/nano3/touch在同一场发布会上发布,苹果首次向我们展示了Cover Flow

在这里插入图片描述

在iOS7之前的“音乐”App中,旋转设备90度,或在iTunes中的“查看”下,选择“Cover Flow”都可以进入到Cover Flow视图。

Cover Flow的交互设计非常优秀:通过指尖滑动从堆叠的专辑库中翻动和挑选一张专辑的交互方式不仅有趣,而且在有限的屏幕空间内,展现了更多的专辑封面。

但由于流媒体时代弱化了专辑的概念,拟物化设计退潮以及设备性能/续航等方面的考虑,苹果逐步放弃了Cover Flow。

在2012年新发布的iTunes 11,2013年新发布的iOS 7,以及2018年发布的MacOS Mojave中删除了Cover Flow界面,Gallery View取而代之

那个是乔布斯时代的苹果——使事情变得简单和有趣。最近我很怀念这个功能,但由于我手头上已经没有任何一台设备能访问这个功能了。于是在
.NET MAUI 中复刻了Cover Flow。

在这里插入图片描述

在这里插入图片描述

使用.NET MAUI实现跨平台支持,本项目可运行于Android、iOS平台。

原理

实际上,Cover flow的原理非常简单,核心算法是对专辑图片进行3D变换(3DTransform)。

.NET MAUI 并没有直接提供3D变换,但我们可以通过SkiaSharp来实现。

PS: Skia 本身是一个开源图形库,它提供适用于各种语言和硬件平台的通用 API,(如 C++/Qt、Chrome、Android、iOS等 ),根据本博文提到的算法,你可以用Skia尝试在你擅长的平台上实现相同的效果。

3D旋转

视图元素的3D变换(3DTransform)中,有一类是以视图元素的Y或X轴作为旋转中心做旋转,称之为3D旋转,除了专业的程序设计领域外,经常使用图形处理工具,甚至是ppt的同学可能都熟悉这个概念。在ppt中插入图形,设置形状格式,可以看到“三维旋转”的选项,如下图:

在这里插入图片描述

这里涉及到一个透视的概念,透视是指在视觉上,远处的物体比近处的物体小,来思考一下,在现实世界中要看到同样大小的物体,可以离得很近,视野变大,物体的畸变会变大。也可以离得很远,用一个望远镜去看,视野变小,物体的畸变也会变小。透视参数就是在屏幕中模拟了现实世界中近大远小透视效果,我简单用ptt做一个演示:

在这里插入图片描述

三个图形沿Y轴方向旋转, 从左到右透视距离依次减小,透视角度依次增大,换句话说是离得更近,视野变大,物体的畸变变大。

在大多数支持3D旋转的图形系统中都会包含透视这个参数变量,如css中的perspective亦或是ppt中的“透视”格式。

在Skia中,3D变换是通过矩阵乘法实现的,这里需要大致了解数字图像处理的基本知识,可以参考这里。

矩阵乘法就是把原始图像矩阵的横排和变换矩阵的竖排相应位相乘,将结果相加。

在二维空间,原始图像中的每个像素点 (x,y) 所代表的单列矩阵,通过变换矩阵相乘,得到新的像素点 (x',y')。
例如缩小图像:

在这里插入图片描述

因为要考虑平移等非线性计算,常用3*3的矩阵来表示变换
在三维空间,用一个4*4的矩阵来表示变换,例如围绕Y轴旋转的变换矩阵如下:

|  cos(α)  0  –sin(α)  0  |
|    0     1     0     0  |
|  sin(α)  0   cos(α)  0  |
|    0     0     0     1  |

平行变换

另外涉及到的图像处理是平行变换(Skew),每一个平台上的值可能不同,但是原理都是通过增加或减少X轴或Y轴的值来实现平行变换。

在这里插入图片描述

在Skia中,根据参数值转换 x' 后的值随着 y 增加而增加。 这就是导致倾斜的原因。

如有一个200*100的图形,其左上角位于 (0、0) 的点上,并且呈现 xSkew 值为 1.5,则以下并行影像结果如下:

在这里插入图片描述

底部边缘 y 的坐标值为 100,因此将 150 像素移向右侧。

接下来我们用代码实现3D变换

创建3D变换控件

我们还是以分治的思路实现,图片变换由控件内部实现,平移及动画由控件外部实现。

新建.NET MAUI项目,命名Coverflow。将界面图片资源文件拷贝到项目\Resources\Raw中并将他们包含在MauiImage资源清单中。

<ItemGroup>
	  <MauiImage Include="Resources\Raw\*.jpg" />
</ItemGroup>

在项目中添加SkiaSharp绘制功能的引用Microsoft.Maui.Graphics.Skia以及SkiaSharp.Views.Maui.Controls

<ItemGroup>
    <PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="7.0.59" />
    <PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.3" />
</ItemGroup>

创建3D变换的图片控件RotationImage.xaml,代码如下:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:forms="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
             x:Class="Coverflow.RotationImage">

        <forms:SKCanvasView x:Name="canvasView"
                           Grid.Row="8"
                           PaintSurface="OnCanvasViewPaintSurface" />

</ContentView>

绘制封面图片

在RotationImage.xaml.cs中添加代码:

SKBitmap对象

public SKBitmap bitmap { get; private set; }

初始化方法,以及在图形大小变化时应用初始化

private async void RotationImage_SizeChanged(object sender, EventArgs e)
{
    await InitBitmap();
}

private async Task InitBitmap()
{
    using (Stream stream = await FileSystem.OpenAppPackageFileAsync("./15.jpg"))
    {
        if (stream!=null)
        {
            var mainDisplayInfo = DeviceDisplay.Current.MainDisplayInfo;
            var pixcelHeight = mainDisplayInfo.Density*200;
            var pixcelWidth = mainDisplayInfo.Density*200;

            var bitmap = SKBitmap.Decode(stream);
            bitmap= bitmap.Resize(new SKImageInfo((int)pixcelHeight,
                (int)pixcelWidth),
                SKFilterQuality.Medium);
            this.bitmap=bitmap;
        }

    }
}

初始化时将读取图片资源文件,然后将图片缩放到200*200的大小。

注意此处使用mainDisplayInfo.Density将MAUI各平台的逻辑分辨率转为图片的真实分辨率

此时在画布中绘制了一个简单的200*200专辑封面图片

在这里插入图片描述

应用3D旋转

在Skia用SKMatrix44类来描述4*4的变换矩阵,同时提供了 CreateRotation 和 CreateRotationDegrees 方法,可用于指定旋转围绕的轴

RotationImage_SizeChanged中,添加代码如下:


SKMatrix matrix = SKMatrix.CreateTranslation(-xCenter, -yCenter);

SKMatrix44 matrix44 = SKMatrix44.CreateIdentity();
matrix44.PostConcat(SKMatrix44.CreateRotationDegrees(1, 0, 0, (float)0));
matrix44.PostConcat(SKMatrix44.CreateRotationDegrees(0, 1, 0, (float)25));
matrix44.PostConcat(SKMatrix44.CreateRotationDegrees(0, 0, 1, (float)0));

SKMatrix44 perspectiveMatrix = SKMatrix44.CreateIdentity();
perspectiveMatrix[3, 2] = -1 / 800;
matrix44.PostConcat(perspectiveMatrix);

matrix= matrix.PostConcat(matrix44.Matrix);

matrix= matrix.PostConcat(
    SKMatrix.CreateTranslation(xCenter, yCenter));

将变换矩阵应用到画布中

canvas.SetMatrix(matrix);

此时在画布中专辑封面图片以800的透视距离,绕Y轴旋转25度

应用平行变换

首先计算倾斜角度,如有一个200*100的图形,其左上角位于 (0、0) 的点上,图中的角度α:

在这里插入图片描述

150 像素到 100 像素垂直方向的比率是该角度的正切值,即 56.3 度。

RotationImage_SizeChanged中,对matrix对象应用平行变换

matrix.SkewY =  (float)Math.Tan(Math.PI * (float)15 / 180);

此时在画布中专辑封面图片以15度平行变换

在这里插入图片描述

绘制倒影

在cover flow中,封面图片包含倒影效果。

之前的绘制的封面图片,在控件中央(也是画布中央)的位置。为了放置倒影后仍然处于控件中心,画布应该一分为二:上半部分绘制封面图片,下半部分绘制倒影。

更改代码:

//float yBitmap = yCenter - bitmap.Height / 2;
float yBitmap = yCenter-bitmap.Height;

绘制倒影封面图片:

using (SKPaint paint = new SKPaint())
{
    paint.Color = SKColors.Black.WithAlpha((byte)(255 * 0.8));
    canvas.Scale(1, -1, 0, yCenter);
    canvas.DrawBitmap(bitmap, xBitmap, yBitmap, paint);
    SKRect rect = SKRect.Create(xBitmap, yBitmap, bitmap.Width, bitmap.Height);
    canvas.DrawRect(rect, paint);
}

倒影用一个黑色半透明的矩形覆盖在原始封面图片上,并且将画布沿Y轴翻转,使得倒影图片在封面图片的下方。

在这里插入图片描述

创建绑定属性

将图片源,旋转角度,平行角度等作为绑定属性,以便在XAML中绑定。代码忽略。

在这里插入图片描述

创建绑定数据

创建MainPageViewModel.cs,用于界面绑定数据源。

AlbumInfo描述专辑信息

public class AlbumInfo
{
    public AlbumInfo() { }

    public string AlbumName { get; set; }
    public string AlbumArtSource { get; set; }
}

在MainPageViewModel构造函数中,初始化AlbumInfo列表,在控件中绑定此列表作为数据源

创建布局

在MainPage.xaml中,创建一个Grid作为专辑封面容器,我们将使用绑定集合的方式,将专辑封面添加到这个容器中。

代码如下:

<Grid Grid.Row="1"
    x:Name="BoxLayout"
    Background="black"
    BindableLayout.ItemsSource="{Binding AlbumInfos}">

它的DataTemplate代表一个专辑信息,使用Grid布局,专辑封面图片与专辑名称分别位于Grid的第一行和第二行。

<BindableLayout.ItemTemplate>
    <DataTemplate>
        <Grid Style="{StaticResource BoxFrameStyle}"
                Background="Transparent">
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition Height="auto"></RowDefinition>
            </Grid.RowDefinitions>
            <controls:RotationImage WidthRequest="200"
                                    HeightRequest="500"
                                    ImageWidth="200"
                                    ImageHeight="200"
                                    Source="{Binding AlbumArtSource}"></controls:RotationImage>


            <Label Margin="0,30,0,0"
                    Text="{Binding AlbumName}"
                    HorizontalTextAlignment="Center"
                    VerticalOptions="Center"></Label>


        </Grid>
    </DataTemplate>

</BindableLayout.ItemTemplate>

对专辑封面Grid的样式进行定义:

<ContentPage.Resources>
   <Style TargetType="Grid"
               x:Key="BoxFrameStyle">
            <Setter Property="HeightRequest"
                    Value="100"></Setter>
            <Setter Property="WidthRequest"
                    Value="100"></Setter>
            <Setter Property="HorizontalOptions"
                    Value="Center"></Setter>
            <Setter Property="VerticalOptions"
                    Value="Center"></Setter>
        </Style>
</ContentPage.Resources>

效果如下:

在这里插入图片描述

计算位置

Cover Flow的滑动交互由两种方式实现:1. 左右轻扫屏幕,切换到上一张或下一张专辑封面;2. 拨动底部Slider控件,切换到指定的专辑封面。

两种方式都会改变当前位置,我们将当前位置定义为一个整数,表示当前专辑在容器中的索引。

private int currentPos;

当手势触发时,根据手势方向,改变当前位置:

this.currentPos=e.Direction==SwipeDirection.Right
    ? Math.Max(0, this.currentPos-1)
    : Math.Min(this.BoxLayout.Children.Count-1, this.currentPos+1);

当Slider控件的值发生变化时,根据Slider的值,计算当前位置:


var currentPos = (int)Math.Floor(e.NewValue*  (this.BoxLayout.Children.Count-1));
if (this.currentPos!=currentPos)
{
    this.currentPos = currentPos;
}

当前位置索引的值始终在0到专辑封面数量减1之间。

当前封面是从专辑堆叠中挑选出来的,它的位置是固定的,左右两边的封面相对于当前封面,有一个固定的距离,step为当前封面和左右第一张封面之间的距离,slidePadding为其它封面和当前封面之间的距离。

在这里插入图片描述

其它封面的位置,分为两种情况:1. 在当前封面的左边;2. 在当前封面的右边。

封面叠层的顺序是当前封面最靠上,左右两边的封面随着距离由近及远,依次向下叠放。

创建RenderTransform方法,作为刷新的入口,当当前位置发生变化时,调用此方法,重新计算每个专辑封面的位置和叠放顺序。

private void RenderTransform(int currentPos)
{
    var step=40.0;
    var currentSlidePadding=100.0;
    foreach (var bitmapLayout in this.BoxLayout.Children)
    {
        var pos = this.BoxLayout.Children.IndexOf(bitmapLayout);
        double xBitmap;
        int zIndex;
        if (pos < currentPos)
        {
            zIndex=pos;
            xBitmap = (double)(-(currentPos * step) + (pos * step)  - currentSlidePadding);
        }
        else if (pos > currentPos)
        {
            zIndex=this.BoxLayout.Children.Count-pos;
            xBitmap = (double)(((pos - currentPos) * step)  + currentSlidePadding);
        }
        else
        {
            xBitmap =  0;
            zIndex=this.BoxLayout.Children.Count;
        }

        (bitmapLayout as VisualElement).ZIndex = zIndex;
        (bitmapObj as RotationImage).TranslationX=xBitmap;
    }
}

创建后,运行效果如下

在这里插入图片描述

计算3D旋转

我们对当前封面的左边的封面,以及当前封面的右边的封面,分别计算旋转角度,以实现3D效果。

var rotateY = 65;
foreach (var bitmapLayout in this.BoxLayout.Children)
{
    double targetRotateY;
    if (pos < currentPos)
    { 
        targetRotateY=rotateY;
    }
    else if (pos > currentPos)
    {   
        targetTransY=transY;
    }
    else
    {
        targetTransY=0;
    }
    (bitmapObj as RotationImage).RotateY=targetRotateY;
}

在这里插入图片描述

再对3D旋转的封面进行平行变换调整,并对封面位置作微调

var rotateY = 65;
var skewY = 0;
var transY = 0;
foreach (var bitmapLayout in this.BoxLayout.Children)
{
   
    double targetRotateY;
    double targetSkewY;
    double targetTransY;
    if (pos < currentPos)
    {
      
        targetRotateY=rotateY;
        targetSkewY=skewY;
        targetTransY=-transY;

    }
    else if (pos > currentPos)
    {
       
        targetRotateY=-rotateY;
        targetSkewY=-skewY;
        targetTransY=transY;
    }
    else
    {
        targetRotateY=0;
        targetSkewY=0;
        targetTransY=0;

    }

    (bitmapObj as RotationImage).RotateY=targetRotateY;
    (bitmapObj as RotationImage).TranslationX=xBitmap;
    (bitmapObj as RotationImage).SkewY=targetSkewY;
    (bitmapObj as RotationImage).TransY=targetTransY;
}

最后配置封面图片的缩放,以及封面标题显示、隐藏。

效果如下:

在这里插入图片描述

至此我们完成了静态的工作内容,下一步要让界面的过渡动画更加流畅,我们将使用MAUI的动画框架,实现平滑的过渡动画。

在这里插入图片描述

创建动效

我们通过创建Animation对象,添加子动画来实现。详情请参考Animation子动画。

RotateY、SkewY、TranslationX、Scale直接赋值的方式将由动画代替。动画是一种缓动机制,通过属性的缓慢改变实现平滑的过渡动画。

在渲染中我们为每一个封面创建一个Animation对象,然后添加子动画,最后调用Animation对象的Commit方法,

在400ms内将各属性缓慢应用到界面上。各属性步调一致,所以动画的过程是平滑的。

foreach (var bitmapLayout in this.BoxLayout.Children)
{
    uint duration = 400;
    ...

    Animation albumAnimation = new Animation();


    var originTranslationX = (bitmapLayout as VisualElement).TranslationX;
    var originScale = (bitmapLayout as VisualElement).Scale;
    var animation1 = new Animation(v => (bitmapLayout as VisualElement).TranslationX = v, originTranslationX, xBitmap, Easing.CubicInOut);
    var animation2 = new Animation(v => (bitmapLayout as VisualElement).Scale = v, originScale, targetScale, Easing.CubicInOut);


    if (targetSkewY!=(bitmapObj as RotationImage).SkewY)
    {
        var animation4 = new Animation(v => (bitmapObj as RotationImage).SkewY = v, (bitmapObj as RotationImage).SkewY, targetSkewY, Easing.CubicInOut);
        albumAnimation.Add(0, 1, animation4);

    }

    if (targetRotateY!=(bitmapObj as RotationImage).RotateY)
    {
        var animation3 = new Animation(v => (bitmapObj as RotationImage).RotateY = v, (bitmapObj as RotationImage).RotateY, targetRotateY, Easing.CubicInOut);
        albumAnimation.Add(0, 1, animation3);

    }

    if (targetTransY!=(bitmapObj as RotationImage).TransY)
    {
        var animation5 = new Animation(v => (bitmapObj as RotationImage).TransY = v, (bitmapObj as RotationImage).TransY, targetTransY, Easing.CubicInOut);
        albumAnimation.Add(0, 1, animation5);

    }
    albumAnimation.Add(0, 1, animation1);
    albumAnimation.Add(0, 1, animation2);

    albumAnimation.Commit((bitmapLayout as VisualElement), "AlbumArtImageAnimation", 16, duration);
}

效果如下:

在这里插入图片描述

在页面大小变化时,重新渲染变换。

    private void MainPage_SizeChanged(object sender, EventArgs e)
    {
        RenderTransform(currentPos);
    }

step和currentSlidePadding值将由屏幕宽度计算得出,使得在不同屏幕大小设备,或者横竖屏切换时,效果保持一致。

var xCenter = this.BoxLayout.Width / 2;
var step = xCenter*0.12;
var currentSlidePadding = this.BoxLayout.Width * 0.15;

在这里插入图片描述

项目地址

Github:maui-samples

关注我,学习更多.NET MAUI开发知识!

本文来自博客园,作者:林晓lx,转载请注明原文链接:http://www.cnblogs.com/jevonsflash/p/17419483.html

本文转载于网络 如有侵权请联系删除

相关文章

  • MATLAB中导入数据:importdata函数

    大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。用load函数导入mat文件大家都会。可是今天我拿到一个数据,文件后缀名竟然是‘.data’。该怎么读呢?我仅仅好用matlab界面Workspace区域的“importdata”button手工导入该文件。恩,还好,竟然成功了。顺便提一下,这个“importdata”button功能非常强大,连excel文件都能导入。可是假设在脚本里怎样导入这样的非mat文件呢?这时候就轮到“importdata”函数登场啦!———————————————————————importdataLoaddatafromfile Syntaximportdata(filename) A=importdata(filename) A=importdata(filename,delimiter) A=importdata(filename,delimiter,nheaderlines) [A,delimiter]=importdata(…) [A,delimiter,nheaderlines]=importdata(…) […]=importdat

  • OJ术语: AC、WA、TLE、OLE、MLE、RE、PE、CE「建议收藏」

    大家好,又见面了,我是你们的朋友全栈君。起因看到一些术语不清楚是什么意思,上网查阅相关资料后,归纳如下。汇总简写全称中文称谓OJOnlineJudge在线判题系统ACAccepted通过WAWrongAnswer答案错误TLETimeLimitExceed超时OLEOutputLimitExceed超过输出限制MLEMemoryLimitExceed超内存RERuntimeError运行时错误PEPresentationError格式错误CECompileError无法编译发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/148342.html原文链接:https://javaforall.cn

  • mysql之日期函数

    文章目录进阶4:常见函数之日期函数三、日期函数now返回当前系统日期+时间curdate返回当前系统日期,不包含时间curtime返回当前时间,不包含日期可以获取指定的部分,年、月、日、小时、分钟、秒str_to_date将字符通过指定的格式转换成日期案例1:查询入职日期为1992-4-3的员工信息date_format将日期转换成字符案例1:查询有奖金的员工名和入职日期(xx月/xx日xx年)进阶4:常见函数之日期函数以下如图数据库为例编写案例三、日期函数now返回当前系统日期+时间SELECTNOW();复制curdate返回当前系统日期,不包含时间SELECTCURDATE();复制curtime返回当前时间,不包含日期SELECTCURTIME();复制可以获取指定的部分,年、月、日、小时、分钟、秒SELECTYEAR(NOW())年; SELECTYEAR('1998-1-1')年;复制SELECTYEAR(hiredate)年FROMemployees;复制SELECTMONTH(NOW())月; SELECTMONTHNAME(NOW())月;复制st

  • java中基本类型boolean在jvm中的具体实现

    在前面在java中boolean类型占多少字节?一文中,对java的基本数据类型,boolean进行过一些简单的分析。在该文中得出,java的boolean类型,实际上存储的时候是4Byte,boolean的操作与int无异。但是在boolean数组中,则每个boolean的长度为1Byte。最近在极客时间学习深入拆解Java虚拟机专栏的时候,也看到类似的问题,现在按照极客时间学习的思路,对boolean的使用进行验证。$echo' publicclassFoo{ publicstaticvoidmain(String[]args){ booleanflag=true; if(flag)System.out.println("Hello,Java!"); if(flag==true)System.out.println("Hello,JVM!"); } }'>Foo.java $javacFoo.java $javaFoo $java-cp/path/to/asmtools.jarorg.openjdk.asmtools

  • Linux之目录结构详解

    对于每一个Linux学习者来说,了解Linux文件系统的目录结构,是学好Linux的至关重要的一步.,深入了解linux文件目录结构的标准和每个目录的详细功能,对于我们用好linux系统只管重要,下面我们就开始了解一下linux目录结构的相关知识。当在使用Linux的时候,如果您通过ls–l/就会发现,在/下包涵很多的目录,比如etc、usr、var、bin......等目录,而在这些目录中,我们进去看看,发现也有很多的目录或文件。文件系统在Linux下看上去就象树形结构,所以我们可以把文件系统的结构形象的称为树形结构。文件系统的是用来组织和排列文件存取的,所以她是可见的,在Linux中,我们可以通过ls等工具来查看其结构,在Linux系统中,我们见到的都是树形结构;比如操作系统安装在一个文件系统中,他表现为由/起始的树形结构。linux文件系统的最顶端是/,我们称/为Linux的root,也就是Linux操作系统的文件系统。Linux的文件系统的入口就是/,所有的目录、文件、设备都在/之下,/就是Linux文件系统的组织者,也是最上级的领导者。由于linux是开放源代码,各大公司和团

  • 2018-11-19 Neo4j百万级数据导入只能用neo4j-import

    image.png业务需要使用Neo4j出数据关系展示图,数据库里有2张表通过一个字段进行关联,数据量是90万和500万,关系量是150w;从一开始使用RESTAPI循环导入,但创建节点没有问题,但是要通过将数据导入内存再生出关联关系就出现内存不足了;后来通过cypher语句,loadcsv来创建节点和关系,创建节点时,数据超过20w条就不行了,创建关系更是慢的不行,注意:windows下loadcsv文件路径为:file:/d:/csv/company.csv,官网上写的貌似不行;以上2种方法可以对少量数据进行操作。 百万级数据可以使用下面这种方法: 1、先生成csv文件,按格式来:文件名:company-header.csv 内容: regno,name,id:ID 文件名:company.csv 内容: 1234,apple,c001 文件名:person.csv 内容: cerno,name,id:ID 3201,jobs,p001 文件名:relationship.csv 内容: :START_ID,:END_ID,:TYPE p001,c001,creator复制然后通过n

  • EngineerCMS与ProjectWise对比

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/details/75208908 二者都是针对工程设计过程中的资源管理。工具/原料EngineerCMS BentleyProjectWise 方法/步骤1 安装角度,PW是非常巨大的服务器和客户端软件,看看下图的服务器群多么庞大,安装完服务器,配置,客户端也要安装。而EngineerCMS只是一个轻巧的exe微服务,直接点击就运行了。用浏览器访问即可。 2 Ecms一键生成一个项目的6个阶段*10个专业*5种文档类型*……;而PW需要一个个建立,或从其他项目拷贝。 3 Ecms直接发布图文并茂的网页文章,如设代日期,记录现场的施工图片进行分享,PW需要结合sharepoint发布文章,或者只能先做成word,再放到PW中。 4 Ecms上传文档,直接将编号和名称分离。 5 ECMS校审流程简单。 6 PW校审流程比较复杂。 7 Ecms免费开源,PW商业收费。 前者可自由二次开发,后者提供API二次开发接口。 前者提

  • 谈谈我理解中的价值

    前言 好久没有写文字了,都不知道自己到底在忙些什么更有价值的事情,2017年也没剩下多少时间,年前信誓旦旦说要实现的目标也想不起来了,人就是这么健忘,总是这么自欺欺人,老板明年的兰博基尼还能实现么,KPI又实现了多少了呢?其实并不是没有东西写,而是时间都花在打王者荣耀了,以前我也是不怎么玩游戏的,在今年尝试玩了之后就中毒了,拿着公司每个月的福利肆意挥霍,打了两个赛季还是没上王者,看来也是没有什么玩游戏的天赋,以后尽量少玩吧,多把时间花在其他更有价值的事情上,比如多写写文章跟大家分享下近期的一些思考,对吧。今天要聊的一个主题就是:价值。为什么会想写这个主题,其实也没有为什么,只是突然觉得这个主题对我来说是一个重新审视自己价值的思考点,也是能够引发大家思考的问题。从角色出发我们在现实当中可能会承担不同的角色,以我为例,我在现实当中承担以下这些角色:对父母而言:我承担的角色是儿子,要说我之所以能有今天,不在工地里搬砖也是在写字楼里搬砖,很大程度是父母对我无私的付出,忙活大半辈子让我读多了点书,这才能看起来比较光鲜的在大城市里吃地沟油,这里开个玩笑。说起儿子这个角色的价值,虽然父母对我是无私的

  • 我的类为什么使用不了了?

    前言在调用类时,如果出现错误信息“当前上下文中不存在名称***”,这时候该如何处理,我们来一起总结一下。错误情况1在同一项目中使用“TestClass2”这个类时出现错误。 【分析】:这个错误比较明显,图中将“TestClass2”写在了“TestClass1”中,类在使用中是不允许出现嵌套的,否则就是我们常说的“类中类”,必须杜绝这样的编程失误。解决方案只要将“TestClass2”从“TestClass1”中移出来即可。错误情况2在同一项目中使用“TestClass2”这个类时出现错误。【分析】:“TestClass1”中只有一个方法,没有其他类,所以不存在“类中类”的情况。仔细观察,发现Program的命名空间为“thinger.com”,而“TestClass1”的命名空间为“thinger.com.cn”,两者的命名空间不一致。解决方案将命名空间改为一致即可。错误情况3在同一项目中使用“TestClass1”这个类时出现错误。【分析】:类名称为“TestClass1”,而使用时却将类名称写成了“TestClass11”。解决方案将类名改为“TestClass1”即可。错误情况

  • 史上最硬核的Linux命令大全,还不收藏? ❤️【通俗易懂,小白一看就会】「建议收藏」

    目录?前言?命令汇总?文件管理1️⃣ls命令–显示指定工作目录下的内容及属性信息2️⃣cp命令–复制文件或目录3️⃣mkdir命令–创建目录4️⃣mv命令–移动或改名文件5️⃣pwd命令–显示当前路径?文档编辑1️⃣cat命令–在终端设备上显示文件内容2️⃣echo命令–输出字符串或提取Shell变量的值3️⃣rm命令–移除文件或目录4️⃣tail命令–查看文件尾部内容5️⃣rmdir命令–删除空目录?系统管理1️⃣rpm命令–RPM软件包管理器2️⃣find命令–查找和搜索文件3️⃣startx命令–初始化X-windows4️⃣uname命令–显示系统信息5️⃣vmstat命令–显示虚拟内存状态?磁盘管理1️⃣df命令–显示磁盘空间使用情况2️⃣fdisk命令–磁盘分区3️⃣lsblk命令–查看系统的磁盘4️⃣hdparm命令–显示与设定硬盘参数5️⃣vgextend命令–扩展卷组?文件传输1️⃣tftp命令–上传及下载文件2️⃣curl命令–文件传输工具3️⃣fsck命令–检查并修复Linux文件系统4️⃣ftpwho命令–显示ftp会话信息5️⃣lprm命令–删除打印队列中的打

  • Kestrel服务器ASP.NetCore 3.1程序启用SSL

    VS2019向导建立的MVC、webapi类型的解决方案只要勾选启用SSL(https)使用IISExpress来调试程序时VS自动就给我们配置好了,项目启用SSL。我们这次使用IIS自动生成的本地证书来完成Kestrel启用SSL。 生成pfx证书 开发环境证书就用iis默认的本地证书即可, 进入管理器:点击服务器证书选项 选中以下本地默认证书后右键导出,指定路径和密码点击确认.      代码:   publicclassProgram { publicstaticvoidMain(string[]args) { if(args.Length>0) { Console.WriteLine(DateTime.Now+"->CommandLineArgs:"+string.Join("|",args)); } CreateHostBuilder(args).Build().Run(); } publicstaticIHostBuilderCreateHostBuilder(string[]args)=> Host.C

  • JQuery选择器

    选择器 实例 解释 基本选择器 * $(*) 选取所有元素 #id $(“#a”) 选取id=a的元素 .class $(“.b”) 选取class=b的元素 element $(“p”) 所有<p>元素 .class.class $(“.a.b”) 选取所有class=a且class=b的元素 基本过滤选择器 :first $(“p:first”) 选取第一个p元素 :last $(“p:last”) 选取最后一个p元素 :odd $(“p:odd”) 选取所有奇数位上的p元素 :even $(“p:even”) 选取所有偶数位上的p元素 :ep(n) $(“p:eq(n)”) 选取第n位p元素(index从0开始) :gt(n) $(“li:gt(n)”) 选取第n位之前的所有li元素(不包含第n位) :lt(n) $(“li:lt(n)”) 选取第

  • svn 外链

    https://blog.csdn.net/wuqingyidongren/article/details/51953162

  • 小程序中校验中文字输入,附小程序常用正则表达式

    同事小刚:小程序里怎么判断只能输入中文字符啊? 我:淦!小程序里不知道中文字符怎么判断,js总知道了吧… 同事小刚:正则!是正则吗,小程序里也可以用吗? 我:当然了,哈哈,亏你每个月能拿上万工资呢~ 先解决小刚的问题(其他两个附赠的): const chinese = /[^\u4E00-\u9FA5]/gconst number = /[^\d]/g;const englishAndNum =/[\W]/gif ((chinese.test(this.data.username))) {    wx.showToast({ title: "只允许输入中文", icon: "none" });    return false;}if ((number.test(this.data.username))) {  

  • 802.11网络协议细节(四)

         1.4  802.11对上层协议的封装 和所有其他的802链路层一样,802.11可以传输各种不同的网络层协议。和以太网不同的是,802.11是以802.2的逻辑链路控制封装来携带上层协议。图1-13显示了如何以802.2LLC封装来携带IP封包。如该图所示,802.1H与RFC1042所使用的『MAC标头』长度为12个bit组,其内容为以太网上的『源MAC地址』与『目的MAC地址』,或者前面所提到的长标头(long802.11MACheader)。                 图1-13:802.11里的IP封装 传输时,用来封装LLC数据的方式有两种。其中一种是RFC1042所描述的方式,另外一种则是802.1H所规范的方式。两种标准各自有其别名。RFC1042有时候被称为IETF封装,而802.1H有时候则被称为隧道式封装(tunnelencapsulation). 这两种方式极为相似,如图1-13所示。此图最上方为以太网帧,它具有MAC标头(源与目的MAC地址),类型代码(typecode),内

  • 导致MySQL索引失效的几种常见写法

      数据准备 先准备一些数据,方便测试 创建表结构 CREATETABLEUSER( idINT(5)UNSIGNEDNOTNULLAUTO_INCREMENT, create_timeDATETIMENOTNULL, NAMEVARCHAR(5)NOTNULL, ageTINYINT(2)UNSIGNEDZEROFILLNOTNULL, sexCHAR(1)NOTNULL, mobileCHAR(12)NOTNULLDEFAULT'', addressCHAR(120)DEFAULTNULL, heightVARCHAR(10)DEFAULTNULL, PRIMARYKEY(id), KEYidx_createtime(create_time)USINGBTREE, KEYidx_name_age_sex(NAME,sex,age)USINGBTREE, KEYidx_height(height)USINGBTREE, KEYidx_address(address)USINGBTREE, KEYidx_age(age)USINGBTREE )ENGINE=INNODBA

  • 像爬旋转楼梯一样

      由于谁都不愿虚度此生,才会促使我们认真思考自身以及自身周围的情和物。 左右我的行为是什么?   个体对利润的追求促进社会的繁荣?   能够找到适合自己的工作,这样的人是幸福的。什么工作和职业适合自己,自己想做什么工作,学生时代常常为此而烦恼。无论如何这是关系到自己一生的大事。 现在的社会,即资本主义社会里,大部分人为赚钱生活费而工作。上班高峰期的痛苦拼挣,对公司上司无理要求的忍耐,这一切都是为了生活。   画家在作品完成之后方知自己想画的是什么。展现在画家面前的作品,已由含糊的概念转变为美丽的画卷。 黑格尔将此表述为“对象化”、“外在化”。精神、才能这样抽象的事物是以具体形式来展现的。   我们也是通过自己的工作成果了解自我的。个人的人性,个性或能力,素质已体现在他的工作中。   黑格尔的“辩证法”是一部成长剧。人类的精神、能力通过行为转变成具体形态。黑格尔称此为“对象化”、“外在化”。人类正是因此才得知自身的优缺点,而且通过“反省”这一理性行为认清问题之所在并进行下一个阶段。   我们看似在同一个地方绕圈子,

  • python在一堆目录中寻找json文件

    在一个目录下,有好几层目录,里面零零散散存放着几个json文件,我要做的是用python脚本把它们都找出来,一开始就一层一层地找,用os.listdir加上for循环,根据目录树的深度确定for循环的数量,很显然,不够灵活,也效率太低 换一种方法,使用os.walk,简直是神器,它可以递归查找某一目录下的所有目录及文件,一个不落 forxinos.walk('./'): print([x[0]+targetfortargetinx[2]if'json'intarget])复制 os.walk()方法返回一个generator,需要for循环来得到每个元素,这里用x代替,每个元素都是一个元组,长度为三,类似下面这样(此处是windows环境,路径里出现了转义符\) ('../../201812/ok\\DX419529',[],['DX419529-1.jpg','DX419529.jpg','DX419529.json']) ('../../201812/ok\\DX419530',[],['DX419530-1.jpg','DX419530.jpg','DX419530.jso

  • 对机器学习中朴素贝叶斯的理解

    通常我们会根据样本X来推断属于某一类的概率,这个概率就叫后验概率P(c|x),概率最大的类就是我们要的结果。 其实,这个结果只是一种特例,最大化后验概率是最小化误判风险的一种情况,具体来说是误判损失为1的情况。 不管怎样,我们都需要求取后验概率,根据贝叶斯定理,后验概率可以由先验概率和类条件概率求出,先验概率可以根据属于某一类的样本数占总样本数的比例直接估计,剩下的问题在于求类条件概率。 当样本数有限时,样本X很难出现在训练集中,即P(x|c)为0,但是在训练集中没有出现不代表在整个样本的分布空间中不存在,所以不能根据样本出现的次数来直接估计。 这时候可以采用极大似然法,假定样本符合某一分布,例如正态分布,然后根据训练集样本来估计正态分布的均值和方差,而这个是有公式的。 不过这样做的问题在于,我们对样本的概率分布完全是猜测,并不一定与真实分布吻合,或者说很大可能不会吻合,哪有这么巧的事,样本就满足正态分布呢。 这个时候,朴素贝叶斯登场了。 类条件概率P(x|c)是所有属性的联合概率,难以从有限的样本直接估计得到,为避开这个问题,朴素贝叶斯采用属性条件独立性假设,对已知类别,假设所有属性

  • 用二分法确定蹦极运动员的体重(选修课大作业)

    用二分法确定蹦极运动员的体重 目录用二分法确定蹦极运动员的体重引言函数的建立二分法二分法的误差估计MATLAB的M文件:bisect 引言 医学研究表明,如果在自由下落4s后自由落体速度超过了36m/s,那么碰击运动员持续大幅振动损伤的概率会显著增加。蹦极运动公司的老板希望确定,在给定0.25kg/m的阻力系数下超越该准测的蹦极运动员体重是多少。 函数的建立 我们知道可以运用如下解析解预测作为时间函数的下落速度: \[v(t)=\sqrt{gm/c_d}*\tan(h)(\sqrt{gc_d/m}*t) \]尽可能的尝试,但是无论如何也都无法将该方程变换为显式地表示m,也就是说,无法将质量单独分离出来放在方程的左边。 可以用另外一种方式看待这个问题,就是在方程的两边同时减去\(V(t)\),可以得到一个新函数: \[f(m)=\sqrt{gm/c_d}*\tan(h)(\sqrt{gc_d/m}*t)-v(t) \] 二分法 二分法是增量搜索方法的一个变种,在增量搜索方法中,区间每次总是折半。如果一个函数的符号在一个区间上发生了变化,那么就计算改函数在区间中点处的值。然后,就可以确定

  • 【jmeter】01 初识别

    1.什么是jmeter Apache 下的一款开源软件,一般用于服务端 性能、压力、接口测试 为什么使用jmeter?    轻量级、免安装|对新手友好,windows 端有界面,可导出脚本 2.jmeter 安装和目录结构 下载zip 版本,然后直接解压。需不需要配置环境变量?可配到bin   \bin\jmeter.propertiesjmeter的配置文件 3.jmeter快速上手 (1)新建测试计划 (2)新建用户线程(测试场景)   设置线程数、上线总时间、循环次数 (3)新建HTTP请求---保存为jmx文件 (4)添加监视器\查看结果树    (5)调用接口,查看返回值

相关推荐

推荐阅读