Android 几个简单的自定义对话框介绍

Android 几个简单的自定义对话框介绍

文章目录

    • 一、前言
    • 二、对话框相关内容
      • 1、效果
      • 2、对话框显示的调用代码
        • (1)原生对话框代码:
        • (2)自定义对话框代码:
      • 3、对话框SweetAlertDialog 主要实现代码:
      • 4、优点和缺点
        • (1)优点
        • (2)缺点
        • (3)其他扩展
    • 三、其他
      • 1、小结
      • 2、Android对话框的使用总结
      • 3、Android加载中对话框示例

一、前言

Android 对话框直接使用肯定是不好看的,如果需要添加取消和确定按钮,肯定是需要进行自定义修改的。

本文简单介绍一个简单的可扩展的对话框,并且支持不同类型样式,加了点小动画。

有需要的可以看看。

二、对话框相关内容

1、效果

在这里插入图片描述

七个demo对话框显示,项目中可以根据实际情况进行使用。

原生对话框标题和内容是靠左的,如果显示默认的按钮是靠右下角的。

自定义的对话框则可以自己决定居中还是靠左和显示自己需要的图片。

2、对话框显示的调用代码

(1)原生对话框代码:
Dialog alertDialog = new AlertDialog.Builder(this).setTitle("对话框的标题").setMessage("对话框的内容").setIcon(R.drawable.ic_launcher).create();
alertDialog.show();

虽然 原生的AlertDialog 调用不复杂,但是显示有点丑,并且大小在不同设备上显示大小是不一样的。

AlertDialog 也是可以行决定是否显示确定和取消按钮的,监听setPositiveButton和setNegativeButton方法即可。

(2)自定义对话框代码:
 //(1)默认字符串显示,显示一个确认按钮,只是用来测试new SweetAlertDialog(this).show();//(2)修改标题和文本内容,显示一个确认按钮new SweetAlertDialog(this).setTitleText("Title!").setContentText("It's pretty, isn't it?").show();//(3)修改标题和文本内容和类型图标,显示一个确认按钮new SweetAlertDialog(this, SweetAlertDialog.CUSTOM_IMAGE_TYPE).setTitleText("Sweet!").setContentText("Here's a custom image.").setCustomImage(R.drawable.custom_img).show();大部分场景上面三个代码比较常用。SweetAlertDialog 的类别,一种有五种,每种类别会选择不同的图标显示。public static final int NORMAL_TYPE = 0;public static final int ERROR_TYPE = 1;public static final int SUCCESS_TYPE = 2;public static final int WARNING_TYPE = 3;public static final int CUSTOM_IMAGE_TYPE = 4;//(4)修改标题和文本内容和类型图标,显示一个确认按钮,确定后执行相应的动作new SweetAlertDialog(this, SweetAlertDialog.SUCCESS_TYPE).setTitleText("Good job!").setContentText("You clicked the button!").setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {@Overridepublic void onClick(SweetAlertDialog sDialog) {// reuse previous dialog instanceToast.makeText(SampleActivity.this, "confirm" ,Toast.LENGTH_SHORT).show();sDialog.dismiss();}})
.show();//(5)修改标题和文本内容和类型图标,点击第一次确定后,还要点一次确定new SweetAlertDialog(this, SweetAlertDialog.WARNING_TYPE).setTitleText("Are you sure?").setContentText("Won't be able to recover this file!").setCancelText("No,cancel plx!").setConfirmText("Yes,delete it!").showCancelButton(true).setCancelClickListener(new SweetAlertDialog.OnSweetClickListener() {@Overridepublic void onClick(SweetAlertDialog sDialog) {// reuse previous dialog instancesDialog.setTitleText("Cancelled!").setContentText("Your imaginary file is safe :)").setConfirmText("OK").showCancelButton(false).setCancelClickListener(null).setConfirmClickListener(null).changeAlertType(SweetAlertDialog.ERROR_TYPE);// or you can new a SweetAlertDialog to show}
})
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {@Overridepublic void onClick(SweetAlertDialog sDialog) {sDialog.setTitleText("Deleted!").setContentText("Your imaginary file has been deleted!").setConfirmText("OK").showCancelButton(false).setCancelClickListener(null).setConfirmClickListener(null).changeAlertType(SweetAlertDialog.SUCCESS_TYPE);}
})
.show();

项目中可以根据实际情况进行适配修改,显示一个按钮还是两个按钮都是可以选择的。

3、对话框SweetAlertDialog 主要实现代码:

package com.my.beautifuldialog;import android.app.Dialog;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Transformation;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;import java.util.List;public class SweetAlertDialog extends Dialog implements View.OnClickListener {private View mDialogView;private AnimationSet mModalInAnim;private AnimationSet mModalOutAnim;private Animation mOverlayOutAnim;private Animation mErrorInAnim;private AnimationSet mErrorXInAnim;private AnimationSet mSuccessLayoutAnimSet;private Animation mSuccessBowAnim;private TextView mTitleTextView;private TextView mContentTextView;private String mTitleText;private String mContentText;private boolean mShowCancel;private String mCancelText;private String mConfirmText;private int mAlertType;private FrameLayout mErrorFrame;private FrameLayout mSuccessFrame;private SuccessTickView mSuccessTick;private ImageView mErrorX;private View mSuccessLeftMask;private View mSuccessRightMask;private Drawable mCustomImgDrawable;private ImageView mCustomImage;private Button mConfirmButton;private Button mCancelButton;private FrameLayout mWarningFrame;private OnSweetClickListener mCancelClickListener;private OnSweetClickListener mConfirmClickListener;private boolean mCanceledOnTouchOutside = false;public static final int NORMAL_TYPE = 0;public static final int ERROR_TYPE = 1;public static final int SUCCESS_TYPE = 2;public static final int WARNING_TYPE = 3;public static final int CUSTOM_IMAGE_TYPE = 4;public static interface OnSweetClickListener {public void onClick(SweetAlertDialog sweetAlertDialog);}public SweetAlertDialog(Context context) {this(context, NORMAL_TYPE);}public SweetAlertDialog(Context context, int alertType) {super(context, R.style.alert_dialog);setCancelable(true);setCanceledOnTouchOutside(false);mAlertType = alertType;mErrorInAnim = OptAnimationLoader.loadAnimation(getContext(), R.anim.error_frame_in);mErrorXInAnim = (AnimationSet)OptAnimationLoader.loadAnimation(getContext(), R.anim.error_x_in);// 2.3.x system don't support alpha-animation on layer-list drawable// remove it from animation setif (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { //Android3 一下,其实没啥必要判断了List<Animation> childAnims = mErrorXInAnim.getAnimations();int idx = 0;for (;idx < childAnims.size();idx++) {if (childAnims.get(idx) instanceof AlphaAnimation) {break;}}if (idx < childAnims.size()) {childAnims.remove(idx);}}mSuccessBowAnim = OptAnimationLoader.loadAnimation(getContext(), R.anim.success_bow_roate);mSuccessLayoutAnimSet = (AnimationSet)OptAnimationLoader.loadAnimation(getContext(), R.anim.success_mask_layout);mModalInAnim = (AnimationSet) OptAnimationLoader.loadAnimation(getContext(), R.anim.modal_in);mModalOutAnim = (AnimationSet) OptAnimationLoader.loadAnimation(getContext(), R.anim.modal_out);mModalOutAnim.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {mDialogView.setVisibility(View.GONE);mDialogView.post(new Runnable() {@Overridepublic void run() {SweetAlertDialog.super.dismiss();}});}@Overridepublic void onAnimationRepeat(Animation animation) {}});// dialog overlay fade outmOverlayOutAnim = new Animation() {@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {WindowManager.LayoutParams wlp = getWindow().getAttributes();wlp.alpha = 1 - interpolatedTime;getWindow().setAttributes(wlp);}};mOverlayOutAnim.setDuration(120);}protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.alert_dialog);mDialogView = getWindow().getDecorView().findViewById(android.R.id.content);mTitleTextView = (TextView)findViewById(R.id.title_text);mContentTextView = (TextView)findViewById(R.id.content_text);mErrorFrame = (FrameLayout)findViewById(R.id.error_frame);mErrorX = (ImageView)mErrorFrame.findViewById(R.id.error_x);mSuccessFrame = (FrameLayout)findViewById(R.id.success_frame);mSuccessTick = (SuccessTickView)mSuccessFrame.findViewById(R.id.success_tick);mSuccessLeftMask = mSuccessFrame.findViewById(R.id.mask_left);mSuccessRightMask = mSuccessFrame.findViewById(R.id.mask_right);mCustomImage = (ImageView)findViewById(R.id.custom_image);mWarningFrame = (FrameLayout)findViewById(R.id.warning_frame);mConfirmButton = (Button)findViewById(R.id.confirm_button);mCancelButton = (Button)findViewById(R.id.cancel_button);mConfirmButton.setOnClickListener(this);mCancelButton.setOnClickListener(this);setTitleText(mTitleText);setContentText(mContentText);showCancelButton(mShowCancel);setCancelText(mCancelText);setConfirmText(mConfirmText);changeAlertType(mAlertType, true);
//        getWindow().setBackgroundDrawableResource(android.R.color.transparent);}private void restore () {mCustomImage.setVisibility(View.GONE);mErrorFrame.setVisibility(View.GONE);mSuccessFrame.setVisibility(View.GONE);mWarningFrame.setVisibility(View.GONE);mConfirmButton.setBackgroundResource(R.drawable.blue_button_background);mErrorFrame.clearAnimation();mErrorX.clearAnimation();mSuccessTick.clearAnimation();mSuccessLeftMask.clearAnimation();mSuccessRightMask.clearAnimation();}private void playAnimation () {if (mAlertType == ERROR_TYPE) {mErrorFrame.startAnimation(mErrorInAnim);mErrorX.startAnimation(mErrorXInAnim);} else if (mAlertType == SUCCESS_TYPE) {mSuccessTick.startTickAnim();mSuccessRightMask.startAnimation(mSuccessBowAnim);}}private void changeAlertType(int alertType, boolean fromCreate) {mAlertType = alertType;// call after created viewsif (mDialogView != null) {if (!fromCreate) {// restore all of views state before switching alert typerestore();}switch (mAlertType) {case ERROR_TYPE:mErrorFrame.setVisibility(View.VISIBLE);break;case SUCCESS_TYPE:mSuccessFrame.setVisibility(View.VISIBLE);// initial rotate layout of success maskmSuccessLeftMask.startAnimation(mSuccessLayoutAnimSet.getAnimations().get(0));mSuccessRightMask.startAnimation(mSuccessLayoutAnimSet.getAnimations().get(1));break;case WARNING_TYPE:mConfirmButton.setBackgroundResource(R.drawable.red_button_background);mWarningFrame.setVisibility(View.VISIBLE);break;case CUSTOM_IMAGE_TYPE:setCustomImage(mCustomImgDrawable);break;}if (!fromCreate) {playAnimation();}}}public int getAlerType () {return mAlertType;}public void changeAlertType(int alertType) {changeAlertType(alertType, false);}public String getTitleText () {return mTitleText;}//解决dialog.setCanceledOnTouchOutside(true) 不生效问题public void setDialogTouchOutsideCloseable(Dialog dialog, int gravity) {Window window = dialog.getWindow();WindowManager.LayoutParams params = window.getAttributes();params.width = ViewGroup.LayoutParams.MATCH_PARENT;params.height = ViewGroup.LayoutParams.WRAP_CONTENT;window.setAttributes(params);window.setGravity(gravity);}//设置点击外部消失public SweetAlertDialog setCanceledOutside(boolean cancel) {setCanceledOnTouchOutside(cancel);return this;}//设置标题public SweetAlertDialog setTitleText (String text) {mTitleText = text;if (mTitleTextView != null && mTitleText != null) {mTitleTextView.setText(mTitleText);}return this;}//设置标题上方图标,Drawable 对象public SweetAlertDialog setCustomImage (Drawable drawable) {mCustomImgDrawable = drawable;if (mCustomImage != null && mCustomImgDrawable != null) {mCustomImage.setVisibility(View.VISIBLE);mCustomImage.setImageDrawable(mCustomImgDrawable);}return this;}//设置标题上方图标,Drawable int值public SweetAlertDialog setCustomImage (int resourceId) {return setCustomImage(getContext().getResources().getDrawable(resourceId));}public String getContentText () {return mContentText;}//设置显示内容public SweetAlertDialog setContentText (String text) {mContentText = text;if (mContentTextView != null && mContentText != null) {mContentTextView.setVisibility(View.VISIBLE);mContentTextView.setText(mContentText);}return this;}public boolean isShowCancelButton () {return mShowCancel;}//显示取消按钮public SweetAlertDialog showCancelButton (boolean isShow) {mShowCancel = isShow;if (mCancelButton != null) {mCancelButton.setVisibility(mShowCancel ? View.VISIBLE : View.GONE);}return this;}public String getCancelText () {return mCancelText;}//设置取消按钮的文字public SweetAlertDialog setCancelText (String text) {mCancelText = text;if (mCancelButton != null && mCancelText != null) {mCancelButton.setText(mCancelText);}return this;}public String getConfirmText () {return mConfirmText;}//设置确认按钮的文字public SweetAlertDialog setConfirmText (String text) {mConfirmText = text;if (mConfirmButton != null && mConfirmText != null) {mConfirmButton.setText(mConfirmText);}return this;}public SweetAlertDialog setCancelClickListener (OnSweetClickListener listener) {mCancelClickListener = listener;return this;}public SweetAlertDialog setConfirmClickListener (OnSweetClickListener listener) {mConfirmClickListener = listener;return this;}protected void onStart() {mDialogView.startAnimation(mModalInAnim);playAnimation();}public void dismiss() {mConfirmButton.startAnimation(mOverlayOutAnim);mDialogView.startAnimation(mModalOutAnim);}@Overridepublic void onClick(View v) {if (v.getId() == R.id.cancel_button) {if (mCancelClickListener != null) {mCancelClickListener.onClick(SweetAlertDialog.this);} else {dismiss();}} else if (v.getId() == R.id.confirm_button) {if (mConfirmClickListener != null) {mConfirmClickListener.onClick(SweetAlertDialog.this);} else {dismiss();}}}
}

4、优点和缺点

(1)优点
显示方便UI好看
调用api简单方便
确定和取消按钮可以选择显示一个或者两个
添加了不同类别图标,并且图标会显示动画效果
扩展了点击一次确定后,还可以进行多次弹框的情况
(2)缺点
对话框显示的大小基本固定,适合简单信息的显示
对话框无法设置点击周边就消失,因为已经全局显示了
(3)其他扩展

可以用来做自定义Toast。

具体实现思路是:自定义几秒后,执行dismiss关闭对话框。

三、其他

1、小结

网上有千千万万中自定义对话框,各种花里胡哨的也很多,但是简单使用的很少。

在很多实际项目中只要用到demo代码中的最普通的显示文本的情况就ok的,需要显示图片的场景并不多。

并且上面显示图片类型的代码,都是添加了不同动画类的,移植反而麻烦。

所以一般的使用只移植简单文本部分的功能使用就OK了。

其他的就当预留的吧,万一真的要呢。

如果对话框UI没啥要求,只要可以显示文本和确定、取消按钮这样的场景,使用原生的AlertDialog很好了。

2、Android对话框的使用总结

都是对原生对话框的详细介绍

https://blog.csdn.net/wenzhi20102321/article/details/52818351

3、Android加载中对话框示例

转圈圈的加载中对话框

https://blog.csdn.net/wenzhi20102321/article/details/79619719

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

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

相关文章

【Linux】-Elasticsearch安装部署[16]

目录 简介 安装 1、添加yum仓库 2、安装es 3、配置es 4、启动es 5、关闭防火墙 6、测试 简介 全文搜索属于最常见的要求&#xff0c;开源的Elasticsearch&#xff08;以下简称es&#xff09;是目前全文搜索引擎的首选。它可以快速的储存、搜索和分析海量数据。维基百科…

以人为本的人工智能:李飞飞谈AI

随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;关于AI的讨论越来越多&#xff0c;特别是围绕其可能带来的威胁。有人担心高效的AI会夺走我们的工作&#xff0c;甚至不可控的AI最终会统治人类。对此&#xff0c;斯坦福大学计算机科学系教授李飞飞提出了不同…

Paddle 稀疏计算 使用指南

Paddle 稀疏计算 使用指南 1. 稀疏格式介绍 1.1 稀疏格式介绍 稀疏矩阵是一种特殊的矩阵&#xff0c;其中绝大多数元素为0。与密集矩阵相比&#xff0c;稀疏矩阵可以节省大量存储空间&#xff0c;并提高计算效率。 例如&#xff0c;一个5x5的矩阵中只有3个非零元素: impor…

springboot中使用spring-cloud-starter-openfeign遇到的问题及解决参考

声明&#xff1a;本文使用的spring boot 版本是2.7.12 在springboot中使用spring-cloud-starter-openfeign遇到的一些问题&#xff1a; Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata java.…

微软文字转语音小工具(Text to speech)网页版

在线文字转语音工具&#xff1a;在线文本转语音 (text-to-speech.cn) 随着科技的迅猛发展&#xff0c;人工智能技术日益成熟&#xff0c;AI配音作为其中的一项重要应用&#xff0c;正在以惊人的速度改变着我们的生活。所谓AI配音&#xff0c;指的是利用人工智能技术模拟人类声音…

使用字节豆包大模型在 Dify 上实现最简单的 Agent 应用(四):AI 信息检索

这篇文章&#xff0c;我们继续聊聊&#xff0c;如何折腾 AI 应用&#xff0c;把不 AI 的东西&#xff0c;“AI 起来”。在不折腾复杂的系统和环境的前提下&#xff0c;快速完成轻量的 Agent 应用。 写在前面 在上一篇文章《使用 Dify、Meilisearch、零一万物模型实现最简单的…

PDF Reader Pro for Mac 直装激活版:专业PDF阅读编辑软件

在数字化时代&#xff0c;PDF文件已成为我们日常工作和学习中不可或缺的一部分。然而&#xff0c;如何高效、便捷地阅读、编辑和管理这些PDF文件&#xff0c;却一直是许多人面临的难题。现在&#xff0c;有了PDF Reader Pro for Mac&#xff0c;这些难题将迎刃而解。 PDF Reade…

GPIO模拟IIC通信测量环境光

目录 iic.h iic.c ap3216c.h ap3216.c main.c 实验效果 iic.h #ifndef __IIC_H__ #define __IIC_H__#include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" //SDA 数据线为PF15 //SCL 时钟线为PF14//配置PF15为输出模式 #define SET_SDA_OUT d…

列举几个淘宝商品详情API接口测试示例

API名&#xff1a;item_get 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_search_shop等]cacheString否[yes…

网络模型-Qinq配置与应用

Qinq配置与应用 通过配置Qinq来实现利用公网提供的VLAN100使企业1互通&#xff0c;利用公网提供的VLAN200使企业2互通不同企业之间互相隔离。并通过在连接其它厂商设备的接口上配置修改0in0外层VLAN Tag的TPID值&#xff0c;来实现与其它厂商设备的互通。 一、创建VLAN #在Swi…

等风来不如追风去 火星皮卡与罗乐的逐梦之旅

都说&#xff0c;男人至死皆少年。少年有梦&#xff0c;不应止于心动。于是&#xff0c;家在毕节的罗乐在节前果断为自己购入了一辆全尺寸火星皮卡当作自己的新年礼物。从此火星皮卡便与罗乐相伴义无反顾地踏上这场热辣滚烫的逐梦之旅 “全尺寸火星满足了我对Dream Car的所有幻…

DVWA代码审计--文件上传

NO.1 Low 首先来看下代码 <?php if( isset( $_POST[ Upload ] ) ) { // Where are we going to be writing to? $target_path DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; $target_path . basename( $_FILES[ uploaded ][ name ] ); // Can we move the f…

netcat一键开始瑞士军刀模式(KALI工具系列五)

目录 1、KALI LINUX简介 2、netcat工具简介 3、在KALI中使用netcat 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、命令示例 4.1 测试某IP的端口是否打开 4.2 TCP扫描 4.3 UDP扫描 4.4 端口刺探 4.5 直接扫描 5、即时通信 5.1 单击对话互联 5.2 传…

知识表示概述

文章目录 知识表示研究现状技术发展趋势 知识表示 知识是人类在认识和改造客观世界的过程中总结出的客观事实、概念、定理和公理的集合。知识具有不同的分类方式&#xff0c;例如按照知识的作用范围可分为常识性知识与领域性知识。知识表示是将现实世界中存在的知识转换成计算机…

巨某量引擎后台登录实战笔记 | Playwright自动化框架

前言 本文章中所有内容仅供学习交流&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; 入正题看看滑块是怎么个事…

网络的基础理解

文章目录 网络的基础认识 网络协议协议分层OSI七层模型TCP/IP 五层/四层 模型 网络的基础认识 先来看下面几个问题 什么是网络&#xff1f; 网络就是有许多台设备包括计算机单不仅限于计算机&#xff0c;这些设备通过相互通信所组成起来系统&#xff0c;我们称之为网络所以如…

Gartner发布中国数据安全安全与风险管理领导者指南:将孤立的数据安全产品集成到数据安全平台中,实施一致的数据安全策略

在中国开展业务或与中国相关的组织面临着越来越多的数据安全风险和法规。安全和风险管理领导者必须采用风险优先的数据安全计划和投资&#xff0c;以响应监管要求&#xff0c;以增强数据驱动的数字创新能力。 主要发现 跨组织职能的分散的数据安全举措和不协调的利益相关者责任…

服务器c盘爆满了,这几种方法可以帮助C盘“瘦身”

我们在使用服务器的时候基本不会在C盘安装软件&#xff0c;那么用久了发现C盘满了&#xff0c;提示空间不足&#xff1f;那么这是怎么回事&#xff0c;为什么空间会占用这么快呢&#xff1f; 原因一&#xff1a; C盘满了&#xff0c;很可能是因为电脑里的垃圾文件过多。操作系…

薪资不公、晋升无望?动笔写一份申诉材料吧!

薪资不公、晋升无望&#xff1f;动笔写一份申诉材料吧&#xff01; 引言&#xff1a;每个努力工作的人都值得公平对待 在职场上&#xff0c;我们付出了汗水和智慧&#xff0c;期待着相应的回报——合理的工资和公正的晋升机会。然而&#xff0c;现实并不总是如此美好。当你感觉…

芯片设计公司外协ERP数字化运营:科技与管理的融合

随着信息技术的快速发展&#xff0c;ERP(企业资源计划)系统已经成为现代企业管理不可或缺的一部分。在芯片设计行业&#xff0c;由于产品的复杂性、技术的高要求以及市场的快速变化&#xff0c;外协ERP数字化运营显得尤为重要。 芯片设计公司的外协ERP数字化运营&#xff0c;主…