高级UI-Canvas(变换技巧,状态保存)

news2024/10/5 16:22:06

前言

在前面我们把Paint关于UI颜色样式的处理进行了学习, 其实真正高级部分就是三个点,渲染,滤镜,图形组合,而我们图形绘制比较重要的另一个对象Canvas也是需要我们去重点掌握的,那么这次课咱们来进行Canvas的深层次的学习,主要了解有两个点:
1.Canvas的变换使用技巧;
2.Canvas的状态,Canvas Layer

1.Canvas基本概念

直面意思是画布,其实是分装的一个工具类(绘制会话,用来和底层沟通最终交给底层绘制)
一个Canvas类对象有四大基本要素
1、一个是用来保存像素的bitmap
2、一个Canvas在Bitmap上进行绘制操作
3、绘制的东西
4、绘制的画笔Paint

1.Canvas变换操作----坐标系概念

在我们进行canvas操作的时候我们会有一个问题产生,在进行图形的平移,旋转操作时,我们没有去更改原始的坐标,只通过了非常简单的几个api就直接进行了
移动,那么中间他的具体到底是发生了什么,通过之前在绘制流程当中draw时我们发现在下面我已经缩减了之后的代码上我门发现, 在绘制之初就产生了一个矩形,并且他通过面板进行了一次初始化

 private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
   ...

    final Rect dirty = mDirty;
    if (mSurfaceHolder != null) {
        // The app owns the surface, we won't draw.
        dirty.setEmpty();
        if (animating && mScroller != null) {
            mScroller.abortAnimation();
        }
        return;
    }

    if (fullRedrawNeeded) {
        mAttachInfo.mIgnoreDirtyState = true;
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    }

    int xOffset = -mCanvasOffsetX;
    int yOffset = -mCanvasOffsetY + curScrollY;
    final WindowManager.LayoutParams params = mWindowAttributes;
    final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
    if (surfaceInsets != null) {
        xOffset -= surfaceInsets.left;
        yOffset -= surfaceInsets.top;

        // Offset dirty rect for surface insets.
        dirty.offset(surfaceInsets.left, surfaceInsets.right);
    }
  ......

            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }

    if (animating) {
        mFullRedrawNeeded = true;
        scheduleTraversals();
    }
}

那么在上面的代码当中我门可以看到在绘制开始之初,在底层就确定了一个绘制区域,确定了canvas绘制位置的坐标,那么这个就是被称之为我门canvas的坐标系,确定我门canvas绘制图形的位置
那么,当我们进行了

    canvas.translate(50, 50);
    canvas.rotate(45);
    canvas.scale(1.5f, 0.5f);
    canvas.skew(1.73f, 0);

等操作的时候,我们的图形绘制会直接发生改变,那么这个时候我门考虑一个问题,下图中绿色的点移动到红色的点,我门刚才所设置的canvas移动了吗

其实很多通过会在这里认为我们canvas的坐标进行了移动,其实不然,在
Canvas里面牵扯两种坐标系:Canvas自己的坐标系、绘图坐标系

Canvas的坐标系,

它就在View的左上角,做坐标原点往右是X轴正半轴,往下是Y轴的正半轴,有且只有一个,唯一不变这一个其实就是在我们canvas当中在绘制之初由surface所初始化的那个点

绘图坐标系

它不是唯一不变的,它与Canvas的Matrix有关系,当Matrix发生改变的时候,绘图坐标系对应的进行改变,他有一个特性就是在这个过程中是不可逆的

那么其实实际就是我门在画图的时候,有一块总面板,总面板不动, 而当我在开始进行绘制图形的时候,有一个时时刻刻在动的面板,而这个面板就是具体去绘制我们图形的画板

那么里层的绘图坐标系他的实际是用一个Matrix矩阵表示的

这个和我门之前的滤镜矩阵表示差不多,只不过,绘图坐标系的矩阵是一个2x2的矩阵传入的值是由我们的canvas进行解析之后将自己想要的数据给底层底层自己计算所得

public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
    throwIfHasHwBitmapInSwMode(paint);
    drawRect(r.left, r.top, r.right, r.bottom, paint);
}

那么在这里我们可以看到在进入底层native方法之前,实现会根据每一种绘制的不同对底层的数据进行传入, 然后会计算出我门的绘制坐标系(此处底层不看,涉及c,我们这里明白这一点就行)

我们通过简单设置translate、rotate、scale、skew来改变我们绘制图形的位置 时他的计算时依赖与另外一个矩阵来对绘图坐标系进行改变这是一个3x3的矩阵,它里面的九个参数

cosX -sinX translateX
sinX cosX translateY
0 0 scale

其中,sinX和cosX,代表的是旋转角度的sin和cos值。注意旋转的正方向是顺时针方向。translateX和translateY代表的是平移的X和Y。scale代表的是缩放的大小。

我们可以通过getMatrix()的到这个矩阵,而通过看到底层源码,这里我能清晰的看到我们是直接调用底层的矩阵

@Deprecated
public void getMatrix(@NonNull Matrix ctm) {
    nGetMatrix(mNativeCanvasWrapper, ctm.native_instance);
}

那么这里我做了一组测试

 RectF r = new RectF(0, 0, 400, 500);
    paint.setColor(Color.GREEN);
    canvas.drawRect(r, paint);
    float[] fs = new float[10];
            canvas.getMatrix().getValues(fs);
    for (int i = 0;i < fs.length;i++){
        Log.i("barry","fs:"+fs[i]);
    }

    //平移
    canvas.translate(50, 50);
    float[] fs2 = new float[10];
    canvas.getMatrix().getValues(fs2);
    for (int i = 0;i < fs2.length;i++){
        Log.i("barry","fs2:"+fs2[i]);
    }

    paint.setColor(Color.BLUE);
    canvas.drawRect(r, paint);

可以很明显看到,矩阵进行平移之后这个矩阵信息的变化

那么注意,绘图矩阵的坐标系移动是一个不可逆转的状态也就是说,一旦矩阵移动完成之后,那么他不能回到之前的位置,具体效果如下

但是在我门的Canvas当中提供了save和restore方法来保存和还原变化操作,

    RectF r = new RectF(0, 0, 400, 500);
    paint.setColor(Color.GREEN);
    //画完之后,绘图坐标系定位在此处
    canvas.drawRect(r, paint);
    //save保存当前坐标
    canvas.save();

    //平移之后,坐标系发生改变
    canvas.translate(50, 50);

    paint.setColor(Color.BLUE);
    canvas.drawRect(r, paint);
    //通过restore进行还原到save保存时的坐标系
    canvas.restore();

但是想要知道这两个方法是怎么进行操作的才能让我们更加深入的去熟悉Canvas的使用技巧,那么我门必须去了解Canvas的状态栈、Layer栈

2.Canvas的状态保存—状态栈、Layer栈
状态栈


在前面我们提到坐标系的转换是一个不可逆转的,而我们可以通过save来进行保存restore进行恢复,其实我们在进行save操作时在canvas当中会将我门save下来的坐标系进行保存到一个栈当中,并且可以通过restore或者是restoreToCount进行操作下面通过一段测试代码我门印证下

public class MyView extends View {

private static final String TAG = "BARRY";

private Paint mPaint = null;
private Bitmap mBitmap = null;

public MyView(Context context) {
    this(context, null);
}

public MyView(Context context, AttributeSet attrs) {
    super(context, attires
    mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lsj);
    init();
}

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

private void init() {
    mPaint = new Paint();
    mPaint.setColor(Color.RED);
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setStrokeWidth(10);
}

@Override
protected void onDraw(Canvas canvas) {
    //第1次保存,并通过canvas.getSaveCount的到当前状态栈容量
    canvas.save();
    Log.i(TAG, "Current SaveCount = " + canvas.getSaveCount());

    canvas.translate(400, 400);
    RectF rectF = new RectF(0,0,600,600);

    canvas.drawBitmap(mBitmap, null, rectF, mPaint);
    //第2次保存,并通过canvas.getSaveCount的到当前状态栈容量
    canvas.save();
    Log.i(TAG, "Current SaveCount = " + canvas.getSaveCount());

    canvas.rotate(45);

    canvas.drawBitmap(mBitmap, null, rectF, mPaint);
    //第3次保存,并通过canvas.getSaveCount的到当前状态栈容量
    canvas.save();
    Log.i(TAG, "Current SaveCount = " + canvas.getSaveCount());

    canvas.rotate(45);

    canvas.drawBitmap(mBitmap, null, rectF, mPaint);
    //第4次保存,并通过canvas.getSaveCount的到当前状态栈容量
    canvas.save();
    Log.i(TAG, "Current SaveCount = " + canvas.getSaveCount());
    //通过canvas.restoreToCount出栈到第三层状态
    canvas.restoreToCount(3);
    Log.i(TAG, "restoreToCount--Current SaveCount = " + canvas.getSaveCount());

    canvas.translate(0, 200);

    //rectF = new RectF(0,0,600,600);
    canvas.drawBitmap(mBitmap, null, rectF, mPaint);
    //通过canvas.restoreToCount出栈到第1层(最原始的那一层)状态
    canvas.restoreToCount(1);
    Log.i(TAG, "restoreToCount--Current SaveCount = " + canvas.getSaveCount());
    canvas.drawBitmap(mBitmap, null, rectF, mPaint);

}
}

那么其实我们这样可以直接明白, 每一次的save其实实际上是用了一个栈保存了我的绘图坐标系,这个栈被我们称之为状态栈起来, 而我门的restore就是一个出栈的过程
save、 restore方法来保存和还原变换操作Matrix以及Clip剪裁


Layer栈

在我们的canvas当中,提供了一个saveLayer的api主要做用是用来新建一个图层
后续的绘图操作都在新建的layer上面进行
当我们调用restore 或者 restoreToCount 时 更新到对应的图层和画布上

下面通过这段测试代码的效果我门来验证当前的结论

public class MyView extends View {

Paint mPaint;
float mItemSize = 0;
float mItemHorizontalOffset = 0;
float mItemVerticalOffset = 0;
float mCircleRadius = 0;
float mRectSize = 0;
int mCircleColor = 0xffffcc44;//黄色
int mRectColor = 0xff66aaff;//蓝色
float mTextSize = 25;

private static final Xfermode[] sModes = {
        new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
        new PorterDuffXfermode(PorterDuff.Mode.SRC),
        new PorterDuffXfermode(PorterDuff.Mode.DST),
        new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
        new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
        new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
        new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
        new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
        new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
        new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
        new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
        new PorterDuffXfermode(PorterDuff.Mode.XOR),
        new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
        new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
        new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
        new PorterDuffXfermode(PorterDuff.Mode.SCREEN)
};

private static final String[] sLabels = {
        "Clear", "Src", "Dst", "SrcOver",
        "DstOver", "SrcIn", "DstIn", "SrcOut",
        "DstOut", "SrcATop", "DstATop", "Xor",
        "Darken", "Lighten", "Multiply", "Screen"
};

public MyView(Context context) {
    super(context);
    init(null, 0);
}

public MyView(Context context, AttributeSet attrs) {
    super(context, attires
    init(attrs, 0);
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(attrs, defStyle);
}

private void init(AttributeSet attrs, int defStyle) {
    if(Build.VERSION.SDK_INT >= 11){
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setTextSize(mTextSize);
    mPaint.setTextAlign(Paint.Align.CENTER);
    mPaint.setStrokeWidth(2);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //设置背景色
    canvas.drawARGB(255, 139, 197, 186);

    int canvasWidth = canvas.getWidth();
    int canvasHeight = canvas.getHeight();

    for(int row = 0; row < 4; row++){
        for(int column = 0; column < 4; column++){
            canvas.save();
            //此处是建立新的图层
            int layer = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
            mPaint.setXfermode(null);
            int index = row * 4 + column;
            float translateX = (mItemSize + mItemHorizontalOffset) * column;
            float translateY = (mItemSize + mItemVerticalOffset) * row;
            canvas.translate(translateX, translateY);
            //画文字
            String text = sLabels[index];
            mPaint.setColor(Color.BLACK);
            float textXOffset = mItemSize / 2;
            float textYOffset = mTextSize + (mItemVerticalOffset - mTextSize) / 2;
            canvas.drawText(text, textXOffset, textYOffset, mPaint);
            canvas.translate(0, mItemVerticalOffset);
            //画边框
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0xff000000);
            canvas.drawRect(2, 2, mItemSize - 2, mItemSize - 2, mPaint);
            mPaint.setStyle(Paint.Style.FILL);
            //画圆
            mPaint.setColor(mCircleColor);
            float left = mCircleRadius + 3;
            float top = mCircleRadius + 3;
            canvas.drawCircle(left, top, mCircleRadius, mPaint);
            mPaint.setXfermode(sModes[index]);
            //画矩形
            mPaint.setColor(mRectColor);
            float rectRight = mCircleRadius + mRectSize;
            float rectBottom = mCircleRadius + mRectSize;
            canvas.drawRect(left, top, rectRight, rectBottom, mPaint);
            mPaint.setXfermode(null);
            //canvas.restore();
            canvas.restoreToCount(layer);
        }
    }
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, old);)
    mItemSize = w / 4.5f;
    mItemHorizontalOffset = mItemSize / 6;
    mItemVerticalOffset = mItemSize * 0.426f;
    mCircleRadius = mItemSize / 3;
    mRectSize = mItemSize * 0.6f;
}
}

这段代码我门可以看到,其实实际上就是我们上次课当中,xfermode的演示代码,而在这段代码当中我才用了saveLayer进行操作,通过上面两个结果,一个是我加了saveLayer的,一个是没加的, 那么从中我门可以明显看到在没有加的时候,xfermode的像素输出效果直接将外层背景色也给清空了,而加入之后没有,那么其实我门可以很明显的明白如果用了layer那么其实实际上我们是在当前这个canvas图形上面新建了一个图层当我们调用restore 或者 restoreToCount 时 我们的绘制会更新到当前图层

那么这个时候我门来详细分析saveLayer的参数

canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
/**
 * Helper version of saveLayer() that takes 4 values rather than a RectF.
 *
 * @deprecated Use {@link #saveLayer(float, float, float, float, Paint)} instead.
 */
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint,
        @Saveflags int saveFlags) {
    return nSaveLayer(mNativeCanvasWrapper, left, top, right, bottom,
            paint != null ? paint.getNativeInstance() : 0,
            saveFlags);
}

通过上诉方法的注释,以及代码我门明显知道,前面四个参数,为上下左右四个点构成一个图层区,Paint画笔也可以继承过来,而最后一个参数表示的是我们当前的保存形式,总共下面6种,这六个模式其实实际上讲的就是告诉canvas当前保存那些信息

MATRIX_SAVE_FLAG:只保存图层的matrix矩阵 save,saveLayer
CLIP_SAVE_FLAG:只保存大小信息 save,saveLayer
HAS_ALPHA_LAYER_SAVE_FLAG:表明该图层有透明度,和下面的标识冲突,都设置时以下面的标志为准 saveLayer
FULL_COLOR_LAYER_SAVE_FLAG:完全保留该图层颜色(和上一图层合并时,清空上一图层的重叠区域,保留该图层的颜色) saveLayer
CLIP_TO_LAYER_SAVE_:创建图层时,会把canvas(所有图层)裁剪到参数指定的范围,如果省略这个flag将导致图层开销巨大(实际上图层没有裁剪,与原图层一样大)
ALL_SAVE_FLAG:保存所有信息 save,saveLayer

从源码当中我发现其他几种模式在高版本当中已经剔除,只保留了一种。就是我门的all_save_flag

  /** @hide */
@IntDef(flag = true,
        value = {
            ALL_SAVE_FLAG
        })
@Retention(RetentionPolicy.SOURCE)
public @interface Saveflags {}

那么这个时候我们来测试一下

 public class MyView3 extends View {

public MyView3(Context context) {
    super(context);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    RectF rectF = new RectF(0,0,400,500);
    Paint paint = new Paint();
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(10);
    paint.setColor(Color.GREEN);

    canvas.drawRect(rectF, paint);
    canvas.translate(50,50);

    canvas.saveLayer(0,0,canvas.getWidth(),canvas.getHeight(),null,Canvas.ALL_SAVE_FLAG);
    //canvas.save();
    canvas.drawColor(Color.BLUE);// 通过drawColor可以发现saveLayer是新建了一个图层,
                                // 同时结合Lsn5的16种Xfermode叠加形式Demo可以验证是新建的透明图层
    paint.setColor(Color.YELLOW);
    canvas.drawRect(rectF,paint);
    //canvas.restore();
    canvas.restore();

    RectF rectF1 = new RectF(10,10,300,400);
    paint.setColor(Color.RED);
    canvas.drawRect(rectF1,paint);

}

那么这段代码也验证了我门上诉的理论,在加了saveLayer之后,背景色被绘制到了另外一个图层导致前面有一节空白的,同时也得出了一个有趣的结论,貌似,平移操作也被继承了,其实这里我们的出一个结论saveLayer会将之前的一些Canvas状态操作延续过来。这里是通过之前的最后一个参数设置成ALL_SAVE_FLAG完成。他在新建图层的时候完成了保留当前所有信息状态的操作.

总结

Canvas里面牵扯两种坐标系:Canvas自己的坐标系、绘图坐标系
Canvas的坐标系
    它就在View的左上角,做坐标原点往右是X轴正半轴,往下是Y轴的正半轴,有且只有一个,唯一不变
绘图坐标系
    它不是唯一不变的,它与Canvas的Matrix有关系,当Matrix发生改变的时候,绘图坐标系对应的进行改变,
    同时这个过程是不可逆的(save和restore方法来保存和还原变化操作)
    Matrix又是通过我们设置translate、rotate、scale、skew来进行改变的
  Canvas的状态保存---状态栈、Layer栈
状态栈--save、 restore方法来保存和还原变换操作Matrix以及Clip剪裁
    也可以通过restoretoCount直接还原到对应栈的保存状态
Layer栈--- saveLayer的时候都会新建一个透明的图层(离屏Bitmap-离屏缓冲),并且会将saveLayer之前的一些Canvas操作延续过来
      后续的绘图操作都在新建的layer上面进行
      当我们调用restore 或者 restoreToCount 时 更新到对应的图层和画布上

更多Android 知识点可参考

Android 性能调优系列https://0a.fit/dNHYY

Android 车载学习指南https://0a.fit/jdVoy

Android Framework核心知识点笔记https://0a.fit/acnLL

Android 八大知识体系https://qr21.cn/CaZQLo?BIZ=ECOMMERCE

Android 中高级面试题锦https://0a.fit/YXwVq

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12002.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数组与指针实验

指针与数组实验 先简单看一下以下c代码 #include <stdio.h> #include <stdlib.h> int main() {char array[10];array[0] 0x56;array[1] 0x78;array[9] 0x12;char *p (char *)malloc(10);p[0] 0x34;p[1] 0x12;printf("%p\n%p\n%p\n%p\n", array, …

[附源码]Python计算机毕业设计 家乡旅游文化推广网站

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

【微信小程序】.js文件的代码结构与Page页面的生命周期

&#x1f3c6;今日学习目标&#xff1a;第十期——.js文件的代码结构与page页面的生命周期 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;25分钟 &#x1f389;专栏系列&#xff1a;我的第一个微信小程序 文章目录…

尚医通 (二十二) --------- MongoDB 简介

目录一、NoSQL 简介二、什么是 MongoDB ?三、MongoDB 特点四、安装 MongoDB1. 数据库2. 文档3. 集合4. 适用场景五、MongoDB 概念解析一、NoSQL 简介 NoSQL (NoSQL Not Only SQL)&#xff0c;意即反 SQL 运动&#xff0c;指的是非关系型的数据库&#xff0c;是一项全新的数据…

使用SRM系统有哪些供应商管理优势?

SRM系统就是我们常说的供应商关系管理&#xff0c;它主要是用来改善与供应链上游供应商的关系&#xff0c;改善企业与供应商的关系的新型管理机制&#xff0c;使双方关系更加紧密&#xff0c;从而实现供应双赢。相信对SRM供应商关系管理系统有了解的朋友们并不陌生&#xff0c;…

[力扣] 剑指 Offer 第二天 - 复杂链表的复制

这里写目录标题题目来源题目描述示例示例 1示例 2示例 3示例 4题目解析算法 1代码实现执行结果复杂度分析算法 2代码实现执行结果复杂度分析总结耐心和持久胜过激烈和狂热。 题目来源 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode…

鲁棒局部均值分解 (RLMD)(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

如何在Ubuntu 22.04使用wine安装windows版本微信

继上篇《如何在 Ubuntu 22.04 上安装 最新版本Wine》成功安装wine。使用以下命令安装&#xff1a; $ sudo dpkg --add-architecture i386 && sudo wget -qO - https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add - && sudo apt-add-repository…

从零开始学前端:json对象,对象的序列化和反序列化 --- 今天你学习了吗?(JS:Day16)

从零开始学前端&#xff1a;程序猿小白也可以完全掌握&#xff01;—今天你学习了吗&#xff1f;&#xff08;JS&#xff09; 复习&#xff1a;从零开始学前端&#xff1a;购物车和鲜花价格排序 — 今天你学习了吗&#xff1f;&#xff08;JS&#xff1a;Day15&#xff09; 文…

JVM基础

JVM简介 JVM是java虚拟机简称&#xff0c;JVM是一种用于计算设备的规范&#xff0c;它是一个虚构出来的计算机&#xff0c;是通过在实际计算机上仿真模拟各种计算机功能来实现的。也正式因为有了它&#xff0c;java才具有了跨平台特性&#xff0c;”一次编译&#xff0c;到处运…

隐私计算行业应用情况和标准化现状

开放隐私计算 2022-11-16 19:17 发表于浙江 开放隐私计算 开放隐私计算OpenMPC是国内第一个且影响力最大的隐私计算开放社区。社区秉承开放共享的精神&#xff0c;专注于隐私计算行业的研究与布道。社区致力于隐私计算技术的传播&#xff0c;愿成为中国 “隐私计算最后一公里的…

如何利用快解析实现个人私有云

个人云盘曾火爆一时&#xff0c;原因有二&#xff0c;一是国内可选择的网盘越来越少&#xff0c;自建网盘无疑是一个不错的选项&#xff0c;毕竟网盘是数据存在别人那&#xff0c;始终让人心里不安&#xff0c;如同车辆一样&#xff0c;云盘就是公交车&#xff0c;私有云盘就是…

Matlab:在文本和值之间转换datetimeduration

Matlab&#xff1a;在文本和值之间转换datetimeduration将值转换为文本datetimeduration将数组转换为字符串数组指定输出文本的格式指定输出文本的区域设置将文本转换为值datetime解释输入文本的格式将文本转换为值duration解释输入文本的格式从文件中读取日期和时间此示例演示…

面试经常问的Linux进程到底是什么呢?1W字从0到1详解进程的所有细节!

目录 1.冯诺伊曼体系 2.操作系统 3.进程 4.进程的三种状态 4.1概念 4.2Linux中不同进程状态 5.进程的优先级 6.进程的几个其它概念 7.进程的切换 8.环境变量 8.1环境变量的概念 8.2常见的环境变量 8.3和环境变量相关的命令 8.4查看环境变量的方法 8.5通过代码获取环境变量的方式…

微信小程序 webview组件内嵌H5二维码识别

结果 支持公众号二维码支持小程序码支持个人名片支持群聊不支持页面二维码 做法 点击图片触发previewImage预览&#xff0c;长按识别 <script type"text/javascript" src"https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>wx.p…

IT部门不想每天忙“取数”,花了几百万买系统,还是这个办法有效

待过几年中小型传统企业的应该都有这个感知&#xff1a;虽然每个月都在采购新的软件系统&#xff0c;但整个公司的数字化仍旧一团糟&#xff0c;数字化转型是越搞越回去了。 这也很好解释。 传统企业业务部门繁多&#xff0c;在搞信息化阶段采购了很多业务系统&#xff0c;比…

C++ 11

文章目录1. 列表初始化1.1 列表初始化的使用格式1.1.1 内置类型1.1.2 自定义类型的列表初始化1.2 列表初始化的本质2. 变量类型的推导2.1 auto 关键字2.2 decltype类型推导3. 范围for4. final与override5. 智能指针6. 新增容器6.1 静态数组array6.2 单向链表 forward_list6.3 u…

【问卷调查发布系统的设计与实现】

系列文章目录 在当前社会&#xff0c;随着信息化的高速发展&#xff0c;收集数据的传统方法——问卷调查法也在发生改变。此问卷调查系统&#xff0c;可以帮助用户在短时间内创建收集数据的问卷&#xff0c;目的是突出高效性、绿色性以及便捷性。在设计过程中&#xff0c;分析…

web网页设计期末课程大作业:漫画网站设计——我的英雄(5页)学生个人单页面网页作业 学生网页设计成品 静态HTML网页单页制作

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 ⚽精彩专栏推荐&#x1…

Charles使用教程

目录预备知识1.HTTP调试代理工具原理2.Charles简介实验目的实验环境实验步骤一实验步骤二实验步骤三预备知识 1.HTTP调试代理工具原理 HTTP调试代理工具广泛应用于web程序开发、安全测试、流量分析等工作。HTTP调试代理工具工作于TCP/IP参考模型中的应用层&#xff0c;浏览器…