Android使用屬性動(dòng)畫如何自定義倒計(jì)時(shí)控件詳解
為什么要引入屬性動(dòng)畫?
Android之前的補(bǔ)間動(dòng)畫機(jī)制其實(shí)還算是比較健全的,在android.view.animation包下面有好多的類可以供我們操作,來(lái)完成一系列的動(dòng)畫效果,比如說(shuō)對(duì)View進(jìn)行移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出,并且我們還可以借助AnimationSet來(lái)將這些動(dòng)畫效果組合起來(lái)使用,除此之外還可以通過(guò)配置Interpolator來(lái)控制動(dòng)畫的播放速度等等等等。那么這里大家可能要產(chǎn)生疑問(wèn)了,既然之前的動(dòng)畫機(jī)制已經(jīng)這么健全了,為什么還要引入屬性動(dòng)畫呢?
其實(shí)上面所謂的健全都是相對(duì)的,如果你的需求中只需要對(duì)View進(jìn)行移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出操作,那么補(bǔ)間動(dòng)畫確實(shí)已經(jīng)足夠健全了。但是很顯然,這些功能是不足以覆蓋所有的場(chǎng)景的,一旦我們的需求超出了移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出這四種對(duì)View的操作,那么補(bǔ)間動(dòng)畫就不能再幫我們忙了,也就是說(shuō)它在功能和可擴(kuò)展方面都有相當(dāng)大的局限性,那么下面我們就來(lái)看看補(bǔ)間動(dòng)畫所不能勝任的場(chǎng)景。
注意上面我在介紹補(bǔ)間動(dòng)畫的時(shí)候都有使用“對(duì)View進(jìn)行操作”這樣的描述,沒(méi)錯(cuò),補(bǔ)間動(dòng)畫是只能夠作用在View上的。也就是說(shuō),我們可以對(duì)一個(gè)Button、TextView、甚至是LinearLayout、或者其它任何繼承自View的組件進(jìn)行動(dòng)畫操作,但是如果我們想要對(duì)一個(gè)非View的對(duì)象進(jìn)行動(dòng)畫操作,抱歉,補(bǔ)間動(dòng)畫就幫不上忙了。可能有的朋友會(huì)感到不能理解,我怎么會(huì)需要對(duì)一個(gè)非View的對(duì)象進(jìn)行動(dòng)畫操作呢?這里我舉一個(gè)簡(jiǎn)單的例子,比如說(shuō)我們有一個(gè)自定義的View,在這個(gè)View當(dāng)中有一個(gè)Point對(duì)象用于管理坐標(biāo),然后在onDraw()方法當(dāng)中就是根據(jù)這個(gè)Point對(duì)象的坐標(biāo)值來(lái)進(jìn)行繪制的。也就是說(shuō),如果我們可以對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作,那么整個(gè)自定義View的動(dòng)畫效果就有了。顯然,補(bǔ)間動(dòng)畫是不具備這個(gè)功能的,這是它的第一個(gè)缺陷。
然后補(bǔ)間動(dòng)畫還有一個(gè)缺陷,就是它只能夠?qū)崿F(xiàn)移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出這四種動(dòng)畫操作,那如果我們希望可以對(duì)View的背景色進(jìn)行動(dòng)態(tài)地改變呢?很遺憾,我們只能靠自己去實(shí)現(xiàn)了。說(shuō)白了,之前的補(bǔ)間動(dòng)畫機(jī)制就是使用硬編碼的方式來(lái)完成的,功能限定死就是這些,基本上沒(méi)有任何擴(kuò)展性可言。
最后,補(bǔ)間動(dòng)畫還有一個(gè)致命的缺陷,就是它只是改變了View的顯示效果而已,而不會(huì)真正去改變View的屬性。什么意思呢?比如說(shuō),現(xiàn)在屏幕的左上角有一個(gè)按鈕,然后我們通過(guò)補(bǔ)間動(dòng)畫將它移動(dòng)到了屏幕的右下角,現(xiàn)在你可以去嘗試點(diǎn)擊一下這個(gè)按鈕,點(diǎn)擊事件是絕對(duì)不會(huì)觸發(fā)的,因?yàn)閷?shí)際上這個(gè)按鈕還是停留在屏幕的左上角,只不過(guò)補(bǔ)間動(dòng)畫將這個(gè)按鈕繪制到了屏幕的右下角而已。
也正是因?yàn)檫@些原因,Android開(kāi)發(fā)團(tuán)隊(duì)決定在3.0版本當(dāng)中引入屬性動(dòng)畫這個(gè)功能,那么屬性動(dòng)畫是不是就把上述的問(wèn)題全部解決掉了?下面我們就來(lái)一起看一看。
新引入的屬性動(dòng)畫機(jī)制已經(jīng)不再是針對(duì)于View來(lái)設(shè)計(jì)的了,也不限定于只能實(shí)現(xiàn)移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出這幾種動(dòng)畫操作,同時(shí)也不再只是一種視覺(jué)上的動(dòng)畫效果了。它實(shí)際上是一種不斷地對(duì)值進(jìn)行操作的機(jī)制,并將值賦值到指定對(duì)象的指定屬性上,可以是任意對(duì)象的任意屬性。所以我們?nèi)匀豢梢詫⒁粋€(gè)View進(jìn)行移動(dòng)或者縮放,但同時(shí)也可以對(duì)自定義View中的Point對(duì)象進(jìn)行動(dòng)畫操作了。我們只需要告訴系統(tǒng)動(dòng)畫的運(yùn)行時(shí)長(zhǎng),需要執(zhí)行哪種類型的動(dòng)畫,以及動(dòng)畫的初始值和結(jié)束值,剩下的工作就可以全部交給系統(tǒng)去完成了。
既然屬性動(dòng)畫的實(shí)現(xiàn)機(jī)制是通過(guò)對(duì)目標(biāo)對(duì)象進(jìn)行賦值并修改其屬性來(lái)實(shí)現(xiàn)的,那么之前所說(shuō)的按鈕顯示的問(wèn)題也就不復(fù)存在了,如果我們通過(guò)屬性動(dòng)畫來(lái)移動(dòng)一個(gè)按鈕,那么這個(gè)按鈕就是真正的移動(dòng)了,而不再是僅僅在另外一個(gè)位置繪制了而已。
好了,介紹了這么多,相信大家已經(jīng)對(duì)屬性動(dòng)畫有了一個(gè)最基本的認(rèn)識(shí)了,下面來(lái)一看看詳細(xì)的介紹吧
引言
本文介紹一下利用屬性動(dòng)畫(未使用Timer,通過(guò)動(dòng)畫執(zhí)行次數(shù)控制倒計(jì)時(shí))自定義一個(gè)圓形倒計(jì)時(shí)控件,比較簡(jiǎn)陋,僅做示例使用,如有需要,您可自行修改以滿足您的需求??丶兴褂玫乃夭募芭渖枪P者隨意選擇,導(dǎo)致效果不佳,先上示例圖片

示例中進(jìn)度條底色、漸變色(僅支持兩個(gè)色值)、字體大小、圖片、進(jìn)度條寬度及是否顯示進(jìn)度條等可通過(guò)xml修改,倒計(jì)時(shí)時(shí)間可通過(guò)代碼設(shè)置。如果您感興趣,可修改代碼設(shè)置更豐富的漸變色值及文字變化效果,本文僅僅提供設(shè)計(jì)思路。
筆者利用屬性動(dòng)畫多次執(zhí)行實(shí)現(xiàn)倒計(jì)時(shí),執(zhí)行次數(shù)即為倒計(jì)時(shí)初始數(shù)值。對(duì)上述示例做一下拆解,會(huì)發(fā)現(xiàn)實(shí)現(xiàn)起來(lái)還是很容易的,需要處理的主要是以下幾部分
1.繪制外部環(huán)形進(jìn)度條
2.繪制中央旋轉(zhuǎn)圖片
3.繪制倒計(jì)時(shí)時(shí)間
一.繪制外部環(huán)形進(jìn)度條,分為兩部分:
1.環(huán)形背景 canvas.drawCircle方法繪制
2.扇形進(jìn)度 canvas.drawArc方法繪制,弧度通過(guò)整體倒計(jì)時(shí)執(zhí)行進(jìn)度控制
二.繪制中央旋轉(zhuǎn)圖片:
前置描述:外層圓形直徑設(shè)為d1;中央旋轉(zhuǎn)圖片直徑設(shè)為d2;進(jìn)度條寬度設(shè)為d3
1.將設(shè)置的圖片進(jìn)行剪切縮放處理(也可不剪切,筆者有強(qiáng)迫癥),使其寬高等于d1 - 2 * d3,即d2 = d1 - 2 * d3;
2.利用Matrix將Bitmap平移至中央;
3.利用Matrix旋轉(zhuǎn)Bitmap
三.繪制倒計(jì)時(shí)時(shí)間:
通過(guò)每次動(dòng)畫執(zhí)行進(jìn)度,控制文本位置
下面上示例代碼:
public class CircleCountDownView extends View {
private CountDownListener countDownListener;
private int width;
private int height;
private int padding;
private int borderWidth;
// 根據(jù)動(dòng)畫執(zhí)行進(jìn)度計(jì)算出來(lái)的插值,用來(lái)控制動(dòng)畫效果,建議取值范圍為0到1
private float currentAnimationInterpolation;
private boolean showProgress;
private float totalTimeProgress;
private int processColorStart;
private int processColorEnd;
private int processBlurMaskRadius;
private int initialCountDownValue;
private int currentCountDownValue;
private Paint circleBorderPaint;
private Paint circleProcessPaint;
private RectF circleProgressRectF;
private Paint circleImgPaint;
private Matrix circleImgMatrix;
private Bitmap circleImgBitmap;
private int circleImgRadius;
private AnimationInterpolator animationInterpolator;
private BitmapShader circleImgBitmapShader;
private float circleImgTranslationX;
private float circleImgTranslationY;
private Paint valueTextPaint;
private ValueAnimator countDownAnimator;
public CircleCountDownView(Context context) {
this(context, null);
}
public CircleCountDownView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleCountDownView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
init(attrs);
}
private void init(AttributeSet attrs) {
circleImgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circleImgPaint.setStyle(Paint.Style.FILL);
circleImgMatrix = new Matrix();
valueTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleCountDownView);
// 控制外層進(jìn)度條的邊距
padding = typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_padding, DisplayUtil.dp2px(5));
// 進(jìn)度條邊線寬度
borderWidth = typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_circleBorderWidth, 0);
if (borderWidth > 0) {
circleBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circleBorderPaint.setStyle(Paint.Style.STROKE);
circleBorderPaint.setStrokeWidth(borderWidth);
circleBorderPaint.setColor(typedArray.getColor(R.styleable.CircleCountDownView_circleBorderColor, Color.WHITE));
showProgress = typedArray.getBoolean(R.styleable.CircleCountDownView_showProgress, false);
if (showProgress) {
circleProcessPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circleProcessPaint.setStyle(Paint.Style.STROKE);
circleProcessPaint.setStrokeWidth(borderWidth);
// 進(jìn)度條漸變色值
processColorStart = typedArray.getColor(R.styleable.CircleCountDownView_processColorStart, Color.parseColor("#00ffff"));
processColorEnd = typedArray.getColor(R.styleable.CircleCountDownView_processColorEnd, Color.parseColor("#35adc6"));
// 進(jìn)度條高斯模糊半徑
processBlurMaskRadius = typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_processBlurMaskRadius, DisplayUtil.dp2px(5));
}
}
int circleImgSrc = typedArray.getResourceId(R.styleable.CircleCountDownView_circleImgSrc, R.mipmap.ic_radar);
// 圖片剪裁成正方形
circleImgBitmap = ImageUtil.cropSquareBitmap(BitmapFactory.decodeResource(getResources(), circleImgSrc));
valueTextPaint.setColor(typedArray.getColor(R.styleable.CircleCountDownView_valueTextColor, Color.WHITE));
valueTextPaint.setTextSize(typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_valueTextSize, DisplayUtil.dp2px(13)));
typedArray.recycle();
// 初始化屬性動(dòng)畫,周期為1秒
countDownAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);
countDownAnimator.setInterpolator(new LinearInterpolator());
countDownAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (countDownListener != null) {
// 監(jiān)聽(tīng)剩余時(shí)間
long restTime = (long) ((currentCountDownValue - animation.getAnimatedFraction()) * 1000);
countDownListener.restTime(restTime);
}
// 整體倒計(jì)時(shí)進(jìn)度
totalTimeProgress = (initialCountDownValue - currentCountDownValue + animation.getAnimatedFraction()) / initialCountDownValue;
if (animationInterpolator != null) {
currentAnimationInterpolation = animationInterpolator.getInterpolation(animation.getAnimatedFraction());
} else {
currentAnimationInterpolation = animation.getAnimatedFraction();
currentAnimationInterpolation *= currentAnimationInterpolation;
}
invalidate();
}
});
countDownAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
currentCountDownValue--;
}
@Override
public void onAnimationEnd(Animator animation) {
if (countDownListener != null) {
countDownListener.onCountDownFinish();
}
}
});
}
// 設(shè)置倒計(jì)時(shí)初始時(shí)間
public void setStartCountValue(int initialCountDownValue) {
this.initialCountDownValue = initialCountDownValue;
this.currentCountDownValue = initialCountDownValue;
// 設(shè)置重復(fù)執(zhí)行次數(shù),共執(zhí)行initialCountDownValue次,恰好為倒計(jì)時(shí)總數(shù)
countDownAnimator.setRepeatCount(currentCountDownValue - 1);
invalidate();
}
public void setAnimationInterpolator(AnimationInterpolator animationInterpolator) {
if (!countDownAnimator.isRunning()) {
this.animationInterpolator = animationInterpolator;
}
}
// 重置
public void reset() {
countDownAnimator.cancel();
lastAnimationInterpolation = 0;
totalTimeProgress = 0;
currentAnimationInterpolation = 0;
currentCountDownValue = initialCountDownValue;
circleImgMatrix.setTranslate(circleImgTranslationX, circleImgTranslationY);
circleImgMatrix.postRotate(0, width / 2, height / 2);
invalidate();
}
public void restart() {
reset();
startCountDown();
}
public void pause() {
countDownAnimator.pause();
}
public void setCountDownListener(CountDownListener countDownListener) {
this.countDownListener = countDownListener;
}
// 啟動(dòng)倒計(jì)時(shí)
public void startCountDown() {
if (countDownAnimator.isPaused()) {
countDownAnimator.resume();
return;
}
if (currentCountDownValue > 0) {
countDownAnimator.start();
} else if (countDownListener != null) {
countDownListener.onCountDownFinish();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getMeasuredWidth();
height = getMeasuredHeight();
if (width > 0 && height > 0) {
doCalculate();
}
}
private void doCalculate() {
circleImgMatrix.reset();
// 圓形圖片繪制區(qū)域半徑
circleImgRadius = (Math.min(width, height) - 2 * borderWidth - 2 * padding) / 2;
float actualCircleImgBitmapWH = circleImgBitmap.getWidth();
float circleDrawingScale = circleImgRadius * 2 / actualCircleImgBitmapWH;
// bitmap縮放處理
Matrix matrix = new Matrix();
matrix.setScale(circleDrawingScale, circleDrawingScale, actualCircleImgBitmapWH / 2, actualCircleImgBitmapWH / 2);
circleImgBitmap = Bitmap.createBitmap(circleImgBitmap, 0, 0, circleImgBitmap.getWidth(), circleImgBitmap.getHeight(), matrix, true);
// 繪制圓形圖片使用
circleImgBitmapShader = new BitmapShader(circleImgBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
// 平移至中心
circleImgTranslationX = (width - circleImgRadius * 2) / 2;
circleImgTranslationY = (height - circleImgRadius * 2) / 2;
circleImgMatrix.setTranslate(circleImgTranslationX, circleImgTranslationY);
if (borderWidth > 0) {
// 外層進(jìn)度條寬度(注意:需要減掉畫筆寬度)
float circleProgressWH = Math.min(width, height) - borderWidth - 2 * padding;
float left = (width > height ? (width - height) / 2 : 0) + borderWidth / 2 + padding;
float top = (height > width ? (height - width) / 2 : 0) + borderWidth / 2 + padding;
float right = left + circleProgressWH;
float bottom = top + circleProgressWH;
circleProgressRectF = new RectF(left, top, right, bottom);
if (showProgress) {
// 進(jìn)度條漸變及邊緣高斯模糊處理
circleProcessPaint.setShader(new LinearGradient(left, top, left + circleImgRadius * 2, top + circleImgRadius * 2, processColorStart, processColorEnd, Shader.TileMode.MIRROR));
circleProcessPaint.setMaskFilter(new BlurMaskFilter(processBlurMaskRadius, BlurMaskFilter.Blur.SOLID)); // 設(shè)置進(jìn)度條陰影效果
}
}
}
private float lastAnimationInterpolation;
@Override
protected void onDraw(Canvas canvas) {
if (width == 0 || height == 0) {
return;
}
int centerX = width / 2;
int centerY = height / 2;
if (borderWidth > 0) {
// 繪制外層圓環(huán)
canvas.drawCircle(centerX, centerY, Math.min(width, height) / 2 - borderWidth / 2 - padding, circleBorderPaint);
if (showProgress) {
// 繪制整體進(jìn)度
canvas.drawArc(circleProgressRectF, 0, 360 * totalTimeProgress, false, circleProcessPaint);
}
}
// 設(shè)置圖片旋轉(zhuǎn)角度增量
circleImgMatrix.postRotate((currentAnimationInterpolation - lastAnimationInterpolation) * 360, centerX, centerY);
circleImgBitmapShader.setLocalMatrix(circleImgMatrix);
circleImgPaint.setShader(circleImgBitmapShader);
canvas.drawCircle(centerX, centerY, circleImgRadius, circleImgPaint);
lastAnimationInterpolation = currentAnimationInterpolation;
// 繪制倒計(jì)時(shí)時(shí)間
// current
String currentTimePoint = currentCountDownValue + "s";
float textWidth = valueTextPaint.measureText(currentTimePoint);
float x = centerX - textWidth / 2;
Paint.FontMetrics fontMetrics = valueTextPaint.getFontMetrics();
// 文字繪制基準(zhǔn)線(圓形區(qū)域正中央)
float verticalBaseline = (height - fontMetrics.bottom - fontMetrics.top) / 2;
// 隨動(dòng)畫執(zhí)行進(jìn)度而更新的y軸位置
float y = verticalBaseline - currentAnimationInterpolation * (Math.min(width, height) / 2);
valueTextPaint.setAlpha((int) (255 - currentAnimationInterpolation * 255));
canvas.drawText(currentTimePoint, x, y, valueTextPaint);
// next
String nextTimePoint = (currentCountDownValue - 1) + "s";
textWidth = valueTextPaint.measureText(nextTimePoint);
x = centerX - textWidth / 2;
y = y + (Math.min(width, height)) / 2;
valueTextPaint.setAlpha((int) (currentAnimationInterpolation * 255));
canvas.drawText(nextTimePoint, x, y, valueTextPaint);
}
public interface CountDownListener {
/**
* 倒計(jì)時(shí)結(jié)束
*/
void onCountDownFinish();
/**
* 倒計(jì)時(shí)剩余時(shí)間
*
* @param restTime 剩余時(shí)間,單位毫秒
*/
void restTime(long restTime);
}
public interface AnimationInterpolator {
/**
* @param inputFraction 動(dòng)畫執(zhí)行時(shí)間因子,取值范圍0到1
*/
float getInterpolation(float inputFraction);
}
}
自定義屬性如下
<declare-styleable name="CircleCountDownView"> <!--控件中間圖片資源--> <attr name="circleImgSrc" format="reference" /> <attr name="circleBorderColor" format="color" /> <attr name="circleBorderWidth" format="dimension" /> <attr name="valueTextSize" format="dimension" /> <attr name="valueTextColor" format="color" /> <attr name="padding" format="dimension" /> <attr name="showProgress" format="boolean" /> <attr name="processColorStart" format="color" /> <attr name="processColorEnd" format="color" /> <attr name="processBlurMaskRadius" format="dimension" /> </declare-styleable>
代碼比較簡(jiǎn)單,如有疑問(wèn)歡迎留言
完整代碼:https://github.com/670832188/TestApp (本地下載)

總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android判斷當(dāng)前棧頂Activity的包名代碼示例
這篇文章主要介紹了Android判斷當(dāng)前棧頂Activity的包名代碼示例,分享了相關(guān)代碼,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02
Android Material Design 陰影實(shí)現(xiàn)示例
這篇文章主要介紹了Android Material Design 陰影實(shí)現(xiàn)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Kotlin協(xié)程launch啟動(dòng)流程原理詳解
這篇文章主要為大家介紹了Kotlin協(xié)程launch啟動(dòng)流程原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Android Bitmap的加載優(yōu)化與Cache相關(guān)介紹
這篇文章主要介紹了Android中性能優(yōu)化之Bitmap的加載優(yōu)化與Cache相關(guān)內(nèi)容介紹,文中介紹的很詳細(xì),對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-02-02
配置一個(gè)好用的Android模擬器讓你不再對(duì)模擬器那么失望
默認(rèn)情況下的Android模擬器窗口占據(jù)了屏幕巨大的空間,而且毫無(wú)緣由的放著一個(gè)屏幕鍵盤,如果你沒(méi)親自用過(guò)模擬器的話,還有一個(gè)不易發(fā)現(xiàn)的問(wèn)題:幾乎是慢到不能用2013-01-01
Android中使用ViewStub實(shí)現(xiàn)布局優(yōu)化
ViewStub是Android布局優(yōu)化中一個(gè)很不錯(cuò)的標(biāo)簽/控件,直接繼承自View。雖然Android開(kāi)發(fā)人員基本上都聽(tīng)說(shuō)過(guò),但是真正用的可能不多。今天我們就來(lái)詳細(xì)探討下ViewStub的使用2016-09-09
Android開(kāi)發(fā)中的錯(cuò)誤及解決辦法總結(jié)
本文屬于個(gè)人平時(shí)項(xiàng)目開(kāi)發(fā)過(guò)程遇到的一些問(wèn)題,記錄下來(lái)并總結(jié)解決方案,希望能幫到大家解決問(wèn)題,需要的朋友可以參考下2022-02-02

