Listview詳解

来源:http://www.cnblogs.com/huangjie123/archive/2016/10/23/5990737.html
-Advertisement-
Play Games

ListView詳解 以學生信息管理系統為例,詳細介紹Listview的使用、優化 ...


      Listview應該是最為常見的控制項。對於大多數規則排列的界面,幾乎都可以用ListView進行編寫。對於單一界面來說,ListView既是最難的控制項,又是使用最為頻繁的控制項。ListView 通常用於展示大量的數據,比如讀取資料庫中的數據。ListView優勢也較為明顯,比如顯示大量數據時節約記憶體,自帶ScrollView 的功能可以實現界面滾動等。ListView 控制項的設計正好遵循MVC 設計模式。mode 數據數據模型,在ListView中具體可以指要顯示到ListView 上的數據集合;view 視圖用於展示數據,數據顯示在每一個條目上;controller 控制層,數組中的數據是無法直接傳遞給 ListView 的,需要藉助適配器把數據展示到控制項上。因此有必要對ListView的使用進行一下總結。

      Listview使用步驟並不複雜,無論是哪種場景下使用都可以歸納為以下幾步:
      1.  佈局文件里申明ListView ,設置id號和其他相關參數
      2.  在代碼中通過findViewById方法找到listView控制項
      3.  設置適配器,可以單獨定義出一個類繼承相關的適配器,也可以創建一個適配器的匿名內部類。關鍵取決於代碼的複雜程度。
      4.  重寫適配器中的方法。主要有兩個,一個是getCount()表示listview的條目數。另一個是getView返回顯示的View ,表示每一個條目的視圖。getView()方法在每個子項被滾動到屏幕內的時候會被調用。
      5.  ListView列表項的點擊事件 listView.setOnItemClickLinstener()

      知道Listview的使用步驟後,還有很多需要註意的細節問題。接下來一一列舉出來。

      首先是適配器的類型選擇。Android 中提供了很多適配器的實現類,通常情況下使用的都是BaseAdapter,而其他的適配器都是繼承自BaseAdapter。但有些界面控制項很少,利用ArrayAdapter或者SimpleAdapter就可以滿足需求。所以在這裡還是稍微介紹一下ArrayAdapter和SimpleAdapter的使用。文章最後給出一個學生信息管理系統的案例,用的適配器是BaseAdapter,所以這裡就不再對BaseAdapter的使用再做講解。

      1.   ArrayAdapter的使用
            ArrayAdapter通常用於顯示較為簡單的數組和集合數據,界面較為簡單,直接向ArrayAdapter添加相關的參數即可。

    public ArrayAdapter(Context context, int resource, T[] objects) {
        init(context, resource, 0, Arrays.asList(objects));
    }

           第一個參數context表示上下文。 resource是佈局文件的ID號,objects表示要顯示的數組或List集合。

 
      2. SimpleAdapter
          使用SimpleAdapter 的數據一般都是HashMap構成的List集合,List 的每一個對象對應ListView 的每一行。HashMap 的每個鍵值數據映射到佈局文件中對應id 的組件上。這樣可以方便的顯示圖文顯示的條目,通常設置界面就是這種形式。

public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,int resource, String[] from, int[] to) {
        mData = data;
        mResource = mDropDownResource = resource;
        mFrom = from;
        mTo = to;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

 

            context表示上下文。data表示要顯示的數據,用鍵值對的形式暫時存放數據。resource是佈局文件的ID號。from表示Map 集合中key 的數組,to表示item 佈局中控制項的id。後兩個參數均是字元轉數組,表示的是多個Item條目。

            以上兩種適配器通常用於顯示較為簡單的條目內容,比如純文本的顯示、簡單的文本和圖片組合。複雜的條目結構需要用藉助BaseaAadpter進行顯示。

       

         接著是對代碼的優化。通常使用ListView時涉及到的數據不會太少,測試的時候幾十上百條的數據可能看不出影響,但當數據達到成千上萬條的時候,未優化的listView很有可能出現異常。因此代碼優化是十分有必要的。這裡介紹兩處代碼的優化處理,一個是convertView的復用,另一個是控制項緩存機制。

         優化一   listview的復用
         convertView用於將之前載入好的佈局進行緩存,以便之後可以進行復用。通過使用convertView 對創建的視圖對象進行復用,可以節約減少記憶體消耗。對於少量相同形式的數據可以用LinearLayout代替顯示,但是當數據增加到上千條、上萬條的時候,快速滾動滑動條就會不斷地生成新的TextView,對於記憶體的消耗過大,容易造成記憶體溢出。Listview自帶上下滑動的功能,因此可以將滑出屏幕的convertView進行回收利用,每當一個item看不見的時候,那個item就可以被覆用起來了,這樣,ListView 始終保持創建的對象個數為屏幕顯示的條目的個數加一。事實上,item的view對象沒有真正的被垃圾回收器回收掉,而是重新將身上的數據給換掉了,看起來好像是出現了一個新的item。這樣視覺上就是連續的滾動條了。

        listview復用具體的做法是在 getView()方法中首先進行了判斷convertView 的內容是否為空,如果convertView的內容為空則用layoutInflater 去載入佈局,如果不為空則直接對 convertView 進行復用。這樣就大大提高了ListView 的運行效率,在快速滾動的時候也可以表現出更好的性能,再也不用擔心記憶體溢出了。

        優化二  緩存控制項的實例
        獲取佈局文件中的控制項,每次都要在getView()方法中調用 View的findViewById()方法來獲取一次控制項的實例,當需要多次關心控制項時,就會創建多次,因此可以定義一個內部類,用於存放控制項,最後將該類的對象存放在view的對象中。當 convertView 為空時,對控制項的實例進行緩存,當 convertView 不為空的時候,從內部類中取出控制項的實例,這樣就減少了調用findViewById()方法的次數了,同樣提高了代碼的運行效率。

        優化後的代碼:

/**
     * 兩點優化
     * 1. 判斷convertView是否為空
     * 2. 內部類緩存控價
     */
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View view;
        Viewholder viewholder;
        Student student = list.get(position);
        if (convertView == null) {
            view = View.inflate(context, R.layout.item, null);
            viewholder = new Viewholder();
            viewholder.tv_name = (TextView) view.findViewById(R.id.tv_name);
            viewholder.iv_sex = (ImageView) view.findViewById(R.id.iv_sex);
            viewholder.iv_delete = (ImageView) view.findViewById(R.id.iv_delete);
            view.setTag(viewholder);
        } else {
            view = convertView;
            viewholder = (Viewholder) view.getTag();
        }
        String sex = student.getSex();
        if ("male".equals(sex)) {
            viewholder.iv_sex .setImageResource(R.drawable.nan);
        } else {
            viewholder.iv_sex .setImageResource(R.drawable.nv);
        }
        viewholder.tv_name.setText(student.getName());
        viewholder.iv_delete.setOnClickListener(
                new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Student student = list.get(position);
                        String name = student.getName();
                        dao.delete(name);
                        Toast.makeText(context, "數據被刪除了", 0).show();
                        refreshData1();
                    }
                });
        return view;
    }
    class Viewholder{
        TextView tv_name;
        ImageView iv_sex,iv_delete;
    }

   

          接下來講一下ListView的界面優化。       

          對於ArrayAdapter來說可以使用Android 系統提供的簡單佈局,比如android.R.layout.simple_list_item_1。但這種方式較為固定,無法滿足實際開發多樣化的需要。因此可以重新創建一個佈局文件,對界面進行自定義,根據自己的需要添加相應的控制項。這樣可以豐富界面的效果,提升軟體美感,適應不同的開發需求。View對象本身或者任何繼承View對象的控制項都有一個方法,那就是inflate。通過inflate方法可以將一個佈局文件轉換成一個View對象,佈局裡面的所有控制項都可以通過這個View對象來進行查找。Inflate的使用方式有以下幾種。
       1. 直接用View.inflate()創建View對象,這是View自帶的方法。

  public static View inflate(Context context, int resource, ViewGroup root) {
         LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
  }

       2.  參考inflate源碼,可以獲取LayoutInflater 對象,然後調用inflate 方法

  LayoutInflater.from(context).inflate(resource, null)     

       3.通過上下文提供的getSystemService 方法獲取LayoutInfater 對象,然後調用inflate 方法

  view=getSystemService(Context.LAYOUT_INFLATER_SERVICE).inflate(R.resource, null);

       三種方式差別並不大,可任意選擇。得到View對象後,可以通過View對象找到佈局文件中的控制項,佈局文件可以單獨定義一個xml文件用來表示每一個Item條目的顯示效果。

     

       接下來要說明的是條目點擊。 Listview 主要有兩種交互方式,一個是條目滾動,另一是條目點擊。前者用於顯示,後者用於界面交互,點擊之後可以進入其他界面或相應的對話框。點擊條目歸根到底是點擊事件,使用 setOnItemClickListener()方法來為 ListView 註冊了一個監聽器,當用戶點擊了 ListView 中的任何一個子項時就會回調 onItemClick()方法, 在這個方法中可以通過 position 參數判斷出用戶點擊的是哪一個子項,然後執行接下來的邏輯。條目點擊大大地提升了界面的交互性,可以通過點擊條目來執行更為複雜的任務。

  /**
     * 
     * 刷新數據,創建條目點擊事件
     */
    private void refreshData() {
        list = dao.findAll();
        if(adapter == null ){
            adapter = new Myadapter(MainActivity.this,list);
            lv.setAdapter(adapter);
            lv.setOnItemClickListener(new OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view,
                        int position, long id) {
                    AlertDialog.Builder builder = new Builder(MainActivity.this);
                    builder.setTitle("詳細信息:");
                    builder.setMessage("姓名:" + list.get(position).getName() +"    性別:"+list.get(position).getSex() );
                    builder.show();
                }
            });
        }else{
            adapter.refreshData1();
        }
    }

        

           最後一個小細節不要忘了,那就是數據更新。很多人在編寫listview過程中經常會忘了數據更新,這樣導致的結果就是,對數據進行增刪改操作時界面不能及時刷新,只有重啟程式,才可以看到界面上的數據發生改變。Listview的界面刷新有兩種方式。一種是在getview中直接進行刷新,另一種則是單獨定義一個方法對數據進行刷新。這裡介紹第二種。在單獨定義的數據方法中,通常要做的是,首先得到要在listview中顯示的數據,可以將數據存放在List集合中。然後判斷適配器對象是否為空,若為空則定義一個新的適配器出來,否則直接調用適配器的notifyDataSetChanged()方法進行刷新。數據刷新是Listview中的一個小細節,稍微一步註意就會影響到界面的顯示效果,因此要引起格外的註意。

          至此,ListView的全部內容講解完畢。接下來用一個實例來鞏固以上的知識點。

          設計一個學生信息管理系統用於記錄學生姓名和性別,用到的知識點包括資料庫、listview顯示。首先搭建軟體界面,需要一個輸入框和單選框用於編輯學生的姓名和性別。還需要一個按鈕用於保存數據,提交數據。然後需要一個listview用於顯示所有學生的信息。對於每一個條目來說,可以用圖片來區別男女,用文本顯示學生姓名。最後還可以添加一個刪除的圖片對數據進行刪除操作。接下來是邏輯部分。首先通過設置點擊事件,當按鈕按下時,將文本框和單選框裡面內容寫入資料庫,同時通知Listview刷新界面,這樣就可以看到listview中的數據添加了一行。同樣的將刪除圖標設置為一個點擊事件,點擊刪除圖標時刪除資料庫中的相關信息,同時通知Listview刷新界面,這樣界面上的listview就會減少一行。最後可以為每一個條目添加一個條目點擊事件。當條目被點擊時,彈出一個對話框,列出學生的詳細信息。至此,用到listview的大部分知識點。

        適這裡適配器用的是BaseAdapter,單獨定義一個類出來繼承BaseAdapter,配器程式編寫如下:

package com.example.studentsysten;

import java.util.List;

import com.example.studentsysten.db.dao.StudentDao;
import com.example.studentsysten.db.domain.Student;

import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class Myadapter extends BaseAdapter {
    private List<Student> list;
    private Context context;
    private StudentDao dao;
    private Myadapter adapter;

    public Myadapter(Context context, List<Student> list) {
        this.list = list;
        this.context = context;
        dao = new StudentDao(context);
    }
    /**
     * 
     * 在getview中刷新數據
     */
    public void refreshData1() {
       list = dao.findAll();
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    /**
     * 兩點優化
     * 1. 判斷convertView是否為空
     * 2. 內部類緩存控價
     */
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View view;
        Viewholder viewholder;
        Student student = list.get(position);
        if (convertView == null) {
            view = View.inflate(context, R.layout.item, null);
            viewholder = new Viewholder();
            viewholder.tv_name = (TextView) view.findViewById(R.id.tv_name);
            viewholder.iv_sex = (ImageView) view.findViewById(R.id.iv_sex);
            viewholder.iv_delete = (ImageView) view.findViewById(R.id.iv_delete);
            view.setTag(viewholder);
        } else {
            view = convertView;
            viewholder = (Viewholder) view.getTag();
        }
        String sex = student.getSex();
        if ("male".equals(sex)) {
            viewholder.iv_sex .setImageResource(R.drawable.nan);
        } else {
            viewholder.iv_sex .setImageResource(R.drawable.nv);
        }
        viewholder.tv_name.setText(student.getName());
        viewholder.iv_delete.setOnClickListener(
                new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Student student = list.get(position);
                        String name = student.getName();
                        dao.delete(name);
                        Toast.makeText(context, "數據被刪除了", 0).show();
                        refreshData1();
                    }
                });
        return view;
    }
    class Viewholder{
        TextView tv_name;
        ImageView iv_sex,iv_delete;
    }
}

 

          程式的主邏輯:

package com.example.studentsysten;

import java.util.List;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;

import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

import com.example.studentsysten.db.dao.StudentDao;
import com.example.studentsysten.db.domain.Student;

public class MainActivity extends Activity {
    private EditText et_name;
    private RadioGroup rg_sex;
    private ListView lv;
    private StudentDao dao;
    private Myadapter adapter;
    private List<Student> list;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_name = (EditText) findViewById(R.id.et_name);
        rg_sex = (RadioGroup) findViewById(R.id.rg_sex);
        lv = (ListView) findViewById(R.id.lv);
        dao = new StudentDao(this);
        refreshData();    //初次載入時的刷新數據,每次修改數據時刷新數據
    }
    /**
     * 
     * 添加數據
     * @param view
     */
    public void save(View view){
        String name = et_name.getText().toString().trim();
        if(TextUtils.isEmpty(name)){
            Toast.makeText(MainActivity.this, "姓名不能為空", 0).show();
            return;
        }
        int id = rg_sex.getCheckedRadioButtonId();
        String sex = "male";
        if(id==R.id.male){
            sex = "male";
        }else{
            sex = "female";
        }
        long result = dao.add(name, sex);
        Toast.makeText(this, "數據添加到第"+result+"行成功", 0).show();
        refreshData();
    }
    
    /**
     * 
     * 刷新數據,創建條目點擊事件
     */
    private void refreshData() {
        list = dao.findAll();
        if(adapter == null ){
            adapter = new Myadapter(MainActivity.this,list);
            lv.setAdapter(adapter);
            lv.setOnItemClickListener(new OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view,
                        int position, long id) {
                    AlertDialog.Builder builder = new Builder(MainActivity.this);
                    builder.setTitle("詳細信息:");
                    builder.setMessage("姓名:" + list.get(position).getName() +"    性別:"+list.get(position).getSex() );
                    builder.show();
                }
            });
        }else{
            adapter.refreshData1();
        }
    }
}

        另外還用到資料庫的知識,需要創建三個類出來,分別是創建資料庫、操作資料庫、學生工具類。

        創建資料庫:

package com.example.studentsysten.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class StudentDBOpenhelper extends SQLiteOpenHelper {

    public StudentDBOpenhelper(Context context) {
        super(context, "stu.db", null, 1);
        // TODO Auto-generated constructor stub
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table student (_id integer primary key autoincrement,name varchar(30),sex varchar(10))");

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub

    }

}

 

         操作資料庫,資料庫的增刪改查:

package com.example.studentsysten.db.dao;

import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.example.studentsysten.db.StudentDBOpenhelper;
import com.example.studentsysten.db.domain.Student;

/**
 * 每一種數據操作都用了兩種方法
 * 
 */
public class StudentDao {
    private StudentDBOpenhelper helper;

    public StudentDao(Context context) {
        helper = new StudentDBOpenhelper(context);
    }

    public long add(String name, String sex) {
        SQLiteDatabase db = helper.getWritableDatabase();
        // db.execSQL("insert into student (name,sex) values(?,?)",new
        // Object[]{name,sex});
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("sex", sex);
        long result = db.insert("student", null, values);
        db.close();
        return result;
    }

    public int delete(String name) {
        SQLiteDatabase db = helper.getWritableDatabase();
        // db.execSQL("delete from student where name = ?",new Object[]{name});
        int result = db.delete("student", "name = ?", new String[] { name });
        db.close();
        return result;
    }

    public int update(String name, String sex) {
        SQLiteDatabase db = helper.getWritableDatabase();
        // db.execSQL("update student set sex = ? where name = ?",new
        // Object[]{sex,name});
        ContentValues values = new ContentValues();
        values.put("sex", sex);
        int result = db.update("student", values, "name = ?",
                new String[] { name });
        db.close();
        return result;
    }

    // 根據姓名查性別
    // public String find(String name){
    // SQLiteDatabase db = helper.getReadableDatabase();
    // Cursor cursor = db.rawQuery("select sex from student where name = ?", new
    // String[]{name});
    // // Cursor cursor = db.query("student", new String[]{"sex"}, "name = ?",
    // new String[]{name}, null, null, null);
    // String sex = null;
    // if(cursor.moveToNext()){
    // sex = cursor.getString(0);
    // }
    // return sex;
    // }
    public String find(String name) {
        String sex = null;
        SQLiteDatabase db = helper.getReadableDatabase();
        // Cursor cursor = db.rawQuery("select sex from student where name=?",
        // new String[]{name});
        Cursor cursor = db.query("student", new String[] { "sex" }, "name=?",
                new String[] { name }, null, null, null);
        boolean result = cursor.moveToNext();
        if (result) {
            sex = cursor.getString(0);
        }
        cursor.close();// 釋放資源
        db.close();
        return sex;
    }

    public List<Student> findAll() {
        List<Student> list = new ArrayList<Student>();
        SQLiteDatabase db = helper.getReadableDatabase();
        // Cursor cursor = db.rawQuery("select name,sex from student", null);
        Cursor cursor = db.query("student", new String[] { "name", "sex" },
                null, null, null, null, null);

        while (cursor.moveToNext()) {
            String name = cursor.getString(0);
            String sex = cursor.getString(1);
            Student stu = new Student();
            stu.setName(name);
            stu.setSex(sex);
            list.add(stu);
        }
        cursor.close();
        db.close();
        return list;
    }

}

         學生工具類,存放學生信息:

package com.example.studentsysten.db.domain;

public class Student {
    private String name;
    private String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", sex=" + sex + "]";
    }

}

          工程目錄結構:

              

          

            

          

 


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

-Advertisement-
Play Games
更多相關文章
  • /** * 獲取程式包名(本程式包名5.0版本上下都可獲取) * * @return */ public String getTaskPackname() { ActivityManager.RunningAppProcessInfo currentInfo = null; Field field  ...
  • 蘋果在iOS 10開放了系統電話許可權,全新的Callkit框架能夠讓音視頻的第三方應用獲得系統級的通話體驗,本次分享將主要介紹如何應用Callkit框架和一些適配經驗。 ...
  • 這裡是Android性能優化典範第6季的課程學習筆記,從被@知會到有連載更新,這篇學習筆記就一直被惦記著,現在學習記錄分享一下,請多多指教包涵!這次一共才6個小段落,涉及的內容主要有:程式啟動時間性能優化的三個方面:優化activity的創建過程,優化application對象的啟動過程,正確使用啟... ...
  • 在開發的過程中,關於對請求回調數據的處理以及消息提示,我發現了兩個問題: 1.別人都怎麼做的我不知道,但是我看到的,很多人在寫網路請求的時候,不管是自己直接寫的,或者還是直接使用第三方網路框架,在拿到數據的時候,一般都是自己根據返回的數據中,使用約定好的key去解析自己需要的數據,直接使用或者轉換成 ...
  • 1.分別創建assets文件夾和res/raw文件夾:(要註意的raw文件是在res下new,然後創建一個名字為raw的文件夾) 2.創建兩個txt文件,複製到asset和raw文件夾中: 3.實現的效果: 4.實現代碼: (1)佈局文件: 1 <?xml version="1.0" encodin ...
  • 新的股票繪製粗來啦,歡迎圍觀star的說(*^__^*) 嘻嘻…… 捏合功能也準備完善了 Github:https://github.com/yate1996/YYStock 長按分時圖+五檔圖 分時圖+五檔圖 長按分時圖 分時圖 K線圖 長按K線圖 非全屏嵌入 咦,發現UI好看但是功能好像有點不夠 ...
  • 1.紅點切換間距 2.顯示和隱藏按鈕 3.getViewTreeObserver() http://www.cnblogs.com/wangying222/p/5267923.html ...
  • 1.app功能:通過註冊登錄賬戶,擁有一個賬戶本,能夠將平時自己容易的忘記的賬戶記錄下來,並可以保持到雲端,不需要擔心數據丟失,只要登錄賬戶,便可獲取到自己的賬戶本。 2.實現的效果圖,如下: 以下界面分別為註冊界面、登錄界面、提交賬戶內容界面、賬戶列表界面、長按刪除賬戶信息、具體賬戶內容信息 3. ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...