自定義可擴展疊加頭部的下拉控制項

来源:http://www.cnblogs.com/LiuZhen/archive/2017/12/08/8000377.html
-Advertisement-
Play Games

最近寫了個下拉控制項,和幾個下拉的頭部樣式,下拉控制項可以連續添加疊加幾個頭部視圖 下麵是沒有添加任何頭部尾部的視圖下拉效果 一步一步來介紹,先介紹這個下拉效果,在介紹自定義的頭部 首先在使用上,和普通的控制項沒有兩樣,拿recyclerview來做例子,因為recyclerview使用比較多,而且可以替 ...


最近寫了個下拉控制項,和幾個下拉的頭部樣式,下拉控制項可以連續添加疊加幾個頭部視圖

下麵是沒有添加任何頭部尾部的視圖下拉效果

 

一步一步來介紹,先介紹這個下拉效果,在介紹自定義的頭部

首先在使用上,和普通的控制項沒有兩樣,拿recyclerview來做例子,因為recyclerview使用比較多,而且可以替代很多的列表控制項

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <com.fragmentapp.view.refresh.RefreshLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:_height="@dimen/dp_60"
        android:id="@+id/refreshLayout" >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:divider="@color/color_e1e1e1"
            android:dividerHeight="10dp"
            android:background="#ffffff"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </com.fragmentapp.view.refresh.RefreshLayout>

</LinearLayout>
protected void init() {
        for (int i = 0; i < 20; i++) {
            list.add("" + i);
        }

        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        recyclerView.setAdapter(new HomeAdapter(getActivity(), R.layout.item_home, list));
        refreshLayout
                .setCallBack(new RefreshLayout.CallBack() {
                    @Override
                    public void refreshHeaderView(int state, String stateVal) {

                        switch (state) {
                            case RefreshLayout.DOWN_REFRESH: // 下拉刷新狀態

                                break;
                            case RefreshLayout.RELEASE_REFRESH: // 鬆開刷新狀態

                                break;
                            case RefreshLayout.LOADING: // 正在刷新中狀態

                                break;
                        }
                    }

                    @Override
                    public void pullListener(int y) {

                    }
                });
    }
  1 package com.fragmentapp.view.refresh;
  2 
  3 import android.content.Context;
  4 import android.content.res.TypedArray;
  5 import android.graphics.Color;
  6 import android.graphics.Rect;
  7 import android.support.v4.view.ViewCompat;
  8 import android.support.v7.widget.LinearLayoutManager;
  9 import android.support.v7.widget.RecyclerView;
 10 import android.support.v7.widget.StaggeredGridLayoutManager;
 11 import android.util.AttributeSet;
 12 import android.util.Log;
 13 import android.view.Gravity;
 14 import android.view.MotionEvent;
 15 import android.view.View;
 16 import android.view.ViewConfiguration;
 17 import android.widget.AbsListView;
 18 import android.widget.FrameLayout;
 19 
 20 import com.fragmentapp.R;
 21 
 22 import java.lang.reflect.Field;
 23 import java.util.ArrayList;
 24 import java.util.List;
 25 
 26 /**
 27  * Created by LiuZhen on 2017/3/24.
 28  */
 29 public class RefreshLayout extends FrameLayout {
 30 
 31     private String TAG = "tag";
 32     private int downY;// 按下時y軸的偏移量
 33     private final static float RATIO = 3f;
 34     //頭部的高度
 35     protected int mHeadHeight = 120;
 36     //頭部layout
 37     protected FrameLayout mHeadLayout,mFootLayout;//頭部容器
 38     private List<IHeadView> heads = new ArrayList<>();//支持添加多個頭部
 39     private List<IFootView> foots = new ArrayList<>();
 40 
 41     public static final int DOWN_REFRESH = 0;// 下拉刷新狀態
 42     public static final int RELEASE_REFRESH = 1;// 鬆開刷新
 43     public static final int LOADING = 2;// 正在刷新中
 44     private int currentState = DOWN_REFRESH;// 頭佈局的狀態: 預設為下拉刷新狀態
 45 
 46     private View list;//子節點中的 recyclerview 視圖
 47     private LayoutParams listParam,footParam;//用於控制下拉動畫展示
 48     private boolean isLoadingMore = false;// 是否進入載入狀態,防止多次重覆的啟動
 49     private boolean isStart = false;//表示正在載入刷新中,還沒停止
 50     private boolean isTop = false,isBottom = false;
 51     private int mTouchSlop;
 52     private CallBack callBack;
 53 
 54     public RefreshLayout(Context context) {
 55         this(context, null, 0);
 56     }
 57 
 58     public RefreshLayout(Context context, AttributeSet attrs) {
 59         this(context, attrs, 0);
 60     }
 61 
 62     public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
 63         super(context, attrs, defStyleAttr);
 64 
 65         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshLayout, defStyleAttr, 0);
 66         try {
 67             mHeadHeight = a.getDimensionPixelSize(R.styleable.RefreshLayout__height, 120);
 68         } finally {
 69             a.recycle();
 70         }
 71         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 72 
 73         init();
 74     }
 75 
 76     private void initHeaderContainer() {
 77         mHeadLayout = new FrameLayout(getContext());
 78         LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight);
 79         this.addView(mHeadLayout,layoutParams);
 80     }
 81 
 82     public void initFootContainer() {
 83         footParam = new LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight);
 84         mFootLayout = new FrameLayout(getContext());//底部佈局
 85         mFootLayout.setBackgroundColor(Color.BLACK);
 86         footParam.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
 87         footParam.setMargins(0,0,0,-mHeadHeight);
 88         this.addView(mFootLayout,footParam);
 89     }
 90 
 91     private void init(){
 92         initHeaderContainer();
 93     }
 94 
 95     @Override
 96     protected void onFinishInflate() {//佈局載入成xml時觸發
 97         super.onFinishInflate();
 98 
 99         initFootContainer();
100         if (list == null) {
101             list = getChildAt(1);
102             listParam = (LayoutParams) list.getLayoutParams();
103             list.setOnTouchListener(new OnTouchListener() {
104                 @Override
105                 public boolean onTouch(View v, MotionEvent event) {
106                     return onFingerTouch(event);
107                 }
108             });
109         }
110     }
111 
112     /**
113      * 設置頭部View
114      */
115     public RefreshLayout setHeaderView(final IHeadView headView) {
116         if (headView != null) {
117             mHeadLayout.addView(headView.getView());
118             heads.add(headView);
119         }
120         return this;
121     }
122 
123     /**
124      * 設置尾部View
125      */
126     public RefreshLayout setFootView(final IFootView footView) {
127         if (footView != null) {
128             mFootLayout.addView(footView.getView());
129             foots.add(footView);
130         }
131         return this;
132     }
133 
134     public boolean onFingerTouch(MotionEvent ev) {
135         isTop = isViewToTop(list,mTouchSlop);
136         isBottom = isViewToBottom(list,mTouchSlop);
137 //        Log.e(TAG,"isTop "+isTop+" isBottom "+isBottom);
138         switch (ev.getAction()) {
139             case MotionEvent.ACTION_DOWN :
140                 currentState = LOADING;
141                 downY = (int) ev.getY();
142                 break;
143             case MotionEvent.ACTION_MOVE :
144                 if (!isTop && !isBottom)//沒有到頂,無需計算操作
145                     break;
146                 int moveY = (int) ev.getY();
147                 int diff = (int) (((float)moveY - (float)downY) / RATIO);
148 //                int paddingTop = -mHeadLayout.getHeight() + diff;
149                 int paddingTop = diff;
150                 if (paddingTop>0 && isTop) {
151                     //向下滑動多少後開始啟動刷新,Margin判斷是為了限制快速用力滑動的時候導致頭部侵入的高度不夠就開始載入了
152                     if (paddingTop >= mHeadHeight && (listParam.topMargin >= mHeadHeight) && currentState == DOWN_REFRESH) { // 完全顯示了.
153 //                        Log.i(TAG, "鬆開刷新 RELEASE_REFRESH");
154                         currentState = RELEASE_REFRESH;
155                         refreshHeaderView();
156                         start();
157                     } else if (currentState == LOADING) { // 沒有顯示完全
158 //                        Log.i(TAG, "下拉刷新 DOWN_PULL_REFRESH");
159                         currentState = DOWN_REFRESH;
160                         refreshHeaderView();
161                     }
162                     if (paddingTop <= (mHeadHeight+10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置
163                         listParam.setMargins(0, paddingTop, 0, 0);
164                         list.setLayoutParams(listParam);
165                         if (callBack != null)
166                             callBack.pullListener(paddingTop);
167                     }
168 
169                 }else if (isBottom){
170                     //限制上滑時不能超過底部的寬度,不然會超出邊界
171                     //mHeadHeight+20 上滑設置的margin要超過headheight,不然下麵判斷的大於headheight不成立,下麵的margin基礎上面設置後的參數
172                     if (Math.abs(paddingTop) <= (mHeadHeight+10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置
173                         listParam.setMargins(0, 0, 0, -paddingTop);
174                         footParam.setMargins(0,0,0,-paddingTop-mHeadHeight);
175                         list.setLayoutParams(listParam);
176                     }
177                     //如果滑動的距離大於頭部或者底部的高度,並且設置的margin也大於headheight
178                     //listParam用來限制recyclerview列表迅速滑動,footParam用來限制bottom foothead迅速滑動導致沒有達到head的高度就開始載入了
179                     if (Math.abs(paddingTop) >= mHeadHeight && (listParam.bottomMargin >= mHeadHeight || footParam.bottomMargin >= 0))
180                         isLoadingMore = true;//頭部是否拉取到位,然後執行載入動畫
181 
182                 }
183 //                Log.e(TAG,"paddingTop "+paddingTop +" mHeadHeight "+mHeadHeight+ " topMargin "+listParam.topMargin+" bottomMargin "+listParam.bottomMargin
184 //                        +" footParam bottom "+footParam.bottomMargin);
185 //                Log.i(TAG,"paddingTop "+paddingTop);
186                 break;
187             case MotionEvent.ACTION_UP :
188                 currentState = LOADING;
189                 refreshHeaderView();
190                 if (isLoadingMore){
191                     isLoadingMore = false;
192                     isStart = true;//是否開始載入
193                     postDelayed(new Runnable() {
194                         @Override
195                         public void run() {
196 //                            Log.i(TAG, "停止 END");
197 //                            currentState = END;
198                             refreshHeaderView();
199                             listParam.setMargins(0, 0, 0, 0);
200                             footParam.setMargins(0,0,0,-mHeadHeight);
201                             list.setLayoutParams(listParam);
202                             stop();
203                         }
204                     },2000);
205                 }else{
206                     if (!isStart){
207                         // 隱藏頭佈局
208                         listParam.setMargins(0, 0,0,0);
209                         footParam.setMargins(0,0,0,-mHeadHeight);
210                         list.setLayoutParams(listParam);
211                     }
212                 }
213 //                Log.i(TAG, "鬆開 REFRESHING");
214                 break;
215             default :
216                 break;
217         }
218         return super.onTouchEvent(ev);
219     }
220 
221     /**
222      * 根據currentState刷新頭佈局的狀態
223      */
224     private void refreshHeaderView() {
225         if (callBack == null || isStart)
226             return;
227         String val = "準備刷新";
228         switch (currentState) {
229             case DOWN_REFRESH : // 下拉刷新狀態
230                 val = "下拉刷新";
231                 break;
232             case RELEASE_REFRESH : // 鬆開刷新狀態
233                 val = "開始刷新...";
234                 break;
235             case LOADING : // 正在刷新中狀態
236                 val = "正在刷新中...";
237                 break;
238         }
239         callBack.refreshHeaderView(currentState,val);
240     }
241 
242     public static boolean isViewToTop(View view, int mTouchSlop){
243         if (view instanceof AbsListView) return isAbsListViewToTop((AbsListView) view);
244         if (view instanceof RecyclerView) return isRecyclerViewToTop((RecyclerView) view);
245         return  (view != null && Math.abs(view.getScrollY()) <= 2 * mTouchSlop);
246     }
247 
248     public static boolean isViewToBottom(View view, int mTouchSlop){
249         if (view instanceof AbsListView) return isAbsListViewToBottom((AbsListView) view);
250         if (view instanceof RecyclerView) return isRecyclerViewToBottom((RecyclerView) view);
251 //        if (view instanceof WebView) return isWebViewToBottom((WebView) view,mTouchSlop);
252 //        if (view instanceof ViewGroup) return isViewGroupToBottom((ViewGroup) view);
253         return false;
254     }
255 
256     public static boolean isAbsListViewToTop(AbsListView absListView) {
257         if (absListView != null) {
258             int firstChildTop = 0;
259             if (absListView.getChildCount() > 0) {
260                 // 如果AdapterView的子控制項數量不為0,獲取第一個子控制項的top
261                 firstChildTop = absListView.getChildAt(0).getTop() - absListView.getPaddingTop();
262             }
263             if (absListView.getFirstVisiblePosition() == 0 && firstChildTop == 0) {
264                 return true;
265             }
266         }
267         return false;
268     }
269 
270     public static boolean isRecyclerViewToTop(RecyclerView recyclerView) {
271         if (recyclerView != null) {
272             RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
273             if (manager == null) {
274                 return true;
275             }
276             if (manager.getItemCount() == 0) {
277                 return true;
278             }
279 
280             if (manager instanceof LinearLayoutManager) {
281                 LinearLayoutManager layoutManager = (LinearLayoutManager) manager;
282 
283                 int firstChildTop = 0;
284                 if (recyclerView.getChildCount() > 0) {
285                     // 處理item高度超過一屏幕時的情況
286                     View firstVisibleChild = recyclerView.getChildAt(0);
287                     if (firstVisibleChild != null && firstVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) {
288                         if (android.os.Build.VERSION.SDK_INT < 14) {
289                             return !(ViewCompat.canScrollVertically(recyclerView, -1) || recyclerView.getScrollY() > 0);
290                         } else {
291                             return !ViewCompat.canScrollVertically(recyclerView, -1);
292                         }
293                     }
294 
295                     // 如果RecyclerView的子控制項數量不為0,獲取第一個子控制項的top
296 
297                     // 解決item的topMargin不為0時不能觸發下拉刷新
298                     View firstChild = recyclerView.getChildAt(0);
299                     RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) firstChild.getLayoutParams();
300                     firstChildTop = firstChild.getTop() - layoutParams.topMargin - getRecyclerViewItemTopInset(layoutParams) - recyclerView.getPaddingTop();
301                 }
302 
303                 if (layoutManager.findFirstCompletelyVisibleItemPosition() < 1 && firstChildTop == 0) {
304                     return true;
305                 }
306             } else if (manager instanceof StaggeredGridLayoutManager) {
307                 StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager;
308 
309                 int[] out = layoutManager.findFirstCompletelyVisibleItemPositions(null);
310                 if (out[0] < 1) {
311                     return true;
312                 }
313             }
314         }
315         return false;
316     }
317 
318     /**
319      * 通過反射獲取RecyclerView的item的topInset
320      */
321     private static int getRecyclerViewItemTopInset(RecyclerView.LayoutParams layoutParams) {
322         try {
323             Field field = RecyclerView.LayoutParams.class.getDeclaredField("mDecorInsets");
324             field.setAccessible(true);
325             // 開發者自定義的滾動監聽器
326             Rect decorInsets = (Rect) field.get(layoutParams);
327             return decorInsets.top;
328         } catch (Exception e) {
329             e.printStackTrace();
330         }
331         return 0;
332     }
333 
334     public static boolean isAbsListViewToBottom(AbsListView absListView) {
335         if (absListView != null && absListView.getAdapter() != null && absListView.getChildCount() > 0 && absListView.getLastVisiblePosition() == absListView.getAdapter().getCount() - 1) {
336             View lastChild = absListView.getChildAt(absListView.getChildCount() - 1);
337 
338             return lastChild.getBottom() <= absListView.getMeasuredHeight();
339         }
340         return false;
341     }
342 
343     public static boolean isRecyclerViewToBottom(RecyclerView recyclerView) {
344         if (recyclerView != null) {
345             RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
346             if (manager == null || manager.getItemCount() == 0) {
347                 return false;
348             }
349 
350             if (manager instanceof LinearLayoutManager) {
351                 // 處理item高度超過一屏幕時的情況
352                 View lastVisibleChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
353                 if (lastVisibleChild != null && lastVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) {
354                     if (android.os.Build.VERSION.SDK_INT < 14) {
355                         return !(ViewCompat.canScrollVertically(recyclerView, 1) || recyclerView.getScrollY() < 0);
356                     } else {
357                         return !ViewCompat.canScrollVertically(recyclerView, 1);
358                     }
359                 }
360 
361                 LinearLayoutManager layoutManager = (LinearLayoutManager) manager;
362                 if (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) {
363                     return true;
364                 }
365             } else if (manager instanceof StaggeredGridLayoutManager) {
366                 StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager;
367 
368                 int[] out = layoutManager.findLastCompletelyVisibleItemPositions(null);
369                 int lastPosition = layoutManager.getItemCount() - 1;
370                 for (int position : out) {
371                     if (position == lastPosition) {
372                         return true;
373                     }
374                 }
375             }
376         }
377         return false;
378     }
379 
380     public void start(){
381         isLoadingMore = true;
382         for (IHeadView head : heads) {
383             head.startAnim();
384         }
385     }
386 
387     public void stop(){
388         isLoadingMore = false;
389         isStart = false;
390         for (IHeadView head : heads) {
391             head.stopAnim();
392         }
393     }
394 
395     public RefreshLayout setCallBack(CallBack callBack) {
396         this.callBack = callBack;
397         return this;
398     }
399 
400     public interface CallBack{
401         /**監聽下拉時的狀態*/
402         void refreshHeaderView(int state,String stateVal);
403         /**監聽下拉時的距離*/
404         void pullListener(
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 有沒有覺得Android的findViewById挺煩人的。使用Kotlin可以讓你徹底拋棄這個煩惱 步驟1、在build.gradle(Module:app)中添加如下一句話 這個在老一點版本的Android Studio中需要手動添加,我的是Android Studio3.0的,這句話是預設加上 ...
  • Android系統中,目前Dangerous級別的許可權都需要動態申請。步驟如下; 1、AndroidManfiest.xml中申明需要的動態許可權 2、代碼中檢查許可權、申請許可權 如下方法執行之後,會彈出提示框,提示要申請該許可權 3、獲取結果 轉載請註明鏈接:http://www.cnblogs.com ...
  • MvvmCross從4.0之後plugin的註冊介面做了重構,網上例子不多,這裡給個參考。本例子使用MvvmCross.Plugins.DownloadCache和MvvmCross.Plugins.File.PluginLoader來顯示網上的一個圖片。 1,View里先給個UIKit.UIIma ...
  • 在iOS開發中、經常用到圖片的本地化。 iOS 圖片本地存儲、本地獲取、本地刪除,可以通過以下類方法實現。 //將圖片保存到本地 + (void)SaveImageToLocal:(UIImage*)image Keys:(NSString*)key { //首先,需要獲取沙盒路徑 NSString ...
  • 內容摘要:Android Handler消息傳遞機制的學習總結、問題記錄 Handler消息傳遞機制的目的: 1.實現線程間通信(如:Android平臺只允許主線程(UI線程)修改Activity里的UI組件,而實際開發時會遇到新開的線程要改變界面組件屬性的情況,這時就要有一種辦法通知主線程更新UI ...
  • 本次分析針對當下流行的中國地圖圖片處理,1億像素,就是下麵這張: 原圖尺寸:11935x8554 文件大小:22.1MB 原始載入方式 首先,我們嘗試一下直接載入的方式,看看效果會有多恐怖 首先,我們嘗試一下直接載入的方式,看看效果會有多恐怖 效果請看下麵的Gif動畫展示: 直接載入原圖記憶體占用 可 ...
  • 一、新浪微博分享規則 新浪微博支持分享類型: 應用內分享也就是網頁分享支持: 文字,文字+圖片,要分享鏈接需要鏈接添加在text里分享 客戶端分享支持:文字,圖片,文字+圖片,圖片+文字+鏈接 參數說明:text:不能超過140個漢字image:圖片最大不超過5M,僅支持JPEG、GIF、PNG格式 ...
  • 有些項目成熟以後,就會有需求自動化配置生成一個全新的項目,不需要再讓開發人員手動修改工程文件,將配置化工作直接移交給運維或者配置團隊去做 其實按照普通的做法,無非就是在xcode里將目標target duplicate一下,然後修改相關的項目名稱、target名稱、bundleid等等,這些內容其實 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...