目录介绍

  • 1.最简单创造方法

    • 1.1 Snackbar作用
    • 1.2 最简单的创建
    • 1.3 Snackbar消失的几种方式
  • 2.源码分析
    • 2.1 Snackbar的make方法源码分析
    • 2.2 对Snackbar属性进行设置
    • 2.3 Snackbar的show显示与点击消失
    • 2.4 显示和隐藏中动画源码分析
  • 3.经典总结
    • 3.1 Snackbar和SnackbarManager类的设计
  • 4.思考问题分析
    • 4.1 Snackbar的设计思路
    • 4.2 什么时候Snackbar显示会导致FloatingActionButton上移
    • 4.3 Snackbar控件show时为何从下往上移出来
    • 4.4 为什么Snackbar总是显示在最下面
    • 4.5 Snackbar与吐司有何区别
  • 5.Snackbar封装库

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
  • Snackbar封装库项目地址:https://github.com/yangchong211/YCDialog
  • 02.Toast源码深度分析
    • 最简单的创建,简单改造避免重复创建,show()方法源码分析,scheduleTimeoutLocked吐司如何自动销毁的,TN类中的消息机制是如何执行的,普通应用的Toast显示数量是有限制的,用代码解释为何Activity销毁后Toast仍会显示,Toast偶尔报错Unable to add window是如何产生的,Toast运行在子线程问题,Toast如何添加系统窗口的权限等等
  • 03.DialogFragment源码分析
    • 最简单的使用方法,onCreate(@Nullable Bundle savedInstanceState)源码分析,重点分析弹窗展示和销毁源码,使用中show()方法遇到的IllegalStateException分析
  • 05.PopupWindow源码分析
    • 显示PopupWindow,注意问题宽和高属性,showAsDropDown()源码,dismiss()源码分析,PopupWindow和Dialog有什么区别?为何弹窗点击一下就dismiss呢?
  • 06.Snackbar源码分析
    • 最简单的创建,Snackbar的make方法源码分析,Snackbar的show显示与点击消失源码分析,显示和隐藏中动画源码分析,Snackbar的设计思路,为什么Snackbar总是显示在最下面
  • 07.弹窗常见问题
    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常见产生的?Toast偶尔报错Unable to add window,Toast运行在子线程导致崩溃如何解决?

1.最简单创造方法

1.1 Snackbar作用

  • Snackbar是Android支持库中用于显示简单消息并且提供和用户的一个简单操作的一种弹出式提醒。当使用Snackbar时,提示会出现在消息最底部,通常含有一段信息和一个可点击的按钮。
  • 同样作为消息提示,Snackbar相比于Toast而言,增加了一个用户操作,并且在同时弹出多个消息时,Snackbar会停止前一个,直接显示后一个,也就是说同一时刻只会有一个Snackbar在显示;而Toast则不然,如果不做特殊处理,那么同时可以有多个Toast出现;Snackbar相比于Dialog,操作更少,因为只有一个用户操作的接口,而Dialog最多可以设置三个,另外Snackbar的出现并不影响用户的继续操作,而Dialog则必须需要用户做出响应,所以相比Dialog,Snackbar更轻量。

1.2 最简单的创建

  • 如下所示
Snackbar sb = Snackbar.make(v,"潇湘剑雨",Snackbar.LENGTH_LONG)
.setAction("删除吗?", new View.OnClickListener() {
@Override
public void onClick(View v) {
//点击了"是吗?"字符串操作
ToastUtils.showRoundRectToast("逗比");
}
})
.setActionTextColor(Color.RED)
.setText("杨充是个逗比")
.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
super.onDismissed(transientBottomBar, event);
switch (event) {
case Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE:
case Snackbar.Callback.DISMISS_EVENT_MANUAL:
case Snackbar.Callback.DISMISS_EVENT_SWIPE:
case Snackbar.Callback.DISMISS_EVENT_TIMEOUT:
ToastUtils.showRoundRectToast("删除成功");
break;
case Snackbar.Callback.DISMISS_EVENT_ACTION:
ToastUtils.showRoundRectToast("撤销了删除操作");
break;
}
Log.d("MainActivity","onDismissed");
}
@Override
public void onShown(Snackbar transientBottomBar) {
super.onShown(transientBottomBar);
Log.d("MainActivity","onShown");
}
});
sb.show();

1.3 Snackbar消失的几种方式

  • Snackbar显示只有一种方式,那就是调用show()方法,但是消失有几种方式:时间到了自动消失、点击了右侧按钮消失、新的Snackbar出现导致旧的Snackbar消失、滑动消失或者通过调用dismiss()消失。

    • 分别对应于Snackbar.Callback中的几个常量值。

      • DISMISS_EVENT_ACTION:点击了右侧按钮导致消失
      • DISMISS_EVENT_CONSECUTIVE:新的Snackbar出现导致旧的消失
      • DISMISS_EVENT_MANUAL:调用了dismiss方法导致消失
      • DISMISS_EVENT_SWIPE:滑动导致消失
      • DISMISS_EVENT_TIMEOUT:设置的显示时间到了导致消失
    • Callback有两个方法
      • void onDismissed(B transientBottomBar, @DismissEvent int event)
      • void onShown(B transientBottomBar)
      • 其中onShown在Snackbar可见时调用,onDismissed在Snackbar准备消失时调用。

2.源码分析

2.1 Snackbar的make方法源码分析

  • 创建Snackbar需要使用静态的make方法,并且其中的view参数是一个查找父布局的起点

    • 这里可以看到,snackBar的布局是design_layout_snackbar_include,假如我们需要自定义SnackBar并且设置字体颜色,大小等属性。则需要拿到这个布局的控件id等。关于封装库,可以查看:https://github.com/yangchong211/YCDialog
  • 其中findSuitableParent()方法为以view为起点寻找合适的父布局,下面看看findSuitableParent()如何做的?
    • 看了下面源码可知:可以看到如果view是CoordinatorLayout,那么就直接作为父布局了;如果是FrameLayout,并且如果是android.R.id.content,也就是查找到了DecorView,即最顶部,那么就只用这个view;如果不是的话,先保存下来;接下来就是获取view的父布局,然后循环再次判断。这样导致的结果最终会有两个选择,要么是CoordinatorLayout,要么就是FrameLayout,并且是最顶层的那个布局。
    • 如果从View往上搜寻,如果有CoordinatorLayout,那么就使用该CoordinatorLayout ;如果从View往上搜寻,没有CoordinatorLayout,那么就使用android.R.id.content的FrameLayout

2.2 对Snackbar属性进行设置

  • 2.2.1 setActionTextColor设置action颜色

    • 可以看到先是获取父布局contentLayout,然后在获取snackbar_action的mActionView
    @NonNull
    public Snackbar setActionTextColor(@ColorInt int color) {
    final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0);
    final TextView tv = contentLayout.getActionView();
    tv.setTextColor(color);
    return this;
    } //然后看SnackbarContentLayout类中getActionView方法
    @Override
    protected void onFinishInflate() {
    super.onFinishInflate();
    mMessageView = (TextView) findViewById(R.id.snackbar_text);
    mActionView = (Button) findViewById(R.id.snackbar_action);
    }
    public Button getActionView() {
    return mActionView;
    }
  • 2.2.2 看setAction()方法的实现

    • 首先是获取父布局contentLayout,然后通过contentLayout调用getActionView()方法,返回的tv其实就是右边的Button,然后判断文本和监听器,设置可见性、文本、监听器。
    @NonNull
    public Snackbar setAction(CharSequence text, final View.OnClickListener listener) {
    final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0);
    final TextView tv = contentLayout.getActionView(); if (TextUtils.isEmpty(text) || listener == null) {
    tv.setVisibility(View.GONE);
    tv.setOnClickListener(null);
    } else {
    tv.setVisibility(View.VISIBLE);
    tv.setText(text);
    tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    listener.onClick(view);
    // Now dismiss the Snackbar
    dispatchDismiss(BaseCallback.DISMISS_EVENT_ACTION);
    }
    });
    }
    return this;
    }

2.3 Snackbar的show显示与点击消失

  • 2.3.1 show显示

    • 可以看到,首先获取一个SnackbarManager对象,然后调用它的show方法。可以看到在这个方法中,先判断如果是当前正在显示的SnackBar对应的CallBack,则更新显示时长,然后从消息队列中移除,最后调用scheduleTimeoutLocked方法发送定时消息dismiss;如果是下一个要显示的,则更新显示时长;如果都不是,那么就创建一个SnackbarRecord对象。
    • isCurrentSnackbarLocked:如果当前已经有一个Snackbar显示了,又再调用了该对象的show方法,但是只是设置了不同时间,那么isCurrentSnackbarLocked就会是true,执行里面的方法。
    • isNextSnackbarLocked:如果当前已有一个Snackbar正在显示,又创建了一个新的Snackbar并调用show方法,则执行这个条件代码
    • 如果两条件都不成立,则需要创建一个新记录并对其进行排队。
    public void show() {
    SnackbarManager.getInstance().show(mDuration, mManagerCallback);
    } public void show(int duration, Callback callback) {
    synchronized (mLock) {
    if (isCurrentSnackbarLocked(callback)) {
    // 表示回调已在队列中。我们只需更新持续时间
    mCurrentSnackbar.duration = duration; // 如果这是当前正在显示的Snackbar,请调用重新调度它的
    // timeout
    mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
    // 这个方法很重要,当执行时间结束后,就会自动dismiss。下面再详细分析
    scheduleTimeoutLocked(mCurrentSnackbar);
    return;
    } else if (isNextSnackbarLocked(callback)) {
    //我们只需更新持续时间
    mNextSnackbar.duration = duration;
    } else {
    //否则,我们需要创建一个新记录并对其进行排队。
    mNextSnackbar = new SnackbarRecord(duration, callback);
    }
    if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {
    // 如果我们目前有一个Snackbar,请尝试取消它并排队等待。
    return;
    } else {
    // 清除当前的快捷键
    mCurrentSnackbar = null;
    //很重要
    showNextSnackbarLocked();
    }
    }
    } //注意这个callback方法
    final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
    @Override
    public void show() {
    sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, BaseTransientBottomBar.this));
    } @Override
    public void dismiss(int event) {
    sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0,
    BaseTransientBottomBar.this));
    }
    }; //处理sHandler发送的消息
    static {
    sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
    @Override
    public boolean handleMessage(Message message) {
    switch (message.what) {
    case MSG_SHOW:
    ((BaseTransientBottomBar) message.obj).showView();
    return true;
    case MSG_DISMISS:
    ((BaseTransientBottomBar) message.obj).hideView(message.arg1);
    return true;
    }
    return false;
    }
    });
    }
    • 然后看看showNextSnackbarLocked这个方法,注意:mCurrentSnackbar当前正在显示的,而mNextSnackbar是下一个要显示的。能看到会调用callback的show方法,而这个calllback对象就是我们在调用snackbar的show方法是传进去的那个。向Snackbar的Handler发送一个消息,最后显示Snackbar。
    private void showNextSnackbarLocked() {
    if (mNextSnackbar != null) {
    mCurrentSnackbar = mNextSnackbar;
    mNextSnackbar = null; final Callback callback = mCurrentSnackbar.callback.get();
    if (callback != null) {
    callback.show();
    } else {
    // The callback doesn't exist any more, clear out the Snackbar
    mCurrentSnackbar = null;
    }
    }
    }
  • 2.3.2 看看scheduleTimeoutLocked源码如何销毁snackBar
    • 可以发现,如果我们设置为无限期,则不会设置超时,直接return函数。然后发送了一个叫做MSG_TIMEOUT的消息,继续追终,最后会到达cancelSnackbarLocked方法。在cancelSnackbarLocked这个方法中,首先移除SnackbarRecord发出的所有消息,然后调用Callback的dismiss方法,从上面我们知道最终是向Snackbar的sHandler发送了一条消息,最终是调用Snackbar的hideView消失。
    private void scheduleTimeoutLocked(SnackbarRecord r) {
    if (r.duration == Snackbar.LENGTH_INDEFINITE) {
    // If we're set to indefinite, we don't want to set a timeout
    return;
    } int durationMs = LONG_DURATION_MS;
    if (r.duration > 0) {
    durationMs = r.duration;
    } else if (r.duration == Snackbar.LENGTH_SHORT) {
    durationMs = SHORT_DURATION_MS;
    }
    mHandler.removeCallbacksAndMessages(r);
    mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs);
    } //接受mHandler消息并且处理
    mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
    @Override
    public boolean handleMessage(Message message) {
    switch (message.what) {
    case MSG_TIMEOUT:
    handleTimeout((SnackbarRecord) message.obj);
    return true;
    }
    return false;
    }
    }); //
    void handleTimeout(SnackbarRecord record) {
    synchronized (mLock) {
    if (mCurrentSnackbar == record || mNextSnackbar == record) {
    cancelSnackbarLocked(record, Snackbar.Callback.DISMISS_EVENT_TIMEOUT);
    }
    }
    } //最终可以追踪到这个方法
    private boolean cancelSnackbarLocked(SnackbarRecord record, int event) {
    final Callback callback = record.callback.get();
    if (callback != null) {
    // Make sure we remove any timeouts for the SnackbarRecord
    mHandler.removeCallbacksAndMessages(record);
    callback.dismiss(event);
    return true;
    }
    return false;
    }

2.4 显示和隐藏中动画源码分析

  • 在显示的时候是这样设置动画的,具体如下所示

  • 在隐藏的时候是这样设置动画的,具体如下所示
  • 最后具体看一下animateViewOut部分源码
    • 可以看到在动画结束的最后都调用了onViewHidden方法,所以最终都是要调用onViewHidden方法的。
    private void animateViewOut(final int event) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    ViewCompat.animate(mView)
    .translationY(mView.getHeight())
    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
    .setDuration(ANIMATION_DURATION)
    .setListener(new ViewPropertyAnimatorListenerAdapter() {
    @Override
    public void onAnimationStart(View view) {
    mContentViewCallback.animateContentOut(0, ANIMATION_FADE_DURATION);
    } @Override
    public void onAnimationEnd(View view) {
    onViewHidden(event);
    }
    }).start();
    } else {
    Animation anim = AnimationUtils.loadAnimation(mView.getContext(),
    R.anim.design_snackbar_out);
    anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
    anim.setDuration(ANIMATION_DURATION);
    anim.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationEnd(Animation animation) {
    onViewHidden(event);
    } @Override
    public void onAnimationStart(Animation animation) {} @Override
    public void onAnimationRepeat(Animation animation) {}
    });
    mView.startAnimation(anim);
    }
    }
  • onViewHidden提供具体的业务处理,具体如下所示
    • 首先调用SnackbarManager的onDismissed方法,然后判断Snackbar.Callback是不是null,调用Snackbar.Callback的onDismissed方法,就是我们上面介绍的处理Snackbar消失的方法。最后就是将Snackbar的mView移除。

3.经典总结

3.1 Snackbar和SnackbarManager类的设计

  • Snackbar和SnackbarManager,SnackbarManager内部有两个SnackbarRecord,一个mCurrentSnackbar,一个mNextSnackbar,SnackbarManager通过这两个对象实现Snackbar的顺序显示,如果在一个Snackbar显示之前有Snackbar正在显示,那么使用mNextSnackbar保存第二个Snackbar,然后让第一个Snackbar消失,然后消失之后再调用SnackbarManager显示下一个Snackbar,如此循环,实现了Snackbar的顺序显示。
  • Snackbar负责显示和消失,具体来说其实就是添加和移除View的过程。Snackbar和SnackbarManager的设计很巧妙,利用一个SnackbarRecord对象保存Snackbar的显示时间以及SnackbarManager.Callback对象,前面说到每一个Snackbar都有一个叫做mManagerCallback的SnackbarManager.Callback对象,下面看一下SnackRecord类的定义:
  • Snackbar向SnackbarManager发送消息主要是调用SnackbarManager.getInstace()返回一个单例对象;而SnackManager向Snackbar发送消息就是通过show方法传入的Callback对象。SnackbarManager中的Handler只处理一个MSG_TIMEOUT事件,最后是调用Snackbar的hideView消失的;Snackbar的sHandler处理两个消息,showView和hideView,而消息的发送者是mManagerCallback,控制者是SnackbarManager。

4.思考问题分析

4.1 Snackbar的设计思路

  • 具体可以看经典总结3.1

4.2 什么时候Snackbar显示会导致FloatingActionButton上移

  • 为什么CoordinatorLayout + FloatingActionButton,当Snackbar显示的时候FloatingActionButton会上移呢,这个是怎么实现的?

    • 把CoordinatorLayout替换成FrameLayout确不行。这个问题我们还没说。其实这个不是在Snackbar里面处理的,是通过CoordinatorLayout和Behavior来处理的。那具体的处理在哪里呢。FloatingActionButton类里面Behavior类。正是Behavior里面的两个函数layoutDependsOn()和onDependentViewChanged()函数作用的结果。直接进去看下FloatingActionButton内部类Behavior里面这两个函数的代码。

4.3 Snackbar控件show时为何从下往上移出来

  • 至于说Snackbar控件show时为何从下往上移出来,看下面这段代码就知道呢,如下所示

4.4 为什么Snackbar总是显示在最下面

  • 直接找到make方法中的填充布局,然后去看design_layout_snackbar_include的布局参数,结果如下:

4.5 Snackbar与吐司有何区别

  • 与Toast进行比较,SnackBar有优势:

    • 1.SnackBar可以自动消失,也可以手动取消(侧滑取消,但是需要在特殊的布局中,后面会仔细说)
    • 2.SnackBar可以通过setAction()来与用户进行交互
    • 3.通过CallBack我们可以获取SnackBar的状态

5.Snackbar封装库

  • 可以一行代码调用,也可以自己使用链式编程调用。支持设置显示时长属性;可以设置背景色;可以设置文字大小,颜色;可以设置action内容,文字大小,颜色,还有点击事件;可以设置icon;代码如下所示,更多内容可以直接运行demo哦!

    //1.只设置text
    SnackBarUtils.showSnackBar(this,"滚犊子"); //2.设置text,action,和点击事件
    SnackBarUtils.showSnackBar(this, "滚犊子", "ACTION", new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    ToastUtils.showRoundRectToast("滚犊子啦?");
    }
    }); //3.设置text,action,和点击事件,和icon
    SnackBarUtils.showSnackBar(this, "滚犊子", "ACTION",R.drawable.icon_cancel, new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    ToastUtils.showRoundRectToast("滚犊子啦?");
    }
    }); //4.链式调用
    SnackBarUtils.builder()
    .setBackgroundColor(this.getResources().getColor(R.color.color_7f000000))
    .setTextSize(14)
    .setTextColor(this.getResources().getColor(R.color.white))
    .setTextTypefaceStyle(Typeface.BOLD)
    .setText("滚犊子")
    .setMaxLines(4)
    .centerText()
    .setActionText("收到")
    .setActionTextColor(this.getResources().getColor(R.color.color_f25057))
    .setActionTextSize(16)
    .setActionTextTypefaceStyle(Typeface.BOLD)
    .setActionClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    ToastUtils.showRoundRectToast("滚犊子啦?");
    }
    })
    .setIcon(R.drawable.icon_cancel)
    .setActivity(MainActivity.this)
    .setDuration(SnackBarUtils.DurationType.LENGTH_INDEFINITE)
    .build()
    .show();

关于其他内容介绍

01.关于博客汇总链接

02.关于我的博客

Snackbar源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

  10. ABP源码分析四:Configuration

    核心模块的配置 Configuration是ABP中设计比较巧妙的地方.其通过AbpStartupConfiguration,Castle的依赖注入,Dictionary对象和扩展方法很巧妙的实现了配 ...

随机推荐

  1. MYSQL-另一种行转列的实现方式

    行转列的实现方式:使用mysql.help_topic --行转列 SELECT b.help_topic_id, substring_index( a.levels, ',', b.help_top ...

  2. Nginx 简介 转载:https://www.cnblogs.com/wztshine/p/16162640.html

    Nginx 安装环境 安装 gcc 安装 nginx 需要先将官网下载的源码进行编译,编译依赖 gcc 环境,如果没有 gcc 环境,则需要安装: yum install gcc-c++ 安装 PCR ...

  3. Python树与树算法

    Python树与树算法 树的概念 树(英语:tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合.它是由n(n>=1)个有限节点组成一个具 ...

  4. OCR 02: Tesseract-OCR

    Catalog OCR 01: EasyOCR OCR 02: Tesseract-OCR OCR 03: PaddleOCR Project Host And Brief Official Site ...

  5. OpenStack调度器

    计算使用 nova-scheduler 服务来确定如何调度计算请求 默认配置中,调度程序会考虑以下所有条件的主机: 位于请求的可用区 (map_az_to_placement_aggregate) 放 ...

  6. Python之读取Excel

    介绍 现在交给你一份2010年美国各州县人口普查表:censuspopdata.xlsx.共72864条记录. 每一行代表一个县某统计区的人口数. 需要你统计出:各县统计区数量和人口数. 表格内容长这 ...

  7. Docker进阶之01-Docker Compose编排工具

    Docker Compose是什么 https://github.com/docker/compose 可以按项目为单位管理多个Docker容器,Python语言开发,底层调用Docker的API接口 ...

  8. sql注入简单初

    import requests,sys,time from PyQt5.QtWidgets import * from PyQt5.QtGui import QIcon from threading ...

  9. .NET Core 集成微信支付签名错误

    .NET Core 集成微信支付签名错误 The provided data is tagged with 'Universal' class value '16', but it should ha ...

  10. 进击的 AI 生成,创造性的新世界!

    2022年,AI艺术生成文本生成图像的AI绘画生成器如雨后春笋般涌现,以一幅幅"不明觉厉"的AI作品进入大众视野.从2月Disco Diffusion爆火,仅两个月后OpenAI发 ...