深入瞭解一些Android動畫

来源:https://www.cnblogs.com/lanjiabin/archive/2020/05/07/12844172.html
-Advertisement-
Play Games

一、PropertyValuesHolder 閱讀本文需要上一文Android屬性動畫的基礎,這樣才可以明白接下來要講什麼。 1.理解和使用 是ObjectAnimation類似的一個方法,只是少了一個target,就是要執行的控制項。看看正常的使用方法:會同時執行全部的Holder 2.方法和參數 ...


一、PropertyValuesHolder

閱讀本文需要上一文Android屬性動畫的基礎,這樣才可以明白接下來要講什麼。

1.理解和使用

PropertyValuesHolder 是ObjectAnimation類似的一個方法,只是少了一個target,就是要執行的控制項。看看正常的使用方法:會同時執行全部的Holder

public void  doPropertyValuesHolder(){
        //定義一個旋轉Holder
        PropertyValuesHolder rotationHolder=
                PropertyValuesHolder.ofFloat(
                        "rotation",
                        60f,40f,100f,-60f,40f,88f,77f);

        //定義一個透明Holder
        PropertyValuesHolder alphaHolder=
                PropertyValuesHolder.ofFloat(
                        "alpha",
                        0.01f,0.5f,1.0f,0.8f,0.2f,0.0f);
        
    	//載入進ObjectAnimator
        ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,rotationHolder,alphaHolder);
        objectAnimator.setDuration(3000);
        objectAnimator.start();
    }

2.方法和參數

可以看看這個方法的參數:

ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)
PropertyValuesHolder ofFloat(String propertyName, float... values)

Object target 是要顯示動畫的控制項

PropertyValuesHolder... values 裝載多個PropertyValuesHolder

String propertyName 代表要反射的參數,跟ObjectAnimation的參數是一樣的

float... values 代表是可變長參數
這樣的方法還有以下圖片這些:

圖片.png

其中ofObject()方法 ,也是跟ObjectAnimation的相似,也是要自定義TypeEvaluator。
圖片.png

二、Keyframe

1.理解和使用

看名字,就是理解為關鍵幀的意思,在動畫中,在某幀做一些操作,從而實現對比效果比較明顯的效果。
關鍵幀表示是某個物體在哪個時間點應該在哪個位置上。
具體使用:

 public void  doPropertyValuesHolderKeyFrame(){

        //頭keyframe1,從進度0.6開始,在進度60%的時候,數值是0.1f
        Keyframe keyframe1=Keyframe.ofFloat(0.6f,0.1f);

        //中間keyframe2
        Keyframe keyframe2=Keyframe.ofFloat(0.1f,0.8f);

        //尾部keyframe3,以50%進度作為結束,這時候的數值為0.2f
        Keyframe keyframe3=Keyframe.ofFloat(0.5f,0.2f);

        //裝載到Holder中,並設置要反射的方法,這是反射的是setAlpha()方法,控制透明度
        PropertyValuesHolder alphaHolder=PropertyValuesHolder.ofKeyframe("alpha",keyframe1,keyframe2,keyframe3);

        //把裝載到Holder中裝載到ObjectAnimator或者ValueAnimation
        ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,alphaHolder);
        objectAnimator.setDuration(3000);
        objectAnimator.start();
    }

2.方法和參數

Keyframe ofFloat(float fraction, float value)

float fraction 表示進度

float value  表示在這個進度下的數值

 PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)

String propertyName 要反射的set方法

Keyframe... values 傳入Keyframe

Keyframe的方法,也是和其他的類似的。
圖片.png

Keyframe的set方法,設置進度,插值器,數值。
沒有設置插值器的時候,預設是線性插值器

 keyframe1.setInterpolator(new LinearInterpolator()); //預設線性插值器

圖片.png

3.幀的操作

直接寫結論:

  • 如果去掉0幀,則以第一個關鍵幀為起始位置
  • 如果去掉結束幀(進度為1),則以最後一個關鍵幀為結束位置
  • 使用keyframe來構建動畫,至少需要2幀

三、ViewPropertyAnimator

1.理解和使用

可以通過串列的形式,快速定義動畫,省去一些定義,在每次界面繪製的時候,啟動動畫,比其他的更節省消耗。
比如:

 ballImageView.animate().alpha(0.5f).rotation(360f).scaleX(1.5f).translationX(100f);

2.參數和方法

可以看到這些方法的返回值,基本都是ViewPropertyAnimator
圖片.png
圖片.png
再引用一張表格:

函數 含義
alpha(float value) 設置透明度
scaleY(float value) 設置 Y軸方向的縮放大小
scaleX(float value) 設置X軸方向的縮放大小
translationY(float value) 設置Y軸方向的移動值
translationX(float value) 設置X軸方向的移動值
rotation(float value) 設置繞Z軸旋轉度數
rotationX(float value) 設置繞x軸旋轉度數
rotationY(float value) 設置繞 Y 軸旋轉度數
x(float value) 相對於父容器的左上角坐標在 X軸方向的最終位置
y(float value) 相對於父容器的左上角坐標在Y軸方向的最終位置
alphaBy(float value) 設置透明度增量
rotationBy(float value) 設置繞Z軸旋轉增量
rotationXBy(float value) 設置繞 X 油旋轉增量
rotationYBy(float value) 設置統Y軸旋轉增量
translationXBy(float value) 設置X軸方向的移動值增量
translationYBy(float value) 設置Y軸方向的移動值增量
scaleXBy(float value) 設置X軸方向的縮放大小增量
scaleYBy(float value) 設置 Y軸方向的縮放大小增量
xBy(float value) 相對於父容器的左上角坐標在 X軸方向的位置增量
yBy(float value) 相對於父容器的左上角坐標在 Y軸方向的位置增量
setlnterpolator(Timelnterpolator interpolator) 設置插值器
setStartDelay(long startDelay) 設置開始延時
setDuration(long duration) 設置動畫時長

四、animateLayoutChanges

 android:animateLayoutChanges="true"
在Layout加入控制項,或者移除控制項的時候,添加動畫,但是只能使用預設動畫。

 <LinearLayout
            android:animateLayoutChanges="true"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"/>

五、LayoutTransition

LayoutTransition可以控制ViewGroup的動畫,可以使用自定義的動畫。
具體使用:

 public void doLayoutTransition(){

        LinearLayout linearLayout=new LinearLayout(this);

        //1.創建實例
        LayoutTransition transition=new LayoutTransition();

        //2.創建動畫
        ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(null,"rotation",0f,90f,0f);

        //3.動畫出現形式進行設置
        transition.setAnimator(LayoutTransition.DISAPPEARING,objectAnimator);

        //4.將LayoutTransition設置到ViewGroup中
        linearLayout.setLayoutTransition(transition);
     
      	//5.開源動畫庫 NineOldAndroids
     
    }

setAnimator(int transitionType, Animator animator)

這個方法中,transitionType有五個選項

image.png

CHANGE_APPEARING  由於容器中要顯示一個新的元素,其他需要變化的元素所應用的動畫(問題多,不常用)

_CHANGE_DISAPPEARING_  當個容器中某個元素要消失時,其他需要變化的元素所應用的動畫(問題多,不常用)

_CHANGING_  容器中正在更改的元素的動畫變化

_APPEARING_  元素在容器中出現時所定義的動畫

_DISAPPEARING_  元素在容器中消失時所定義的動畫

六、PathMeasure

PathMeasure類似一個計算器,可以計算出目標path的坐標,長度等

1.初始化

  public void doPathMeasure(){
        Path path=new Path();

        //初始化方法1
        PathMeasure pathMeasure1=new PathMeasure();
        pathMeasure1.setPath(path,true);

        //初始化方法2
        PathMeasure pathMeasure2=new PathMeasure(path,false);
    }

setPath(Path path, boolean forceClosed)
path    就是代表要計算的目標Path。
forceClosed    是否閉合,true會計算閉合狀態下的Path,false會按照Path原來情況來計算。

2.函數調用

自定義一個view

public class PathView extends View {
    Path mPath;
    Paint mPaint;
    PathMeasure mPathMeasure;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPath=new Path();
        mPaint=new Paint();
        mPathMeasure=new PathMeasure();
    }

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

        canvas.translate(250,250); //畫布移動
        mPaint.setColor(Color.BLUE); //畫筆顏色
        mPaint.setStrokeWidth(5); //畫筆粗細
        mPaint.setStyle(Paint.Style.STROKE); //畫筆風格

        mPath.moveTo(0,0);
        mPath.lineTo(0,100);
        mPath.lineTo(100,100);
        mPath.lineTo(100,0);

        mPathMeasure.setPath(mPath,true);
        Log.v("showLog",
                "getLength()=="+mPathMeasure.getLength()
                        +"  isClosed()=="+ mPathMeasure.isClosed()); //結果400.0  true

        mPathMeasure.setPath(mPath,false);
        Log.v("showLog",
                "getLength()=="+mPathMeasure.getLength()
                        +"  isClosed()=="+ mPathMeasure.isClosed()); //結果300.0  false

        canvas.drawPath(mPath,mPaint); //繪製路徑
    }
}

繪製效果:

image.png

2.1 PathMeasure.getLength()

PathMeasure.getLength() 函數用於測量路徑的長度

2.2 PathMeasure.isClosed()

PathMeasure.isClosed() 函數用於返回是否測量閉合狀態

2.3 PathMeasure.nextContour()

   		mPath.addRect(-50, -50, 50, 50, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPath.addRect(-100, -100, 100, 100, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPath.addRect(-120, -120, 120, 120, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPathMeasure.setPath(mPath, false);

        do {
            float len = mPathMeasure.getLength();
            Log.v("showLog", "len=" + len);
        } while (mPathMeasure.nextContour());

效果:

image.png

列印結果:

len=400.0
len=800.0
len=960.0

PathMeasure.nextContour()得到的順序與添加的Path的順序相同

PathMeasure.getLength()只是得到當前path的長度,不是全部的長度

2.3 getSegment()

使用getSegment函數需要禁用硬體加速 在構造方法中加入
setLayerType(LAYER_TYPE_SOFTWARE,null);

 		mPath.addRect(-50, -50, 50, 50, Path.Direction.CW);
        mPathMeasure.setPath(mPath,false); //計算的path
        mPathMeasure.getSegment(0,150,mDstPath,true); //截取並添加到mDstPath,是添加,不是其他
        canvas.drawPath(mPath, mPaint); //繪製原來的path

        canvas.translate(200,0); //畫布移動
        mPaint.setColor(Color.RED);
        canvas.drawPath(mDstPath, mPaint); //繪製添加後的mDstPath
 boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)

startD path開始截取的點,截取的起始點,是以左上角的點開始的

stopD 截取停止的點

dst 截取後添加到的path

startWithMoveTo  是否保存原狀,true保存原樣,false則會連接初始點和終點,和原來的不一定相同形狀
以上代碼的效果: 截圖的方向,與原來的path的生成方向有關

image.png

2.4 動態畫圓的例子

代碼:


public class PathView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();

        mPaint.setColor(Color.BLUE); //畫筆顏色
        mPaint.setStrokeWidth(5); //畫筆粗細
        mPaint.setStyle(Paint.Style.STROKE); //畫筆風格

        mPath.addCircle(100, 100, 50, Path.Direction.CW); //一個完整的圓
        mPathMeasure.setPath(mPath, true); //要計算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //進度 0~1
        animator.setRepeatCount(ValueAnimator.INFINITE); //無限迴圈
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //得到當前的進度
                invalidate();//重繪,重新執行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(100, 100); //畫布移動

        float stop=mPathMeasure.getLength()*mCurAnimValue; //一個進度確定一個截取點
        mDstPath.reset();
        mPathMeasure.getSegment(0,stop,mDstPath,true); //一點點添加

        canvas.drawPath(mDstPath,mPaint); //每次有進度更新,就繪製一小段截取
    }
}

效果:
動態畫圓.gif

2.5 getPosTan()

先看看函數的定義:

boolean getPosTan(float distance, float pos[], float tan[]) 

float distance 距離path的其實長度

float pos[]  該點的坐標值。x和y pos[0]=x,pos[1]=y

float tan[] 該點的正切值。x和y pos[0]=x,pos[1]=y  tan<a=y/x

2.6 箭頭畫圓的例子

代碼:


public class PathView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;
    Bitmap mArrowBmp;
    float[] mPos;
    float[] mTan;
    int mCenterX,mCenterY;
    float mRadius;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();
        mPos=new float[2];
        mTan=new float[2];

        //載入箭頭圖片
        mArrowBmp= BitmapFactory.decodeResource(getResources(), R.drawable.arrow);

        mPaint.setColor(Color.BLUE); //畫筆顏色
        mPaint.setStrokeWidth(5); //畫筆粗細
        mPaint.setStyle(Paint.Style.STROKE); //畫筆風格

        mPath.addCircle(540, 972, 486, Path.Direction.CW); //一個完整的圓
        mPathMeasure.setPath(mPath, true); //要計算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //進度 0~1
        animator.setRepeatCount(ValueAnimator.INFINITE); //無限迴圈
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //得到當前的進度
                invalidate();//重繪,重新執行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        /*
         * 得到h,w的最小的那個值;
         * >> 1 移位 跟 /2 相同;
         * 乘以0.9f,表示占佈局的90%
         * */
        mRadius = (Math.min(h, w) >> 1) * 0.9f;

        // 中心坐標
        mCenterX = w / 2;
        mCenterY = h / 2;
        Log.v("showLog",mCenterX+"  "+mCenterY+"  "+mRadius);
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float stop=mPathMeasure.getLength()*mCurAnimValue; //一個進度確定一個截取點

        mDstPath.reset();
        mPathMeasure.getSegment(0,stop,mDstPath,true); //一點點添加

        canvas.drawPath(mDstPath,mPaint); //每次有進度更新,就繪製一小段截取

        mPathMeasure.getPosTan(stop,mPos,mTan); //獲得每點的正切值和坐標

        /**
         *    Math.atan2(mTan[1],mTan[0])獲得tan的弧度值
         *    *180.0/Math.PI將轉化為角度值
         * */
        float degrees=(float)(Math.atan2(mTan[1],mTan[0])*180.0/Math.PI);

        Matrix matrix=new Matrix();

        /**
         * 將圖片圍繞中心點旋轉指定角度
         * postRotate(float degrees, float px, float py)
         * degrees是角度  (px,py)是圖片中心點
         * */
        matrix.postRotate(degrees,mArrowBmp.getWidth()/2,mArrowBmp.getHeight()/2);

        /**
         * 將圖片從預設的(0,0)點移動到路徑的最前端
         * */
        matrix.postTranslate(mPos[0]-mArrowBmp.getWidth()/2,mPos[1]-mArrowBmp.getHeight()/2);

        //繪製圖片
        canvas.drawBitmap(mArrowBmp,matrix,mPaint);

    }
}

效果:

箭頭動態畫圓.gif

2.7 getMatrix()

參數類型:

boolean getMatrix(float distance, Matrix matrix, int flags)

使用方法:

  		//計算方位角
        Matrix matrix = new Matrix();

		//獲取位置信息
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG);

		//獲取切邊信息
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.TANGENT_MATRIX_FLAG); 

2.8 支付成功例子

public class TickView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;
    int mCenterX, mCenterY;
    float mRadius;

    public TickView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();

        mPaint.setColor(Color.BLUE); //畫筆顏色
        mPaint.setStrokeWidth(5); //畫筆粗細
        mPaint.setStyle(Paint.Style.STROKE); //畫筆風格

        mCenterX = 540;
        mCenterY = 972;
        mRadius = 486 / 2;

        /**
         * 圓
         * */
        mPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW);

        /**
         * 對勾
         * */
        mPath.moveTo(mCenterX - mRadius / 2, mCenterY);
        mPath.lineTo(mCenterX, mCenterY + mRadius / 2);
        mPath.lineTo(mCenterX + mRadius / 2, mCenterY - mRadius / 3);

        mPathMeasure.setPath(mPath, false); //要計算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 2); //進度 0~1 是圓,1~2是對勾
        animator.setRepeatCount(ValueAnimator.RESTART);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //得到當前的進度
                invalidate();//重繪,重新執行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        /*
         * 得到h,w的最小的那個值;
         * >> 1 移位 跟 /2 相同;
         * 乘以0.9f,表示占佈局的90%
         * */
        mRadius = (Math.min(h, w) >> 1) * 0.9f;

        // 中心坐標
        mCenterX = w / 2;
        mCenterY = h / 2;
        Log.v("showLog", mCenterX + "  " + mCenterY + "  " + mRadius);
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }


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

        if (mCurAnimValue < 1) {
            float stop = mPathMeasure.getLength() * mCurAnimValue;
            mPathMeasure.getSegment(0, stop, mDstPath, true);
        } else if (mCurAnimValue == 1) {
            mPathMeasure.getSegment(0, mPathMeasure.getLength(), mDstPath, true);
            mPathMeasure.nextContour();
        } else {
            float stop = mPathMeasure.getLength() * (mCurAnimValue - 1);
            mPathMeasure.getSegment(0, stop, mDstPath, true);
        }

        canvas.drawPath(mDstPath, mPaint);
    }
}

效果:
對勾動畫.gif

編程中我們會遇到多少挫折?表放棄,沙漠盡頭必是綠洲。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 來源:http://www.postgres.cn/docs/11/ 6.4. 從修改的行中返回數據 有時在修改行的操作過程中獲取數據很有用。INSERT、 UPDATE和DELETE命令都有一個支持這個的可選的 RETURNING子句。使用RETURNING 可以避免執行額外的資料庫查詢來收集數據 ...
  • skip-name-resolve IP address 'XX.XX.XX.XX' has been resolved to the host name 'XX.XX.XX.XX.ro.ovo.sc', which resembles IPv4-address itself. ...
  • SpringMVC 初始SpringMVC 在 Spring 的基本架構中,紅色圈起來的 Spring Web MVC ,也就是本系列的主角 SpringMVC,它是屬於Spring基本架構裡面的一個組成部分,屬於SpringFrameWork的後續產品,已經融合在Spring Web Flow裡面 ...
  • 來源:http://www.postgres.cn/docs/11/ 5.1. 表基礎 SQL並不保證表中行的順序。當一個表被讀取時,表中的行將以非特定順序出現,除非明確地指定需要排序。 嘗試移除一個不存在的表會引起錯誤。然而,在SQL腳本中在創建每個表之前無條件地嘗試移除它的做法是很常見的,即使發 ...
  • 本篇博客是Redis系列的第6篇,主要講解以下內容: 1. 資料庫數量 2. 切換目標資料庫 3. 設置鍵的過期時間 4. 移除鍵的過期時間 本系列的前5篇可以點擊以下鏈接查看: "Redis系列(一):Redis簡介及環境安裝" "Redis系列(二):Redis的5種數據結構及其常用命令" "R ...
  • DataHub 首先,阿裡雲也有一款名為DataHub的產品,是一個流式處理平臺,本文所述DataHub與其無關。 數據治理是大佬們最近談的一個火熱的話題。不管國家層面,還是企業層面現在對這個問題是越來越重視。數據治理要解決數據質量,數據管理,數據資產,數據安全等等。而數據治理的關鍵就在於 元數據管 ...
  • 表結構 student(StuId,StuName,StuAge,StuSex) 學生表 teacher(TId,Tname) 教師表 course(CId,Cname,C_TId) 課程表 sc(SId,S_CId,Score) 成績表 問題五:查詢沒學過“葉平”老師課的同學的學號、姓名 SELE ...
  • C快速複習,知識點總結 數據類型 基本數據類型 類型名稱說明char 字元類型存放字元的ASCII碼 int 整型存放有符號整數short短整型存放有符號整數long長整型存放有符號整數long long存放有符號整數 float 單精度浮點型存放精度不高的小數 double 雙精度浮點型存放精度較 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...