ViewPager之Fragment頁面切換

来源:http://www.cnblogs.com/snser/archive/2016/08/07/5700754.html
-Advertisement-
Play Games

一、概述 ViewPager是android-support-v4中提供的類,它是一個容器類,常用於頁面之間的切換。 繼上篇文章《ViewPager之引導頁》之後,本文主要介紹ViewPager更為通用的實踐:ViewPager搭配Fragment實現頁面切換。 這種實現方式相對於上篇文章而言,可以 ...


一、概述

ViewPager是android-support-v4中提供的類,它是一個容器類,常用於頁面之間的切換。

繼上篇文章《ViewPager之引導頁》之後,本文主要介紹ViewPager更為通用的實踐:ViewPager搭配Fragment實現頁面切換。

這種實現方式相對於上篇文章而言,可以更好的支持不同頁面各自的複雜邏輯,與此同時,也能夠保障頁面之間的耦合度儘可能的低。

按照慣例,先曬出效果圖:

      

 

二、實現思路

首先分析一下不同區域的交互需求:

中間灰色區域除了要支持三套完全不同的邏輯之外,還要支持左右滑動切換。而頂部ActionBar和底部Tab切換都只需要更新狀態,無需整體變換,也不需要整體滑動;

因此,中間灰色區域用ViewPager實現,它包含三個Fragment子頁面。而頂部ActionBar和底部Tab切換各自是一個Fragment,直接隸屬於Activity。

然後解決不同Fragment之間的依賴關係:

五個Fragment之間溝通的唯一紐帶就是ViewPager頁面的切換,因此,ViewPager頁面切換時通知到不同的Fragment即可。

 [轉載請保留本文地址:http://www.cnblogs.com/snser/p/5700754.html] 

三、開始幹活

3.1 搭建整體框架

本文的五個Fragment採用三種方式載入:

ViewPager中的三個Fragment自然是通過其Adatper進行載入,頂部的TitleFragment(ActionBar)直接聲明在layout佈局中,而底部的TabFragment採用動態載入。

這樣做的目的是方便後續分析不同載入方式的實際載入實際。

ok,整體的layout佈局自然也就成了下麵的樣子:

viewpager_fragment.xml(activity的佈局): 

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="@color/background_default"
 6     tools:context="${relativePackage}.${activityClass}" >
 7     
 8     <fragment
 9         android:id="@+id/viewpager_fragment_title"
10         android:name="cc.snser.cnblog5700754.TitleFragment"
11         android:layout_width="match_parent"
12         android:layout_height="wrap_content"
13         android:layout_alignParentTop="true" />
14 
15     <android.support.v4.view.ViewPager
16         android:id="@+id/viewpager_fragment_pager"
17         android:layout_width="match_parent"
18         android:layout_height="match_parent"
19         android:layout_above="@+id/viewpager_fragment_container"
20         android:layout_below="@+id/viewpager_fragment_title" />
21     
22     <FrameLayout
23         android:id="@id/viewpager_fragment_container"
24         android:layout_width="match_parent"
25         android:layout_height="wrap_content"
26         android:layout_alignParentBottom="true" >
27     </FrameLayout>
28     
29 </RelativeLayout>

對應的,主界面的邏輯如下:

ViewPagerFragmentActivity.java:

  1 public class ViewPagerFragmentActivity extends FragmentActivity {
  2     private FragmentManager mManager = getSupportFragmentManager();
  3     
  4     private TitleFragment mTitleFragment;
  5     private TabFragment mTabFragment;
  6     private ViewPager mPager;
  7     
  8     private ViewPagerAdapter mAdapter;
  9     
 10     private static final int DEFAULT_PAGE = 1; //預設頁面
 11     
 12     @Override
 13     protected void onCreate(Bundle savedInstanceState) {
 14         Log.d("Snser", "ViewPagerFragmentActivity onCreate");
 15         super.onCreate(savedInstanceState);
 16         Log.d("Snser", "ViewPagerFragmentActivity onCreate setContentView");
 17         setContentView(R.layout.viewpager_fragment);
 18         Log.d("Snser", "ViewPagerFragmentActivity onCreate initView");
 19         initView();
 20     }
 21     
 22     private void initView() {
 23         mTitleFragment = (TitleFragment)mManager.findFragmentById(R.id.viewpager_fragment_title);
 24         mTabFragment = new TabFragment();
 25         mTabFragment.setOnTabClickListenser(new ViewPageTabClickListenser());
 26         mManager.beginTransaction().replace(R.id.viewpager_fragment_container, mTabFragment).commit();
 27         mPager = (ViewPager)findViewById(R.id.viewpager_fragment_pager);
 28         mPager.setAdapter(mAdapter = new ViewPagerAdapter(mManager));
 29         mPager.setOnPageChangeListener(new ViewPageChangeListener());
 30         setCurrentItem(DEFAULT_PAGE);
 31     }
 32     
 33     @Override
 34     protected void onResume() {
 35         Log.d("Snser", "ViewPagerFragmentActivity onResume");
 36         super.onResume();
 37     }
 38     
 39     private class ViewPageChangeListener implements OnPageChangeListener {
 40         @Override
 41         public void onPageSelected(int position) {
 42             setCurrentItem(position);
 43         }
 44         
 45         @Override
 46         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
 47         }
 48         
 49         @Override
 50         public void onPageScrollStateChanged(int state) {
 51         }
 52     }
 53     
 54     private class ViewPagerAdapter extends FragmentPagerAdapter {
 55         private ArrayList<PagerFragment> mFragments = new ArrayList<PagerFragment>();
 56         
 57         public ViewPagerAdapter(FragmentManager fm) {
 58             super(fm);
 59             mFragments.add(new ClickFragment());
 60             mFragments.add(new DateFragment());
 61             mFragments.add(new AnimFragment());
 62         }
 63 
 64         @Override
 65         public Fragment getItem(int position) {
 66             return mFragments.get(position);
 67         }
 68 
 69         @Override
 70         public int getCount() {
 71             return mFragments.size();
 72         }
 73     }
 74     
 75     private class ViewPageTabClickListenser implements OnTabClickListenser {
 76         @Override
 77         public void onTabClick(int tab) {
 78             setCurrentItem(tab);
 79         }
 80     }
 81        
 82     private void setCurrentItem(int item) {
 83         if (item == mPager.getCurrentItem()) {
 84             //此時是源於initView或onPageSelected的調用
 85             notifyPageChangeToFragments(item);
 86         } else {
 87             //此時是源於initView或onTabClick的調用,後續會自動觸發一次onPageSelected
 88             mPager.setCurrentItem(item);
 89         }
 90     }
 91     
 92     private void notifyPageChangeToFragments(int item) {
 93         for (int page = 0; page != mAdapter.getCount(); ++page) {
 94             final Fragment fragment = mAdapter.getItem(page);
 95             if (fragment instanceof PagerFragment) {
 96                 if (page == item) {
 97                     ((PagerFragment)fragment).onPageIn();
 98                 } else {
 99                     ((PagerFragment)fragment).onPageOut();
100                 }
101             }
102         }
103         mTitleFragment.setCurrentTab(item);
104         mTabFragment.setCurrentTab(item);
105     }
106 }

可以看到,包含三種完全不同功能的Activity,其主界面代碼居然只有區區106行,這甚至比上篇文章《ViewPager之引導頁》還要少。

這必須要歸功於Fragment的使用。

三個頁面的具體邏輯分別由DateFragment、ClickFragment、AnimFragment進行維護,而ViewPager要做的,只是進行Fragment的切換和通知。

具體來說,ViewPagerAdapter負責根據不同的postion取出不同的Fragment。而三個頁面Fragment都繼承自PagerFragment:

PagerFragment.java: 

1 public class PagerFragment extends Fragment{
2     
3     public void onPageIn() {
4     }
5     
6     public void onPageOut() {
7     }
8     
9 }

亦即在頁面切換的時候(不管是來自滑動還是點擊tab),activity都會通知三個fragment它們各自是否可見。

同時,在 notifyPageChangeToFragments 方法中,也會通知TitleFragment和TabFragment,它們該切換狀態了。

3.2 Fragment的各自實現

拿AnimFragment舉例,這個動畫Fragment的功能是在其切入(onPageIn)的時候播放淡入的動畫,而在其切出(onPageOut)的時候銷毀動畫。 

 1 public class AnimFragment extends PagerFragment {
 2     private View mViewRoot;
 3     private ImageView mImg;
 4     
 5     private Animation mAnim;
 6     
 7     @Override
 8     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 9         Log.d("Snser", "AnimFragment onCreateView");
10         mViewRoot = inflater.inflate(R.layout.viewpager_fragment_anim, container, false);
11         mAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.anim_fade_in);
12         mAnim.setInterpolator(new LinearInterpolator());
13         initView(mViewRoot);
14         return mViewRoot;
15     }
16     
17     private void initView(View root) {
18         mImg = (ImageView)root.findViewById(R.id.viewpager_fragment_anim_img);
19         startAnim();
20     }
21     
22     @Override
23     public void onDestroy() {
24         super.onDestroy();
25         stopAnim();
26     }
27     
28     @Override
29     public void onPageIn() {
30         super.onPageIn();
31         startAnim();
32     }
33     
34     @Override
35     public void onPageOut() {
36         super.onPageOut();
37         stopAnim();
38     }
39     
40     private void startAnim() {
41         if (mImg != null && mAnim != null) {
42             mImg.startAnimation(mAnim);
43         }
44     }
45     
46     private void stopAnim() {
47         if (mImg != null && mAnim != null) {
48             mImg.clearAnimation();
49         }
50     }
51     
52 }

DateFragment的作用是在切入的時候刷新當前日期和時間:

DateFragment.java:

 1 public class DateFragment extends PagerFragment {
 2 
 3     private View mViewRoot;
 4     private TextView mTxtDate;
 5     
 6     private SimpleDateFormat mFormat = new SimpleDateFormat("yyyy/MM/dd\nHH:mm:ss", Locale.CHINA);
 7     
 8     @Override
 9     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
10         Log.d("Snser", "DateFragment onCreateView");
11         mViewRoot = inflater.inflate(R.layout.viewpager_fragment_date, container, false);
12         initView(mViewRoot);
13         return mViewRoot;
14     }
15     
16     @Override
17     public void onPageIn() {
18         super.onPageIn();
19         refreshDate();
20     }
21     
22     private void initView(View root) {
23         mTxtDate = (TextView)root.findViewById(R.id.viewpager_fragment_date_txt);
24         refreshDate();
25     }
26     
27     private void refreshDate() {
28         if (mTxtDate != null) {
29             mTxtDate.setText(mFormat.format(new Date()));
30         }
31     }
32     
33 }
View Code

ClickFragment則搞定了一個多次連續點擊“中彩蛋”的效果:

ClickFragment.java:

 1 public class ClickFragment extends PagerFragment {
 2     private View mViewRoot;
 3     private Button mBtnClick;
 4     
 5     @Override
 6     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 7         Log.d("Snser", "ClickFragment onCreateView");
 8         mViewRoot = inflater.inflate(R.layout.viewpager_fragment_click, container, false);
 9         initView(mViewRoot);
10         return mViewRoot;
11     }
12     
13     private void initView(View root) {
14         mBtnClick = (Button)root.findViewById(R.id.viewpager_fragment_click_btn);
15         mBtnClick.setOnClickListener(new MultiClickListener());
16     }
17     
18     private static class MultiClickListener implements View.OnClickListener {
19         private int mCount = 0;
20         private long mLastClickTime = 0;
21         
22         private static final long MAX_CLICK_COUNT = 4;
23         private static final long TIMEOUT_CLICK = 500;
24         private static final int MSG_TIMEOUT_CLICK = 0x1001;
25         
26         private static Handler sHandler = new Handler() {
27             @Override
28             public void handleMessage(Message msg) {
29                 if (msg.what == MSG_TIMEOUT_CLICK && msg.obj instanceof Button) {
30                     Log.d("Snser", "MultiClickListener 連續點擊超時");
31                     ((Button)msg.obj).setText("點我");
32                 }
33             }
34         };
35         
36         @Override
37         public void onClick(View v) {
38             if (v instanceof Button) {
39                 final long time = System.currentTimeMillis();
40                 if (mCount == 0 || time - mLastClickTime < TIMEOUT_CLICK) {
41                     //這是連續點擊
42                     Log.d("Snser", "MultiClickListener 連續點擊 mCount=" + (mCount + 1));
43                     ++mCount;
44                     mLastClickTime = time;
45                     sHandler.removeMessages(MSG_TIMEOUT_CLICK);
46                     sHandler.sendMessageDelayed(Message.obtain(sHandler, MSG_TIMEOUT_CLICK, v), TIMEOUT_CLICK);
47                     if (mCount < MAX_CLICK_COUNT) {
48                         ((Button)v).setText("點我   +" + mCount);
49                     } else {
50                         ((Button)v).setText("死機了!");
51                     }
52                 } else {
53                     //這是新的點擊
54                     Log.d("Snser", "MultiClickListener 新的點擊 mCount=0");
55                     mCount = 0;
56                     onClick(v);
57                 }
58             }
59         }
60     }
61     
62 }
View Code

除了頁面Fragment之外,TabFragment和TitleFragment都會在 setCurrentTab 方法中更新自己的狀態。

不同的是,TabFragment會將點擊tab的事件通過 OnTabClickListenser 反饋給activity: 

 1 public class TabFragment extends Fragment {
 2     private View mRootView;
 3     
 4     private TextView mTxtTabApp;
 5     private TextView mTxtTabHome;
 6     private TextView mTxtTabUser;
 7     
 8     private OnTabClickInternalListener mTabClickInternalListener = new OnTabClickInternalListener();
 9     private OnTabClickListenser mOnTabClickListenser;
10     
11     private int mDefaultTab = 0;
12     private int mCurrentTab = -1;
13     
14     @Override
15     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
16         Log.d("Snser", "TabFragment onCreateView");
17         mRootView = inflater.inflate(R.layout.viewpager_fragment_tab, container, false);
18         initView(mRootView);
19         return mRootView;
20     }
21     
22     private void initView(View root) {
23         mTxtTabApp = (TextView)root.findViewById(R.id.viewpager_fragment_tab_app);
24         mTxtTabApp.setTag(Integer.valueOf(0));
25         mTxtTabApp.setOnClickListener(mTabClickInternalListener);
26         mTxtTabHome = (TextView)root.findViewById(R.id.viewpager_fragment_tab_home);
27         mTxtTabHome.setTag(Integer.valueOf(1));
28         mTxtTabHome.setOnClickListener(mTabClickInternalListener);
29         mTxtTabUser = (TextView)root.findViewById(R.id.viewpager_fragment_tab_user);
30         mTxtTabUser.setTag(Integer.valueOf(2));
31         mTxtTabUser.setOnClickListener(mTabClickInternalListener);
32         setCurrentTab(mDefaultTab);
33     }
34     
35     public interface OnTabClickListenser {
36         public void onTabClick(int tab);
37     }
38     
39     private class OnTabClickInternalListener implements View.OnClickListener {
40         @Override
41         public void onClick(View v) {
42             if (v != null && v.getTag() instanceof Integer) {
43                 final int tab = (Integer)v.getTag();
44                 if (tab != mCurrentTab && mOnTabClickListenser != null) {
45                     setCurrentTab(tab);
46                     mOnTabClickListenser.onTabClick(tab);
47                 }
48             }
49         }
50     }
51     
52     public void setOnTabClickListenser(OnTabClickListenser listenser) {
53         mOnTabClickListenser = listenser;
54     }
55     
56     public void setCurrentTab(int tab) {
57         if (mTxtTabApp != null && mTxtTabHome != null && mTxtTabUser != null) {
58             mCurrentTab = tab;
59             mTxtTabApp.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_normal));
60             mTxtTabHome.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_normal));
61             mTxtTabUser.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_normal));
62             switch (tab) {
63                 case 0:
64                     mTxtTabApp.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_current));
65                     break;
66                 case 1:
67                     mTxtTabHome.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_current));
68                     break;
69                 case 2:
70                     mTxtTabUser.setTextColor(getResources().getColor(R.color.viewpager_fragment_tab_current));
71                     break;
72                 default:
73                     break;
74             }
75         } else {
76             //TabFragment在Activity的onResume之後才會onCreateView
77             //setCurrentTab的時候控制項還沒初始化,存一下初始值在initView里再初始化
78             mDefaultTab = tab;
79         }
80     }
81 }
 1 public class TitleFragment extends Fragment {
 2     private View mRootView;
 3     private TextView mTxt;
 4     
 5     @Override
 6     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 7         Log.d("Snser", "TitleFragment onCreateView");
 8         mRootView = inflater.inflate(R.layout.viewpager_fragment_title, container, false);
 9         initView(mRootView);
10         return mRootView;
11     }
12     
13     private void initView(View root) {
14         mTxt = (TextView)root.findViewById(R.id.viewpager_fragment_title_txt);
15     }
16     
17     public void setCurrentTab(int tab) {
18         if (mTxt != null) {
19             switch (tab) {
20                 case 0:
21                     mTxt.setText("#ClickFragment");
22                     break;
23                 case 1:
24                     mTxt.setText("#DateFragment");
25                     break;
26                 case 2:
27                     mTxt.setText("#AnimFragment");
28                     break;
29                 default:
30                     break;
31             }
32         }
33     }
34 }

3.3 Fragment的載入順序

回歸到3.1節中介紹到本文采用三種方式進行Fragment的載入,目的就是為了比較不同方式下Fragment的載入時機。

在 DEFAULT_PAGE = 1 時,各個Fragment的載入順序為: 

//          ViewPagerFragmentActivity onCreate
//          ViewPagerFragmentActivity onCreate setContentView
//          TitleFragment onCreateView (聲明在layout佈局中的Fragment在setContentView之後就會被載入)
//          ViewPagerFragmentActivity onCreate initView
//          TabFragment onCreateView (動態replace的Fragment在replace之後、onResume之前被載入)
//          ViewPagerFragmentActivity onResume
//          DateFragment onCreateView (ViewPager最先載入預設頁)
//          ClickFragment onCreateView (ViewPager預設頁左邊的頁面會被預載入)
//<

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

-Advertisement-
Play Games
更多相關文章
  • 文本陰影text-shadow屬性特效: 1.右下角陰影,左下角陰影,左上角陰影,右上角陰影 文字效果為: 如果將text-shadow改成:text-shadow:-10px 10px #333 ,就將出現左下角陰影 將text-shadow改成:text-shadow:-10px -10px # ...
  • Node.js v6.3.1 Documentation https://nodejs.org/dist/latest-v6.x/docs/api/​ npm官網 https://www.npmjs.com/​ 菜鳥教程nodejs http://www.runoob.com/nodejs/node ...
  • meta標簽作用 META標簽是HTML標記HEAD區的一個關鍵標簽,提供文檔字元集、使用語言、作者等基本信息,以及對關鍵詞和網頁等級的設定等,最大的作用是能夠做搜索引擎優化(SEO)。 PS:便於搜索引擎機器人查找、分類,互聯網應用應該要註意。 大網站都是怎麼寫? 在瞭解這個標簽之前,我查找了各個 ...
  • 1、效果圖 2、佈局文件 3、代碼實現(方式一) 4、代碼實現(方式二) ...
  • OS X 10.11 安裝Cocoapods 出現問題的解決方法 今天嘗試用 Cocoapods安裝個第三方庫.. 輸入pod install, 發現 command not find。 WTF! 估計是升級10.11後Cocoapods被幹掉了。 我輸入 sudo gem install coco ...
  • 今天博客中的Alamofire源碼的版本是以現在最新的3.4版本為例。上篇博客系統的對NSURLSession相關的東西進行了詳細的解析,詳情請看《詳解NSURLSession》,為了就是給本篇博客打下基礎。因為AlamoFire就是對NSURLSession及其相關的東西進行了進一步的封裝,讓網路 ...
  • 前言.為什麼要升級到Greendao3.0? 1. 多人開發 以往的資料庫建表建Dao等操作要新開一個module,在統一的地方管理資料庫建表,現在可以直接寫Entity。多人開發時自己管自己的Entity即可 不用像以前衝突成狗。 2. 結構簡潔方便 以往是在寫CreateTable addEnt ...
  • AptPreferences是基於面向對象設計的快速持久化框架,目的是為了簡化SharePreferences的使用,減少代碼的編寫。可以非常快速地保存基本類型和對象。AptPreferences是基於APT技術實現,在編譯期間實現代碼的生成,支持混淆。根據不同的用戶區分持久化信息。 特點 1. 把 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...