@

今天来谈一谈MAUI跨平台技术的核心概念——跨平台控件。

无论是MAUI,Xamarin.Forms还是其它的跨平台技术,他们是多个不同平台功能的抽象层,利用通用的方法实现所谓“一次开发,处处运行”。

跨平台框架需要考虑通用方法在各平台的兼容,但由于各原生平台(官方将原生称为本机)功能的差异,可能不能满足特定平台的所有功能。

比如,众所周知,MAUI的手势识别器没有提供长按(LongPress)手势的识别, TapGestureRecognizer也仅仅是按下和抬起的识别,没有提供长按的识别。

这时候就需要开发者自己实现特定平台的功能,这就是自定义控件。

要想重写控件,或增强默认控件的功能或视觉效果,最基础的功能就是要拿到跨平台控件,和本机控件。

通过跨平台控件定义的属性传递到本机控件,在本机控件中响应和处理自定义属性的变化。达到自定义控件的目的。

接下来介绍在MAUI新增的特性:控制器(Handler),好用但知道的人不多 。

Handler

因为跨平台控件的实现由本机视图在每个平台上提供的,MAUI为每个控件创建了接口用于抽象控件。 实现这些接口的跨平台控件称为 虚拟视图。 处理程序 将这些虚拟视图映射到每个平台上的控件,这些控件称为 本机视图

在VisualElement中的Handler对象是一个实现了IElementHandler接口的类,通过它可以访问 虚拟视图本机视图

public interface IViewHandler : IElementHandler
{
bool HasContainer { get; set; }
object? ContainerView { get; }
IView? VirtualView { get; } Size GetDesiredSize(double widthConstraint, double heightConstraint);
void PlatformArrange(Rect frame);
}

每个控件有各自的Handler以及接口,请查看官方文档

它可以通过注册全局的映射器,作为特定本机平台上实现自定义控件的功能的入口。

然后结合.NET 6 条件编译的语言特性,可以更加方便在但文件上,为每个平台编写自定义处理程序。

Entry是实现IEntry接口的单行文本输入控件,它对应的Handler是EntryHandler。

如果我们想要在Entry控件获取焦点时,自动全选文本。

  Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
{
#if ANDROID
handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
handler.PlatformView.EditingDidBegin += (s, e) =>
{
handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
};
#elif WINDOWS
handler.PlatformView.GotFocus += (s, e) =>
{
handler.PlatformView.SelectAll();
};
#endif
});

或者,可以使用分部类将代码组织到特定于平台的文件夹和文件中。 有关条件编译的详细信息,请参考官方文档

与Xamarin.Forms实现的区别

在Xamarin.Forms时代,已经提供了一套自定义控件的机制,呈现器(Renderer)。

Xamarin.Forms的控件,比如Entry是通过在封装于特定平台下的EntryRenderer的类中渲染的。

通过重写控件默认Renderer,可以完全改变控件的外观和行为方式。

  • Element,Xamarin.Forms 元素
  • Control,本机视图、小组件或控件对象

为什么要用Handler代替Renderer

虽然Renderer功能非常强大,但是绝大部分场景来说,不是每次都需要重写控件,而仅仅是给控件添加一些特定平台的增强功能,如果还需要重写OnElementPropertyChanged 将跨平台控件的属性值传输到本机控件,这种方式太过于复杂。

以我的理解,Handler是对Renderer的一种优化,它解决了Renderer的这些问题:Renderer和跨平台控件的耦合,对自定义控件的生命周期管理,和对自定义控件的更细粒度控制。

解耦

在Xamarin.Froms的Render中,要想拿到跨平台控件的属性,需要通过直接引用跨平台类型,这样就导致了Renderer和跨平台控件的耦合。

在MAUI中,处理程序会将平台控件与框架分离。平台控件只需处理框架的需求。这样的好处是处理程序也适用于其他框架(如 Comet 和 Fabulous)重复使用。

生命周期管理

可以通过处理程序的映射器(Mapper)在应用中的任意位置进行处理程序自定义。 自定义处理程序后,它将影响在应用中任意位置的该类型所有控件。

可以通过控件HandlerChanged 和HandlerChanging,管理Handler的生命周期,通过其参数可以获取控件挂载、移除Handler的时机,可以在这里做一些初始化和清理工作。

更细粒度的控制

因为实现了全局映射器注册,这样的好处还有不用重写子类控件,我们可以通过获取跨平台控件的某属性,或注解属性,拿到需要进行处理的控件。实现自由的面向切面的过滤。

用Effect来实现呢?

或者我们仅仅想更改控件外观,可以通过Effect来实现。但无论是Effect还是Renderer,他们只能是全局的,在需要状态维护的业务逻辑中,比如长按,实际上是按下,抬起的过程,没有按下的控件不要响应抬起,正因为这样要记录哪些控件已经按下,可能需要用一个字典维护所有的自定义控件。

而MAUI的自定义映射器实际上就是一个字典,减少了代码的复杂度。

在MAUI中,官方建议迁移到Handler。Renderer虽仍然可以在MAUI中使用,但是它们属于兼容方案(Compatibility命名空间),并且不提供ExportRenderer标签,需要在CreateMauiApp中手动添加:

.ConfigureMauiHandlers((handlers) =>
{
#if ANDROID
handlers.AddHandler(typeof(PressableView), typeof(XamarinCustomRenderer.Droid.Renderers.PressableViewRenderer));
#elif IOS
handlers.AddHandler(typeof(PressableView), typeof(XamarinCustomRenderer.iOS.Renderers.PressableViewRenderer));
#endif
});

从Renderer迁移到Handler的详细步骤,请参考官方文档

刚才说到,MAUI缺少长按的手势控制,

所谓长按(LongPress),实际上是将手指接触屏幕到离开屏幕的动作分解。当手指接触屏幕时,触发按下(Pressed)事件,当手指离开屏幕时,触发抬起(Released)事件。如果在按下和抬起之间的时间间隔超过一定的时间,就认为是长按。

对于这样简单的功能,MAUI团队并不打算将它加入到手势识别中。可能将这个需求下放给社区来实现,我在CommunityToolkit找到了这个issue(https://github.com/CommunityToolkit/Maui/issues/86)但是到目前为止,官方仅有的只是用Effect实现的手势识别案例(https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/effects/touch-tracking)

那么我们参考这个官方案例,在MAUI上实现一个长按的手势控制吧

自定义手势监听控件

定义可以监听的手势类别,分别是按下、移动、抬起、取消、进入、退出


public enum TouchActionType
{
Entered,
Pressed,
Moved,
Released,
Exited,
Cancelled
}

添加手势监听器TouchRecognizer,它将提供一个事件OnTouchActionInvoked,用触发手势动作。

public partial class TouchRecognizer: IDisposable
{
public event EventHandler<TouchActionEventArgs> OnTouchActionInvoked;
public partial void Dispose();
}

EventArg类TouchActionEventArgs,用于传递手势动作的参数

public long Id { private set; get; }

public TouchActionType Type { private set; get; }

public Point Location { private set; get; }

public bool IsInContact { private set; get; }

在各平台上实现TouchRecognizer

使用分布类(partial class)的方式,创建TouchRecognizer.iOS.csTouchRecognizer.Android.csTouchRecognizer.Windows.cs文件,分别在各平台上实现TouchRecognizer。在各平台上的实现代码不会混在一起,便于维护。

iOS中的实现

public partial class TouchRecognizer : UIGestureRecognizer, IDisposable
{
UIView iosView; public TouchRecognizer(UIView view)
{
this.iosView = view;
} public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt); foreach (UITouch touch in touches.Cast<UITouch>())
{
long id = touch.Handle.Handle.ToInt64();
InvokeTouchActionEvent(this, id, TouchActionType.Pressed, touch, true);
} } public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt); foreach (UITouch touch in touches.Cast<UITouch>())
{
long id = touch.Handle.Handle.ToInt64(); InvokeTouchActionEvent(this, id, TouchActionType.Moved, touch, true); }
} public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt); foreach (UITouch touch in touches.Cast<UITouch>())
{
long id = touch.Handle.Handle.ToInt64(); InvokeTouchActionEvent(this, id, TouchActionType.Released, touch, false); }
} public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
base.TouchesCancelled(touches, evt); foreach (UITouch touch in touches.Cast<UITouch>())
{
long id = touch.Handle.Handle.ToInt64(); InvokeTouchActionEvent(this, id, TouchActionType.Cancelled, touch, false); }
} void InvokeTouchActionEvent(TouchRecognizer recognizer, long id, TouchActionType actionType, UITouch touch, bool isInContact)
{
var cgPoint = touch.LocationInView(recognizer.View);
var xfPoint = new Point(cgPoint.X, cgPoint.Y);
OnTouchActionInvoked?.Invoke(this, new TouchActionEventArgs(id, actionType, xfPoint, isInContact));
}
}

Android中的实现

public partial class TouchRecognizer : IDisposable
{
Android.Views.View androidView;
Func<double, double> fromPixels;
int[] twoIntArray = new int[2];
private Point _oldscreenPointerCoords; public TouchRecognizer(Android.Views.View view)
{
this.androidView = view;
if (view != null)
{
fromPixels = view.Context.FromPixels;
view.Touch += OnTouch;
}
} public partial void Dispose()
{
androidView.Touch -= OnTouch;
} void OnTouch(object sender, Android.Views.View.TouchEventArgs args)
{
var senderView = sender as Android.Views.View;
var motionEvent = args.Event;
var pointerIndex = motionEvent.ActionIndex;
var id = motionEvent.GetPointerId(pointerIndex);
senderView.GetLocationOnScreen(twoIntArray);
var screenPointerCoords = new Point(twoIntArray[0] + motionEvent.GetX(pointerIndex),
twoIntArray[1] + motionEvent.GetY(pointerIndex)); switch (args.Event.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
InvokeTouchActionEvent(this, id, TouchActionType.Pressed, screenPointerCoords, true);
break; case MotionEventActions.Move:
for (pointerIndex = 0; pointerIndex < motionEvent.PointerCount; pointerIndex++)
{
id = motionEvent.GetPointerId(pointerIndex); senderView.GetLocationOnScreen(twoIntArray); screenPointerCoords = new Point(twoIntArray[0] + motionEvent.GetX(pointerIndex),
twoIntArray[1] + motionEvent.GetY(pointerIndex)); if (IsOutPit(senderView, screenPointerCoords))
{
if (_oldscreenPointerCoords != default)
{
InvokeTouchActionEvent(this, id, TouchActionType.Exited, screenPointerCoords, true);
_oldscreenPointerCoords=default;
} }
else
{
if (_oldscreenPointerCoords == default
||screenPointerCoords!= _oldscreenPointerCoords)
{
_oldscreenPointerCoords=screenPointerCoords;
InvokeTouchActionEvent(this, id, TouchActionType.Moved, screenPointerCoords, true);
}
} }
break; case MotionEventActions.Up:
case MotionEventActions.Pointer1Up:
InvokeTouchActionEvent(this, id, TouchActionType.Released, screenPointerCoords, false);
break; case MotionEventActions.Cancel: InvokeTouchActionEvent(this, id, TouchActionType.Cancelled, screenPointerCoords, false);
break;
}
} private bool IsOutPit(Android.Views.View senderView, Point screenPointerCoords)
{
return (screenPointerCoords.X<twoIntArray[0]||screenPointerCoords.Y<twoIntArray[1])
||(screenPointerCoords.X>twoIntArray[0]+senderView.Width||screenPointerCoords.Y>twoIntArray[1]+senderView.Height);
} void InvokeTouchActionEvent(TouchRecognizer touchEffect, int id, TouchActionType actionType, Point pointerLocation, bool isInContact)
{
touchEffect.androidView.GetLocationOnScreen(twoIntArray);
double x = pointerLocation.X - twoIntArray[0];
double y = pointerLocation.Y - twoIntArray[1];
var point = new Point(fromPixels(x), fromPixels(y));
OnTouchActionInvoked?.Invoke(this, new TouchActionEventArgs(id, actionType, point, isInContact));
} }

Windows中的实现

public partial class TouchRecognizer : IDisposable
{
FrameworkElement windowsView; public TouchRecognizer(FrameworkElement view)
{
this.windowsView = view;
if (this.windowsView != null)
{
this.windowsView.PointerEntered += View_PointerEntered;
this.windowsView.PointerPressed += View_PointerPressed;
this.windowsView.Tapped +=View_Tapped;
this.windowsView.PointerMoved += View_PointerMoved;
this.windowsView.PointerReleased += View_PointerReleased;
this.windowsView.PointerExited += View_PointerExited;
this.windowsView.PointerCanceled += View_PointerCancelled;
}
} public partial void Dispose()
{
windowsView.PointerEntered -= View_PointerEntered;
windowsView.PointerPressed -= View_PointerPressed;
windowsView.Tapped -=View_Tapped;
windowsView.PointerMoved -= View_PointerMoved;
windowsView.PointerReleased -= View_PointerReleased;
windowsView.PointerExited -= View_PointerEntered;
windowsView.PointerCanceled -= View_PointerCancelled;
}
private void View_Tapped(object sender, TappedRoutedEventArgs args)
{
//var windowsPoint = args.GetPosition(sender as UIElement);
//Point point = new Point(windowsPoint.X, windowsPoint.Y);
//InvokeTouchActionEvent(TouchActionType.Pressed, point, 0, true); }
private void View_PointerEntered(object sender, PointerRoutedEventArgs args)
{
Point point = GetPoint(sender, args);
var id = args.Pointer.PointerId;
var isInContact = args.Pointer.IsInContact;
InvokeTouchActionEvent(TouchActionType.Entered, point, id, isInContact);
} private void View_PointerPressed(object sender, PointerRoutedEventArgs args)
{
Point point = GetPoint(sender, args);
var id = args.Pointer.PointerId;
var isInContact = args.Pointer.IsInContact;
InvokeTouchActionEvent(TouchActionType.Pressed, point, id, isInContact);
(sender as FrameworkElement).CapturePointer(args.Pointer);
} private void View_PointerMoved(object sender, PointerRoutedEventArgs args)
{
Point point = GetPoint(sender, args);
var id = args.Pointer.PointerId;
var isInContact = args.Pointer.IsInContact;
InvokeTouchActionEvent(TouchActionType.Moved, point, id, isInContact);
} private void View_PointerReleased(object sender, PointerRoutedEventArgs args)
{
Point point = GetPoint(sender, args);
var id = args.Pointer.PointerId;
var isInContact = args.Pointer.IsInContact;
InvokeTouchActionEvent(TouchActionType.Released, point, id, isInContact);
} private void View_PointerExited(object sender, PointerRoutedEventArgs args)
{
Point point = GetPoint(sender, args);
var id = args.Pointer.PointerId;
var isInContact = args.Pointer.IsInContact;
InvokeTouchActionEvent(TouchActionType.Exited, point, id, isInContact);
} private void View_PointerCancelled(object sender, PointerRoutedEventArgs args)
{
Point point = GetPoint(sender, args);
var id = args.Pointer.PointerId;
var isInContact = args.Pointer.IsInContact;
InvokeTouchActionEvent(TouchActionType.Cancelled, point, id, isInContact);
} private void InvokeTouchActionEvent(TouchActionType touchActionType, Point point, uint id, bool isInContact)
{
OnTouchActionInvoked?.Invoke(this, new TouchActionEventArgs(id, touchActionType, point, isInContact)); } private static Point GetPoint(object sender, PointerRoutedEventArgs args)
{
var pointerPoint = args.GetCurrentPoint(sender as UIElement);
Windows.Foundation.Point windowsPoint = pointerPoint.Position;
Point point = new Point(windowsPoint.X, windowsPoint.Y);
return point;
}
}

创建控件

创建手势监听控件TouchContentView,它继承于ContentView。

注意:尽量避免在构造函数中调用ViewHandler.ViewMapper.AppendToMapping,它将导致从页面的XAML根元素开始,递归遍历所有IView虚拟视图子元素,将其添加到ViewMapper中

我们用HandlerChanging监听Handler改变,当OldHandler属性不为空时,表示即将从跨平台控件中删除现有的本机控件,此时我们需要将TouchRecognizer移除,以免内存泄漏。

public class TouchContentView : ContentView
{
private TouchRecognizer touchRecognizer; public event EventHandler<TouchActionEventArgs> OnTouchActionInvoked; public TouchContentView()
{
this.HandlerChanged+=TouchContentView_HandlerChanged;
this.HandlerChanging+=TouchContentView_HandlerChanging;
} private void TouchContentView_HandlerChanged(object sender, EventArgs e)
{ var handler = this.Handler;
if (handler != null)
{
#if WINDOWS
touchRecognizer = new TouchRecognizer(handler.PlatformView as Microsoft.UI.Xaml.FrameworkElement);
touchRecognizer.OnTouchActionInvoked += TouchRecognizer_OnTouchActionInvoked;
#endif
#if ANDROID
touchRecognizer = new TouchRecognizer(handler.PlatformView as Android.Views.View);
touchRecognizer.OnTouchActionInvoked += TouchRecognizer_OnTouchActionInvoked; #endif #if IOS|| MACCATALYST
touchRecognizer = new TouchRecognizer(handler.PlatformView as UIKit.UIView);
touchRecognizer.OnTouchActionInvoked += TouchRecognizer_OnTouchActionInvoked; (handler.PlatformView as UIKit.UIView).UserInteractionEnabled = true;
(handler.PlatformView as UIKit.UIView).AddGestureRecognizer(touchRecognizer);
#endif
} } private void TouchContentView_HandlerChanging(object sender, HandlerChangingEventArgs e)
{ if (e.OldHandler != null)
{
var handler = e.OldHandler; #if WINDOWS
touchRecognizer.OnTouchActionInvoked -= TouchRecognizer_OnTouchActionInvoked;
#endif
#if ANDROID
touchRecognizer.OnTouchActionInvoked -= TouchRecognizer_OnTouchActionInvoked; #endif #if IOS|| MACCATALYST
touchRecognizer.OnTouchActionInvoked -= TouchRecognizer_OnTouchActionInvoked; (handler.PlatformView as UIKit.UIView).UserInteractionEnabled = false;
(handler.PlatformView as UIKit.UIView).RemoveGestureRecognizer(touchRecognizer);
#endif }
} private void TouchRecognizer_OnTouchActionInvoked(object sender, TouchActionEventArgs e)
{
OnTouchActionInvoked?.Invoke(this, e);
Debug.WriteLine(e.Type + " is Invoked, position:" + e.Location);
}
}

使用控件

在Xaml中引用TouchContentView所在的命名空间

xmlns:controls="clr-namespace:Lession2.TouchRecognizer;assembly=Lession2"

将你的控件放在TouchContentView中,然后监听TouchContentView的OnTouchActionInvoked事件即可。

注意:对于Button这样的点击控件,点击事件不会向下传递,因此如果包裹了Button,那么OnTouchActionInvoked事件将不会被触发。

<controls:TouchContentView Style="{StaticResource HoldDownButtonStyle}"

                            Grid.Column="0"
OnTouchActionInvoked="TouchContentView_OnTouchActionInvoked">
<BoxView CornerRadius="10" Color="Red"></BoxView> </controls:TouchContentView> <controls:TouchContentView Style="{StaticResource HoldDownButtonStyle}" Grid.Column="1"
OnTouchActionInvoked="TouchContentView_OnTouchActionInvoked">
<Image Source="./dotnet_bot.svg"></Image> </controls:TouchContentView> <controls:TouchContentView Style="{StaticResource HoldDownButtonStyle}" Grid.Column="2"
OnTouchActionInvoked="TouchContentView_OnTouchActionInvoked">
<Label Text="假装我是一个按钮"></Label> </controls:TouchContentView>

最终效果

在控件中将应用手势监听。

项目地址

Github:maui-learning

[MAUI程序设计] 用Handler实现自定义跨平台控件的更多相关文章

  1. ExtJs5_继承自定义一个控件

    Extjs的开发都可以遵循OOP的原则,其对类的封装也很完善了.自定义一个控件最简单的办法就是继承一个已有的控件.根据上一节的需要,我做了一个Button的子类.首先根据目录结构,在app目录下建立一 ...

  2. 6、手把手教你Extjs5(六)继承自定义一个控件

    Extjs的开发都可以遵循OOP的原则,其对类的封装也很完善了.自定义一个控件最简单的办法就是继承一个已有的控件.根据上一节的需要,我做了一个Button的子类.首先根据目录结构,在app目录下建立一 ...

  3. VS2010中如何实现自定义MFC控件

    本文简要讲解在VS2010中怎样实现自定义MFC控件的知识,以下是分步骤说明. 一.自定义一个空白控件  1.先创建一个MFC工程 NEW Project-->MFC-->MFC Appl ...

  4. 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件

        好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ...

  5. 安卓自定义组合控件--toolbar

    最近在学习安卓APP的开发,用到了toolbar这个控件, 最开始使用时include layout这种方法,不过感觉封装性不好,就又改成了自定义组合控件的方式. 使用的工具为android stud ...

  6. Android自定义控件之自定义组合控件

    前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...

  7. asp.net webform 自定义分页控件

    做web开发一直用到分页控件,自己也动手实现了个,使用用户自定义控件. 翻页后数据加载使用委托,将具体实现放在在使用分页控件的页面进行注册. 有图有真相,给个直观的认识: 自定义分页控件前台代码: & ...

  8. arcgis api for js共享干货系列之二自定义Navigation控件样式风格

    arcgis api for js默认的Navigation控件样式风格如下图: 这样的风格不能说不好,各有各的爱好,审美观,这里也不是重点,这里的重点是如何自定义一套自己喜欢的样式风格呢:自己自定义 ...

  9. C# DataGridView自定义分页控件

    好些日子不仔细写C#代码了,现在主要是Java项目,C#.Net相关项目不多了,有点手生了,以下代码不足之处望各位提出建议和批评. 近日闲来无事想研究一下自定义控件,虽然之前也看过,那也仅限于皮毛,粗 ...

  10. C# 自定义FileUpload控件

    摘要:ASP.NET自带的FileUpload控件会随着浏览器的不同,显示的样式也会发生改变,很不美观,为了提高用户体验度,所以我们会去自定义FileUpload控件 实现思路:用两个Button和T ...

随机推荐

  1. ElasticSearch 实现分词全文检索 - 聚合查询 cardinality

    目录 ElasticSearch 实现分词全文检索 - 概述 ElasticSearch 实现分词全文检索 - ES.Kibana.IK安装 ElasticSearch 实现分词全文检索 - Rest ...

  2. 配置 RSTP

    实验1-5-2 配置 RSTP [实验名称] 配置 RSTP. [实验目的] 理解快速生成树协议 RSTP 的配置及原理. [背景描述] 某学校为了开展计算机教学和网络办公,建立了一个计算机教室和一个 ...

  3. 在Vue中发起axios请求成功,却被catch捕捉返回Network Error

    前端发起请求成功,后台接收处理返回,却被axios的catch捕获,没有走then函数. 最后添加了headers配置成功解决,如上,附上axios接口配置中文文档:axios中文文档|axios中文 ...

  4. 一个.Net简单、易用的配置文件操作库

    在我们日常项目开发中,操作INI/CFG配置文件,往往会通过调用WinAPI来实现,WinAPI接口参数只支持字符串,而我们项目中,往往数据类型是多种多样的,在保存和获取配置值,我们就要进行类型的转换 ...

  5. [Java SE/JDK]Intellij IDEA中设置JDK版本

    1 Intellij IDEA 修改JDK版本 第1步:配置JDK环境变量 装好JDK之后,要添加一个环境变量:JAVA_HOME 第2步:修改Idea配置 由Maven决定的版本 <build ...

  6. GPT-4:思考的曙光还是数据的缩影?

    海盗分金,GPT-4初露锋芒 GPT系列模型横空出世后,其是否真实具有思考和推理的能力一直被业界关注.GPT-3.5在多条狗问题和海盗分金问题上表现糟糕.GPT-4在这两个谜题上给出的答案令人惊喜,甚 ...

  7. java项目 学生成绩管理系统 (源码+数据库文件)

    ​ 需要的私信我 备注来意:项目名称 来了就点个赞再走呗,即将毕业的兄弟有福了 文章底部获取源码 java项目  学生成绩管理 (源码+数据库文件)技术框架:java+springboot+vue+m ...

  8. “露天煤矿现场调研和交流案例分享”在CSDN发表,两次审核未通过,判定:全篇涉及广告

    我在博客园发布了:露天煤矿现场调研和交流案例分享.后台分享到了CSDN,结果判定为:全篇涉及广告.我要是真能写出来全篇涉及广告的文章,也算我能力比较强,就算是让ChatGPT可能也写不出来吧. 这种坐 ...

  9. A-O-P 一篇概览

    一.什么是AOP? AOP 即 Aspect-oriented Programming,Aspect 切面,什么是切面,就是一条大路上的收费站,检查站,首先它是一个统一的功能单元,或是收费.或是检查, ...

  10. [Pytorch框架] 3.1 logistic回归实战

    文章目录 3.1 logistic回归实战 3.1.1 logistic回归介绍 3.1.2 UCI German Credit 数据集 3.2 代码实战 import torch import to ...