Android原生实现控件阴影方案(API28及以上)

news2024/11/24 19:45:24

Android控件的阴影效果的实现方式有很多种,这里介绍一下另一种Android原生的阴影实现方案(API28及以上)。

我们利用elevation、outlineAmbientShowColor、outlineSpotShadowColor来实现一个带阴影的Button。

实现效果如下图,阴影宽度分别为2dp、10dp、30dp:
在这里插入图片描述

阴影原理

阴影效果的实现采用的是Android原生的View的属性,拔高Z轴。Z轴会让View产生阴影的效果。

阴影的效果由Z轴(elevation)、光源(shadowColor)和环境阴影透明度(ambientShadowAlpha)三者综合决定的。所以我们需要对这三者进行合理的设置。环境阴影透明度是通过主题theme设置的,一般为1,因此起决定性作用的是Z轴(elevation)和光源(shadowColor)。

光源又分为主光源(outlineSpotShadowColor)和环境光源(outlineAmbientShowColor)。

阴影原理
在这里插入图片描述

实现

属性

在attrs.xml文件中,创建Button的自定义属性:

    <declare-styleable name="shape_button">
        <attr name="android:background" />
        <attr name="android:textColor" />
        
        <!-- shadow 相关-->
        <attr name="carbon_elevation" />
        <attr name="carbon_elevationShadowColor" />
        <attr name="carbon_elevationAmbientShadowColor" />
        <attr name="carbon_elevationSpotShadowColor" />

        <attr name="android:textStyle" />
        <attr name="android:textAppearance" />
        <attr name="android:singleLine" />
        <attr name="android:maxLines" />
        <attr name="android:textAllCaps" />
    </declare-styleable>

另外,创建一个styles_button.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="carbon_Button" parent="@android:style/Widget.Button">
        <item name="android:gravity">center_vertical|center_horizontal</item>
        <item name="android:paddingTop">0dp</item>
        <item name="android:paddingBottom">0dp</item>
        <item name="android:paddingLeft">@dimen/carbon_paddingHalf</item>
        <item name="android:paddingRight">@dimen/carbon_paddingHalf</item>
        <item name="android:minHeight">36dp</item>
        <item name="android:minWidth">88dp</item>
        <item name="carbon_elevation">@dimen/carbon_elevationButton</item>
        <item name="android:textColor">@null</item>
        <item name="android:textAppearance">?attr/carbon_textAppearanceButton</item>
        <item name="android:maxLines">1</item>
        <item name="android:background">@color/carbon_defaultColorControl</item>
    </style>
</resources>

自定义ShapeButton

创建一个阴影接口,这样所有需要阴影的控件实现这个接口就好了:

/**
 * 阴影相关
 */
public interface ShadowView {
    /**
     * The elevation value. There are useful values of elevation defined in xml as
     * carbon_elevationFlat, carbon_elevationLow, carbon_elevationMedium, carbon_elevationHigh,
     * carbon_elevationMax.
     */
    float getElevation();

    void setElevation(float elevation);

    float getTranslationZ();

    void setTranslationZ(float translationZ);

    ColorStateList getElevationShadowColor();

    void setElevationShadowColor(ColorStateList color);

    void setElevationShadowColor(int color);

    int getOutlineAmbientShadowColor();

    void setOutlineAmbientShadowColor(int color);

    void setOutlineAmbientShadowColor(ColorStateList color);

    int getOutlineSpotShadowColor();

    void setOutlineSpotShadowColor(int color);

    void setOutlineSpotShadowColor(ColorStateList color);

}

创建ShapeButton.java 实现ShadowView:

public class ShapeButton extends AppCompatButton implements ShadowView{
    public ShapeButton(@NonNull Context context) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
    }

    public ShapeButton(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initButton(attrs, android.R.attr.buttonStyle, R.style.carbon_Button);
    }

    public ShapeButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initButton(attrs, defStyleAttr, R.style.carbon_Button);
    }

    public ShapeButton(Context context, String text, OnClickListener listener) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
        setText(text);
        setOnClickListener(listener);
    }

    private static int[] elevationIds = new int[]{
            R.styleable.shape_button_carbon_elevation,
            R.styleable.shape_button_carbon_elevationShadowColor,
            R.styleable.shape_button_carbon_elevationAmbientShadowColor,
            R.styleable.shape_button_carbon_elevationSpotShadowColor
    };
	// 重点就是利用下面的属性:
	// 拔高Z轴可以通过控制elevation和translationZ。区别:
	// elevation:一般是写在 xml 文件中做静态配置,单纯的控制Z轴;
	// translateZ:除了控制Z轴,还可以用来控制动画效果,比如我们点击按钮时希望它有一个弹起的效果。
	// 由于我们只需要实现阴影效果,所以我们只关注elevation即可。
    private float elevation = 0;
    private float translationZ = 0;
    // 表示光源的颜色
    private ColorStateList ambientShadowColor, spotShadowColor;

    private void initButton(AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.shape_button, defStyleAttr, defStyleRes);
        // todo-获取自定义属性的初始化
        //  Carbon.initElevation(this, a, elevationIds);
        a.recycle();
    }

    @Override
    public float getElevation() {
        return elevation;
    }

    @Override
    public void setElevation(float elevation) {
      // todo-设置elevation的值
    }

    @Override
    public float getTranslationZ() {
        return translationZ;
    }

    public void setTranslationZ(float translationZ) {
      // todo-设置translationZ的值
    }

    @Override
    public ColorStateList getElevationShadowColor() {
        return ambientShadowColor;
    }

    @Override
    public void setElevationShadowColor(ColorStateList shadowColor) {
        // todo-设置ElevationShadowColor的值,这个光源值表示统一主光源和环境光源的颜色
    }

    @Override
    public void setElevationShadowColor(int color) {
       // todo-设置ElevationShadowColor的值,这个光源值表示统一主光源和环境光源的颜色
    }

    @Override
    public void setOutlineAmbientShadowColor(ColorStateList color) {
        // todo-设置OutlineAmbientShadowColor的值
    }

    @Override
    public void setOutlineAmbientShadowColor(int color) {
        // todo-设置OutlineAmbientShadowColor的值
    }

    @Override
    public int getOutlineAmbientShadowColor() {
        return ambientShadowColor.getDefaultColor();
    }

    @Override
    public void setOutlineSpotShadowColor(int color) {
         // todo-设置OutlineSpotShadowColor的值
    }

    @Override
    public void setOutlineSpotShadowColor(ColorStateList color) {
       // todo-设置OutlineSpotShadowColor的值
    }

    @Override
    public int getOutlineSpotShadowColor() {
        return ambientShadowColor.getDefaultColor();
    }
}

属性初始化

public class Carbon {

    public static final boolean IS_LOLLIPOP_OR_HIGHER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;

    public static final boolean IS_PIE_OR_HIGHER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
    private Carbon() {
    }

    public static void initElevation(ShadowView view, TypedArray a, int[] ids) {
        int carbon_elevation = ids[0];
        int carbon_shadowColor = ids[1];
        int carbon_ambientShadowColor = ids[2];
        int carbon_spotShadowColor = ids[3];
		// 获取xml设置的elevation属性的值,并设置给View
        float elevation = a.getDimension(carbon_elevation, 0);
        view.setElevation(elevation);
        // 获取xml设置的shadowColor属性的值,并设置给View
        ColorStateList shadowColor = a.getColorStateList(carbon_shadowColor);
        view.setElevationShadowColor(shadowColor != null ? shadowColor.withAlpha(255) : null);
        //如果有设置环境光源 ambientShadowColor,则设置给View
        if (a.hasValue(carbon_ambientShadowColor)) {
            ColorStateList ambientShadowColor = a.getColorStateList(carbon_ambientShadowColor);
            view.setOutlineAmbientShadowColor(ambientShadowColor != null ? ambientShadowColor.withAlpha(255) : null);
        }
        //如果有设置主光源spotShadowColor,则设置给View
        if (a.hasValue(carbon_spotShadowColor)) {
            ColorStateList spotShadowColor = a.getColorStateList(carbon_spotShadowColor);
            view.setOutlineSpotShadowColor(spotShadowColor != null ? spotShadowColor.withAlpha(255) : null);
        }
    }
}

设置阴影相关属性

设置Elevation

@Override
    public void setElevation(float elevation) {
    // 针对API=28以上
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setElevation(elevation);
            super.setTranslationZ(translationZ);
        } else if (Carbon.IS_LOLLIPOP_OR_HIGHER) {
            if (ambientShadowColor == null || spotShadowColor == null) {
                super.setElevation(elevation);
                super.setTranslationZ(translationZ);
            } else {
                super.setElevation(0);
                super.setTranslationZ(0);
            }
        } else if (elevation != this.elevation && getParent() != null) {
            ((View) getParent()).postInvalidate();
        }
        this.elevation = elevation;
    }

设置translationZ

public void setTranslationZ(float translationZ) {
        if (translationZ == this.translationZ)
            return;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setTranslationZ(translationZ);
        } else if (Carbon.IS_LOLLIPOP_OR_HIGHER) {
            if (ambientShadowColor == null || spotShadowColor == null) {
                super.setTranslationZ(translationZ);
            } else {
                super.setTranslationZ(0);
            }
        } else if (translationZ != this.translationZ && getParent() != null) {
            ((View) getParent()).postInvalidate();
        }
        this.translationZ = translationZ;
    }

设置主光源和环境光源

@Override
    public void setElevationShadowColor(ColorStateList shadowColor) {
        ambientShadowColor = spotShadowColor = shadowColor;
        setElevation(elevation);
        setTranslationZ(translationZ);
    }
    
    @Override
    public void setElevationShadowColor(int color) {
        ambientShadowColor = spotShadowColor = ColorStateList.valueOf(color);
        setElevation(elevation);
        setTranslationZ(translationZ);
    }

// 设置环境光源
@Override
    public void setOutlineAmbientShadowColor(ColorStateList color) {
        ambientShadowColor = color;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setOutlineAmbientShadowColor(color.getColorForState(getDrawableState(), color.getDefaultColor()));
        } else {
            setElevation(elevation);
            setTranslationZ(translationZ);
        }
    }

# 设置主光源
@Override
    public void setOutlineSpotShadowColor(ColorStateList color) {
        spotShadowColor = color;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setOutlineSpotShadowColor(color.getColorForState(getDrawableState(), color.getDefaultColor()));
        } else {
            setElevation(elevation);
            setTranslationZ(translationZ);
        }

    }

@Override
    public void draw(Canvas canvas) {
        if (Carbon.IS_PIE_OR_HIGHER) {
            if (spotShadowColor != null)
                super.setOutlineSpotShadowColor(spotShadowColor.getColorForState(getDrawableState(), spotShadowColor.getDefaultColor()));
            if (ambientShadowColor != null)
                super.setOutlineAmbientShadowColor(ambientShadowColor.getColorForState(getDrawableState(), ambientShadowColor.getDefaultColor()));
        }
        super.draw(canvas);
    }

怎样使用

<com.chinatsp.demo1.shadow.ShapeButton
                android:layout_width="56dp"
                android:layout_height="56dp"
                android:layout_margin="@dimen/carbon_padding"
                android:background="#ffffff"
                android:stateListAnimator="@null"
                android:text="TOM"
                android:elevation="20dp"
                app:carbon_elevationShadowColor="@color/carbon_amber_700" />

完整代码:

public class ShapeButton extends AppCompatButton implements ShadowView{
    public ShapeButton(@NonNull Context context) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
    }

    public ShapeButton(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initButton(attrs, android.R.attr.buttonStyle, R.style.carbon_Button);
    }

    public ShapeButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initButton(attrs, defStyleAttr, R.style.carbon_Button);
    }

    public ShapeButton(Context context, String text, OnClickListener listener) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
        setText(text);
        setOnClickListener(listener);
    }

    private static int[] elevationIds = new int[]{
            R.styleable.shape_button_carbon_elevation,
            R.styleable.shape_button_carbon_elevationShadowColor,
            R.styleable.shape_button_carbon_elevationAmbientShadowColor,
            R.styleable.shape_button_carbon_elevationSpotShadowColor
    };

    private float elevation = 0;
    private float translationZ = 0;
    private ColorStateList ambientShadowColor, spotShadowColor;

    private void initButton(AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.shape_button, defStyleAttr, defStyleRes);
        Carbon.initElevation(this, a, elevationIds);
        a.recycle();
    }

    @Override
    public float getElevation() {
        return elevation;
    }

    @Override
    public void setElevation(float elevation) {
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setElevation(elevation);
            super.setTranslationZ(translationZ);
        } else if (Carbon.IS_LOLLIPOP_OR_HIGHER) {
            if (ambientShadowColor == null || spotShadowColor == null) {
                super.setElevation(elevation);
                super.setTranslationZ(translationZ);
            } else {
                super.setElevation(0);
                super.setTranslationZ(0);
            }
        } else if (elevation != this.elevation && getParent() != null) {
            ((View) getParent()).postInvalidate();
        }
        this.elevation = elevation;
    }

    @Override
    public float getTranslationZ() {
        return translationZ;
    }

    public void setTranslationZ(float translationZ) {
        if (translationZ == this.translationZ)
            return;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setTranslationZ(translationZ);
        } else if (Carbon.IS_LOLLIPOP_OR_HIGHER) {
            if (ambientShadowColor == null || spotShadowColor == null) {
                super.setTranslationZ(translationZ);
            } else {
                super.setTranslationZ(0);
            }
        } else if (translationZ != this.translationZ && getParent() != null) {
            ((View) getParent()).postInvalidate();
        }
        this.translationZ = translationZ;
    }

    @Override
    public ColorStateList getElevationShadowColor() {
        return ambientShadowColor;
    }

    @Override
    public void setElevationShadowColor(ColorStateList shadowColor) {
        ambientShadowColor = spotShadowColor = shadowColor;
        setElevation(elevation);
        setTranslationZ(translationZ);
    }

    @Override
    public void setElevationShadowColor(int color) {
        ambientShadowColor = spotShadowColor = ColorStateList.valueOf(color);
        setElevation(elevation);
        setTranslationZ(translationZ);
    }

    @Override
    public void setOutlineAmbientShadowColor(ColorStateList color) {
        ambientShadowColor = color;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setOutlineAmbientShadowColor(color.getColorForState(getDrawableState(), color.getDefaultColor()));
        } else {
            setElevation(elevation);
            setTranslationZ(translationZ);
        }
    }

    @Override
    public void setOutlineAmbientShadowColor(int color) {
        setOutlineAmbientShadowColor(ColorStateList.valueOf(color));
    }

    @Override
    public int getOutlineAmbientShadowColor() {
        return ambientShadowColor.getDefaultColor();
    }

    @Override
    public void setOutlineSpotShadowColor(int color) {
        setOutlineSpotShadowColor(ColorStateList.valueOf(color));
    }

    @Override
    public void setOutlineSpotShadowColor(ColorStateList color) {
        spotShadowColor = color;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setOutlineSpotShadowColor(color.getColorForState(getDrawableState(), color.getDefaultColor()));
        } else {
            setElevation(elevation);
            setTranslationZ(translationZ);
        }

    }

    @Override
    public int getOutlineSpotShadowColor() {
        return ambientShadowColor.getDefaultColor();
    }

    @Override
    public void draw(Canvas canvas) {
        if (Carbon.IS_PIE_OR_HIGHER) {
            if (spotShadowColor != null)
                super.setOutlineSpotShadowColor(spotShadowColor.getColorForState(getDrawableState(), spotShadowColor.getDefaultColor()));
            if (ambientShadowColor != null)
                super.setOutlineAmbientShadowColor(ambientShadowColor.getColorForState(getDrawableState(), ambientShadowColor.getDefaultColor()));
        }
        super.draw(canvas);
    }
}

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

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

相关文章

第0章 前言

大家好&#xff0c;我叫 Rick Blyth&#xff0c;我是一名软件开发人员、企业家、创始人、博主和父亲 &#x1f44b; 几年前&#xff0c;在成功构建和扩展了一些自筹资金的 Micro SaaS&#xff08;微型 SaaS&#xff09; 应用后&#xff0c;我放弃了&#xff08;薪水不错但很糟…

SpringBoot 实现数据脱敏

SpringBoot 实现数据脱敏 前言Hutool 实现数据脱敏引入依赖脱敏工具类代码实现 使用注解的方式定义枚举自定义序列化类定义注解测试 前言 数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形&#xff0c;实现敏感隐私数据的可靠保护。 数据脱敏常用规则有替换、重排、加密…

接口测试和性能测试的区别

最近我在一个论坛上看到了一个关于性能测试和接口测试的经典问题&#xff0c;问题如下&#xff1a; 问题&#xff1a;后端性能测试&#xff0c;一个功能其实都是由后台多个接口组成的。 例如一个单据的保存&#xff0c;可能后台需要调用几个接口。用LR录制这个功能做性能测试。…

Java如何进行数据脱敏

1.SQL数据脱敏实现 MYSQL(电话号码,身份证)数据脱敏的实现 1 2 3 4 5 6 7 8 -- CONCAT()、LEFT()和RIGHT()字符串函数组合使用&#xff0c;请看下面具体实现 -- CONCAT(str1,str2,…)&#xff1a;返回结果为连接参数产生的字符串 -- LEFT(str,len)&#xff1a;返回从字符串st…

Vue Router的进阶

进阶 导航守卫 官方文档上面描述的会比较深奥&#xff0c;而守卫类型也比较多&#xff0c;其中包含了全局前置守卫、全局解析守卫、全局后置钩子、路由独享守卫、组件内守卫。每一种守卫的作用和用法都不相同。这会使得大家去学习的时候觉得比较困难&#xff0c;这边主要介绍…

如何平衡需求的优先级冲突?

每个项目都有各种需求&#xff0c;如业务需求、技术需求、用户需求、系统需求。我们需要对这些需求进行优先级排序&#xff0c;平衡不同利益相关者的需求&#xff0c;以更好满足客户需求&#xff0c;确保关键业务目标得到优先满足&#xff0c;并合理分配资源&#xff0c;避免资…

移植 NetXDuo 到 STM32F4 芯片

移植 NetXDuo 到 STM32F4 芯片 1. NetXDuo 和 ThreadX 源码获取2. 准备工作2.1 基本工程模板获取 —— CubeMx 3.ThreadX 移植3.1 添加到工程3.2 文件修改3.3 补充完成回调函数 4. NetXDuo 移植4.1 将 NetXDuo 添加到工程4.2 驱动层实现4.3 测试 1. NetXDuo 和 ThreadX 源码获取…

RT-Thread 中断管理(学习二)

中断的底半处理 RT-Thread不对中断服务程序所需要的处理时间做任何假设、限制&#xff0c;但如同其它实时操作系统或非实时操作系统一样&#xff0c;用户需要保证所有的中断服务程序在尽可能短的时间内完成。这样在发生中断嵌套&#xff0c;或屏蔽了相应中断源的过程中&#x…

小黑开始了拉歌训练,第一次进入部室馆,被通知要去当主持人心里有些紧张的leetcode之旅:337. 打家劫舍 III

小黑代码&#xff08;小黑卡在了bug中&#xff0c;上午一步步探索做出&#xff0c;非常NB!!!&#xff09; # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left lef…

Hive窗口函数回顾

1.语法 1.1 基于行的窗口函数 Hive的窗口函数分为两种类型&#xff0c;一种是基于行的窗口函数&#xff0c;即将某个字段的多行限定为一个范围&#xff0c;对范围内的字段值进行计算&#xff0c;最后将形成的字段拼接在该表上。 注意&#xff1a;在进行窗口函数计算之前&#…

X86指令基本格式

X86指令基本格式 1 什么是机器码2 X86指令基本格式3 指令前缀3.1 第一组&#xff1a;封锁和重复执行前缀3.2 第二组&#xff1a;段前缀3.3 第三组&#xff1a;修改操作数默认长度3.4 第四组&#xff1a;修改默认地址长度 4 操作码5 ModR/M与SIB5.1 ModR/M字节5.2 SIB字节 6 地址…

uCharts常用图表组件demo

带渐变阴影的曲线图 <view class"charts-box"><qiun-data-charts type"area" :opts"opts" :chartData"chartData" :ontouch"true":background"rgba(256,256,256,0)" /> </view>data(){return{…

嵌入式学习(1)HAL库

文章目录 1.HAL库文件介绍2.HAL库编程目录结构3.使用cubemx生成HAL库编程目录结构 1.HAL库文件介绍 2.HAL库编程目录结构 3.使用cubemx生成HAL库编程目录结构

【JavaEE重点知识归纳】第7节:类和对象

目录 一&#xff1a;了解面向对象 1.什么是面向对象 2.面向对象和面向过程区分 二&#xff1a;类定义和使用 1.什么是类 2.练习&#xff1a;定义一个学生类 三&#xff1a;类的实例化 1.什么是实例化 2.类和对象的说明 四&#xff1a;认识this 1.为什么要有this引用…

rails 常量自动加载和重新加载机制

在Rails中&#xff0c;有一个称为"常量自动加载和重新加载机制"的功能&#xff0c;它使得在开发和生产环境中能够自动加载和重新加载类和模块。这个机制允许您不必手动管理类的加载&#xff0c;使得开发更加方便。 快乐学习&#xff1a; 自动加载、重新加载 自动加…

Yii2全拦截路由catchAll的使用

定义&#xff1a;catchAll 路由&#xff08;全拦截路由&#xff09; 应用场景&#xff1a;网站维护的时候需要向用户抛出一个维护的页面&#xff0c;方便提醒用户 使用方法&#xff1a; 1、在应用配置中设置 yii\web\Application::catchAll 属性 2、新增对应的控制器方法 3、…

【Putty】win10 / win 11:SSH 远程连接工具 Putty 下载、安装

目录 一、Jmerter 连接 SSH 隧道的 mysql&#xff08;不可行&#xff09; 二、Putty 介绍 三、Putty 的下载 四、Putty 无需安装直接使用 五、Putty 使用 &#xff08;1&#xff09;我需要连接 ssh 隧道的 MySQL 参数如下 &#xff08;2&#xff09;Putty 使用教程 一、…

MA-SAM:模态不可知的三维医学图像分割SAM自适应

论文&#xff1a;MA-SAM: Modality-agnostic SAM Adaptation for 3D Medical Image Segmentation | Papers With Code 代码&#xff1a;GitHub - cchen-cc/MA-SAM: PyTorch implementation for MA-SAM 机构&#xff1a;a)高级医疗计算和分析中心&#xff0c;麻省总医院和哈佛…

华为云开源低代码引擎 TinyEngine 正式发布

随着企业对于低代码开发平台的需求日益增长,急需一个通用的解决方案来满足各种低代码平台的开发需求。正是在这种情况下,低代码引擎应运而生。它是一种通用的开发框架,通过对低代码平台系统常用的功能进行解构,将其划分为多个功能模块,并为每个模块定义了相应的协议和开发…

Go 语言中 panic 和 recover 搭配使用

本次主要聊聊 Go 语言中关于 panic 和 recover 搭配使用 &#xff0c;以及 panic 的基本原理 最近工作中审查代码的时候发现一段代码&#xff0c;类似于如下这样&#xff0c;将 recover 放到一个子协程里面&#xff0c;期望去捕获主协程的程序异常 看到此处&#xff0c;是否会…