• Android——数据存储(二)(二十二)


    1. SQLite数据库存储

    1.1 知识点

    (1)了解SQLite数据库的基本作用;

    (2)掌握数据库操作辅助类:SQLiteDatabase的使用;

    (3)可以使用命令操作SQLite数据库;

    (4)可以完成数据库的CRUD操作;

    (5)掌握数据库查询及Cursor接口的使用。

    1.2 具体内容

    在Android当中,本身提供了一种微型的嵌入式数据库叫做SQLite,同样可以执行SQL语句,对于不熟悉sql和jdbc的小伙伴,还是需要去自学一下。

    下面我们首先使用一段代码演示如何取得数据库表操作。

    1. package com.example.sqliteopenhelper;
    2. import android.content.Context;
    3. import android.database.sqlite.SQLiteDatabase;
    4. import android.database.sqlite.SQLiteDatabase.CursorFactory;
    5. import android.database.sqlite.SQLiteOpenHelper;
    6. public class MySQLiteOpenHelper extends SQLiteOpenHelper {
    7. public static final String DATABASENAME="wancy";//数据库名称
    8. public static final int DATABASEVERSION=1;//版本号
    9. public static final String TABLENAME = "DH10";//数据库表名
    10. public MySQLiteOpenHelper(Context context) {
    11. super(context, DATABASENAME, null, DATABASEVERSION);
    12. // TODO Auto-generated constructor stub
    13. }
    14. @Override
    15. public void onCreate(SQLiteDatabase db) {
    16. String sql = "create table "+TABLENAME+" (id INTEGER PRIMARY KEY,"+
    17. "name VARCHAR(50) NOT NULL,"+"birthday DATE NOT NULL)";
    18. db.execSQL(sql);
    19. }
    20. @Override
    21. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    22. String sql = "drop table if exists "+TABLENAME;//当退出的时候删除表
    23. db.execSQL(sql);
    24. this.onCreate(db);
    25. }
    26. }

    以上要注意一点,在SQLite当中如果想要让某个字段自动增长,那么在创建表的时候使用“INTEGER PRIMARY KEY”声明即可。

    那么这样的话一个数据库辅助类已经定义好了,那么我们下面在Activity当中去调用此类的方法。

    1. package com.example.sqliteproject;
    2. import com.example.sqliteopenhelper.MySQLiteOpenHelper;
    3. import android.os.Bundle;
    4. import android.app.Activity;
    5. import android.view.Menu;
    6. public class SQLiteActivity extends Activity {
    7. @Override
    8. protected void onCreate(Bundle savedInstanceState) {
    9. super.onCreate(savedInstanceState);
    10. setContentView(R.layout.activity_sqlite);
    11. MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this);
    12. helper.getWritableDatabase();//表示以读写的形式打开数据库
    13. }
    14. }

    现在已经取得了数据库的连接了,如果以上代码没有错误,运行之后就可以在文件列表当中找到数据表.FileExplorer/data/data/数据库辅助类所在的包/database。根据代码,可以发现,如果数据库不存在,那么会自动调用onCreate方法去创建表,如果数据库已经存在了,但是版本号发生了改变,将会调用onUpgrade方法,现将原来的表删除之后,在去创建新的表。

    ·使用SQLite数据库并完成数据库的更新操作。

    1. public void insert(String name,String birthday){
    2. SQLiteDatabase db = super.getWritableDatabase();//取得数据库操作对象
    3. String sql = "insert into "+TABLENAME+" (name,birthday) values ('"+name+"',"+birthday+")";
    4. db.execSQL(sql);
    5. db.close();
    6. }
    7. public void update(int id,String name,String birthday){
    8. SQLiteDatabase db = super.getWritableDatabase();//取得数据库操作对象
    9. String sql = "update "+TABLENAME+" set name='"+name+"',birthday="+birthday+" where id="+id;
    10. db.execSQL(sql);
    11. db.close();
    12. }
    13. public void delete(int id){
    14. SQLiteDatabase db = super.getWritableDatabase();//取得数据库操作对象
    15. String sql = "delete from "+TABLENAME+" where id="+id;
    16. db.execSQL(sql);
    17. db.close();
    18. }

    以上增删改三个方法写完之后,就可以在Activity当中去进行调用。

    1. package com.example.sqliteproject;
    2. import android.app.Activity;
    3. import android.os.Bundle;
    4. import android.view.View;
    5. import android.view.View.OnClickListener;
    6. import android.widget.Button;
    7. import android.widget.Toast;
    8. import com.example.sqliteopenhelper.MySQLiteOpenHelper;
    9. public class SQLiteActivity extends Activity {
    10. MySQLiteOpenHelper helper=null;
    11. Button insert,update,del = null;
    12. @Override
    13. protected void onCreate(Bundle savedInstanceState) {
    14. super.onCreate(savedInstanceState);
    15. setContentView(R.layout.activity_sqlite);
    16. insert=(Button) super.findViewById(R.id.insert);
    17. update = (Button) super.findViewById(R.id.update);
    18. del = (Button) super.findViewById(R.id.del);
    19. helper = new MySQLiteOpenHelper(this);
    20. helper.getWritableDatabase();//表示以读写的形式打开数据库
    21. insert.setOnClickListener(new OnClickListener() {
    22. @Override
    23. public void onClick(View v) {
    24. helper.insert("毛栗子", "1995-01-05");
    25. Toast.makeText(SQLiteActivity.this, "新增成功", 0).show();
    26. }
    27. });
    28. update.setOnClickListener(new OnClickListener() {
    29. @Override
    30. public void onClick(View v) {
    31. helper.update(1, "大白兔", "1995-01-05");
    32. Toast.makeText(SQLiteActivity.this, "修改成功", 0).show();
    33. }
    34. });
    35. del.setOnClickListener(new OnClickListener() {
    36. @Override
    37. public void onClick(View v) {
    38. helper.delete(1);
    39. Toast.makeText(SQLiteActivity.this, "删除成功", 0).show();
    40. }
    41. });
    42. }
    43. }

    以上的程序,所有数据都是固定的,这在实际开发当中显然是不科学的,因为我们至少也要使用编辑框进行数据的编辑才是符合实际,而且使用字符串拼凑sql这种形式也是存在问题的,那么我们可以使用占位符的形式来处理这种问题。

    1. public void insert(String name,String birthday){
    2. SQLiteDatabase db = super.getWritableDatabase();//取得数据库操作对象
    3. java.sql.Date date = null;
    4. try {
    5. date = new java.sql.Date(new SimpleDateFormat("yyyy-MM-dd").parse(birthday).getTime());
    6. } catch (ParseException e) {
    7. // TODO Auto-generated catch block
    8. e.printStackTrace();
    9. }
    10. String sql = "insert into "+TABLENAME+" (name,birthday) values (?,?)";//使用占位符
    11. Object args[] = new Object[]{name,birthday};
    12. db.execSQL(sql,args);
    13. db.close();
    14. }

    对于以上程序,只是换了另一种操作方法来解决拼凑的问题,功能上没有变化。

    对于拼凑sql和使用占位符,可以形成一个共识:

    肯定是使用占位符更好一些。

    在android当中,还对数据的封装提供了一个ContentValues类。 可以理解为一个Map集合,有key和value,唯一不同的是ContentValues中的key必须是字符串。以后的操作就可以直接通过key取得value。并将value设置到sql语句当中。

    1. public void insert(String name,String birthday){
    2. SQLiteDatabase db = super.getWritableDatabase();//取得数据库操作对象
    3. ContentValues cv = new ContentValues();
    4. cv.put("name", name);
    5. cv.put("birthday", birthday);
    6. db.insert(TABLENAME, null, cv);//进行新增操作
    7. db.close();
    8. }
    9. public void update(int id,String name,String birthday){
    10. SQLiteDatabase db = super.getWritableDatabase();//取得数据库操作对象
    11. ContentValues cv = new ContentValues();
    12. cv.put("name", name);
    13. cv.put("birthday", birthday);
    14. String whereClause = " id=?";
    15. String whereArgs[]= new String[]{String.valueOf(id)};
    16. db.update(TABLENAME, cv, whereClause, whereArgs);//更新操作
    17. db.close();
    18. }
    19. public void delete(int id){
    20. SQLiteDatabase db = super.getWritableDatabase();//取得数据库操作对象
    21. String whereClause = " id=?";
    22. String whereArgs[]= new String[]{String.valueOf(id)};
    23. db.delete(TABLENAME, whereClause, whereArgs);//删除操作
    24. db.close();
    25. }

    那么对于使用占位符和ContentValues进行数据库更新操作,两种方式并没有严格的优劣之分,但是对于不会sql的开发人员来说,使用ContentValues更好的。

    ·数据库查询机Cursor接口

    在JDBC中ResultSet接口不呢是的功能就是依靠一个next()方法一动指针向下取数据,是到了JDBC2.0之后才能够向上取数据,但是我们一般也不去用这种向上取的形式。

    Cursor接口,出现的比ResultSet晚,所有操作机制有些不同。

    1. package com.example.sqliteproject;
    2. import java.util.ArrayList;
    3. import java.util.List;
    4. import android.database.Cursor;
    5. import android.database.sqlite.SQLiteDatabase;
    6. public class DH10Cursor {
    7. private static final String TABLENAME="PR10";
    8. private SQLiteDatabase db =null;
    9. public DH10Cursor(SQLiteDatabase db) {
    10. super();
    11. this.db = db;
    12. }
    13. public List find(){
    14. List all = new ArrayList();
    15. String sql = "select id,name,birthday from "+TABLENAME;
    16. Cursor result = this.db.rawQuery(sql, null);//执行查询语句
    17. //采用循环的形式去结果集中的数据
    18. for(result.moveToFirst();!result.isAfterLast();result.moveToNext()){
    19. all.add(result.getInt(0)+"====="+result.getString(1)+"====="+result.getString(2));
    20. }
    21. result.close();
    22. db.close();
    23. return all;
    24. }
    25. }
    1. "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:orientation="vertical"
    6. android:id="@+id/mylayout"
    7. tools:context=".SQLiteActivity" >
    8. android:id="@+id/insert"
    9. android:layout_width="wrap_content"
    10. android:layout_height="wrap_content"
    11. android:text="新增"
    12. />
    13. android:id="@+id/update"
    14. android:layout_width="wrap_content"
    15. android:layout_height="wrap_content"
    16. android:text="修改"
    17. />
    18. android:id="@+id/del"
    19. android:layout_width="wrap_content"
    20. android:layout_height="wrap_content"
    21. android:text="删除"
    22. />
    23. android:id="@+id/query"
    24. android:layout_width="wrap_content"
    25. android:layout_height="wrap_content"
    26. android:text="查询"
    27. />
    1. package com.example.sqliteproject;
    2. import android.app.Activity;
    3. import android.os.Bundle;
    4. import android.view.View;
    5. import android.view.View.OnClickListener;
    6. import android.widget.ArrayAdapter;
    7. import android.widget.Button;
    8. import android.widget.LinearLayout;
    9. import android.widget.ListView;
    10. import android.widget.Toast;
    11. import com.example.sqliteopenhelper.MySQLiteOpenHelper;
    12. public class SQLiteActivity extends Activity {
    13. MySQLiteOpenHelper helper=null;
    14. Button insert,update,del,query = null;
    15. ListView listView = null;
    16. LinearLayout myLayout = null;
    17. @Override
    18. protected void onCreate(Bundle savedInstanceState) {
    19. super.onCreate(savedInstanceState);
    20. setContentView(R.layout.activity_sqlite);
    21. helper = new MySQLiteOpenHelper(this);
    22. insert=(Button) super.findViewById(R.id.insert);
    23. update = (Button) super.findViewById(R.id.update);
    24. del = (Button) super.findViewById(R.id.del);
    25. query = (Button) super.findViewById(R.id.query);
    26. myLayout = (LinearLayout) super.findViewById(R.id.mylayout);
    27. query.setOnClickListener(new OnClickListener() {
    28. @Override
    29. public void onClick(View v) {
    30. SQLiteActivity.this.listView = new ListView(SQLiteActivity.this);
    31. listView.setAdapter(new ArrayAdapter(SQLiteActivity.this, android.R.layout.simple_list_item_1,
    32. new DH10Cursor(SQLiteActivity.this.helper.getWritableDatabase()).find()));
    33. myLayout.addView(listView);
    34. }
    35. });
    36. insert.setOnClickListener(new OnClickListener() {
    37. @Override
    38. public void onClick(View v) {
    39. helper.insert("毛栗子", "1995-01-05");
    40. Toast.makeText(SQLiteActivity.this, "新增成功", 0).show();
    41. }
    42. });
    43. update.setOnClickListener(new OnClickListener() {
    44. @Override
    45. public void onClick(View v) {
    46. helper.update(1, "大白兔", "1995-01-05");
    47. Toast.makeText(SQLiteActivity.this, "修改成功", 0).show();
    48. }
    49. });
    50. del.setOnClickListener(new OnClickListener() {
    51. @Override
    52. public void onClick(View v) {
    53. helper.delete(1);
    54. Toast.makeText(SQLiteActivity.this, "删除成功", 0).show();
    55. }
    56. });
    57. }
    58. }

    需要注意一下Cursor的下标是从0开始的有别于ResultSet

    ·模糊查询

         ·使用sql语句完成模糊查询

    1. public List find(){
    2. List all = new ArrayList();
    3. String sql = "select id,name,birthday from "+TABLENAME+" where name like ?";
    4. String args[] = new String[]{"%栗%"};
    5. Cursor result = this.db.rawQuery(sql, args);//执行查询语句
    6. //采用循环的形式去结果集中的数据
    7. for(result.moveToFirst();!result.isAfterLast();result.moveToNext()){
    8. all.add(result.getInt(0)+"====="+result.getString(1)+"====="+result.getString(2));
    9. }
    10. result.close();
    11. db.close();
    12. return all;
    13. }

            ·使用query方法完成

    1. public List find(){
    2. List all = new ArrayList();
    3. //String sql = "select id,name,birthday from "+TABLENAME+" where name like ?";
    4. String selection = "name like ?";
    5. String args[] = new String[]{"%栗%"};
    6. String columns[] = new String[]{"id","name","birthday"};
    7. Cursor result = this.db.query(TABLENAME, columns, selection, args, null, null, null);//执行查询语句
    8. //采用循环的形式去结果集中的数据
    9. for(result.moveToFirst();!result.isAfterLast();result.moveToNext()){
    10. all.add(result.getInt(0)+"====="+result.getString(1)+"====="+result.getString(2));
    11. }
    12. result.close();
    13. db.close();
    14. return all;
    15. }

    既然模糊查询已经能够完成了,那么还有一种功能可以往下去实现,那就是数据的分页。如果想要实现数据的部分显示,这个语句和Mysql中的查询方式是非常相似,使用limit完成。下面我们进行一些简单的分页操作。

    1. public List find(){
    2. int currentPage = 1;//当前页码
    3. int lineSize = 5;//每页显示的数据量
    4. List all = new ArrayList();
    5. String sql = "select id,name,birthday from "+TABLENAME+" limit ?,?";
    6. String args[] = new String[]{String.valueOf((currentPage-1)*lineSize),String.valueOf(currentPage*lineSize)};
    7. Cursor result = this.db.rawQuery(sql, args);
    8. //采用循环的形式去结果集中的数据
    9. for(result.moveToFirst();!result.isAfterLast();result.moveToNext()){
    10. all.add(result.getInt(0)+"====="+result.getString(1)+"====="+result.getString(2));
    11. }
    12. result.close();
    13. db.close();
    14. return all;
    15. }

    ·使用ListView来实现滑动分页

      Android中有这样一个操作,只需要手指进行上下滑动,屏幕自动滚动,到达最后一条数据的时候,提示:请稍等,数据正在加载......。这个操作需要使用SimpleAdapter来完成,因为在这个适配器中有下面这样一个方法。

    pulibc void notifyDataSetChanged()

    表示如果在SimpleAdapter中的填充数据集合list内容一旦发生变化,就会立刻通知ListView进行及时新数据加载,当数据加载底部的时候,需要提示一个信息,而这个信息可以通过ListView组件里的方法去添加

    public void addFooterView(View v)

    现在还剩下一个问题,需要去监听滚动事件,需要使用如下接口:

    首先顶一个ListView的布局模板。

    1. "1.0" encoding="utf-8"?>
    2. "http://schemas.android.com/apk/res/android"
    3. android:id="@+id/mylayout"
    4. android:layout_width="match_parent"
    5. android:layout_height="match_parent" >
    6. android:id="@+id/id"
    7. android:layout_width="50px"
    8. android:layout_height="wrap_content"
    9. android:textSize="30px"
    10. />
    11. android:id="@+id/name"
    12. android:layout_width="120px"
    13. android:layout_height="wrap_content"
    14. android:textSize="30px"
    15. />
    16. android:id="@+id/birthday"
    17. android:layout_width="130px"
    18. android:layout_height="wrap_content"
    19. android:textSize="30px"
    20. />

    现在我们需进行查询操作,需要返回一共有多少笔数据,修改一下查询的类

    1. package com.example.sqliteproject;
    2. import java.util.ArrayList;
    3. import java.util.HashMap;
    4. import java.util.List;
    5. import java.util.Map;
    6. import android.database.Cursor;
    7. import android.database.sqlite.SQLiteDatabase;
    8. public class DH10Cursor {
    9. private static final String TABLENAME="DH10";
    10. private SQLiteDatabase db =null;
    11. public DH10Cursor(SQLiteDatabase db) {
    12. super();
    13. this.db = db;
    14. }
    15. public List> find(int currentPage,int lineSize){
    16. List> list = new ArrayList>();
    17. String sql = "select id,name,birthday from "+TABLENAME+" limit ?,?";
    18. String args[] = new String[]{String.valueOf((currentPage-1)*lineSize),String.valueOf(currentPage*lineSize)};
    19. Cursor result = this.db.rawQuery(sql, args);
    20. for(result.moveToFirst();!result.isAfterLast();result.moveToNext()){
    21. Map map = new HashMap();
    22. map.put("id", result.getInt(0));
    23. map.put("name", result.getString(1));
    24. map.put("birthday", result.getString(2));
    25. list.add(map);
    26. }
    27. return list;
    28. }
    29. public int getCount(){
    30. int count = 0;
    31. String sql = "select count(id) from "+TABLENAME;
    32. Cursor result = this.db.rawQuery(sql, null);
    33. for(result.moveToFirst();!result.isAfterLast();result.moveToNext()){
    34. count = result.getInt(0);//取得数据的总笔数
    35. }
    36. return count;
    37. }
    38. }

    下面写一下主布局文件,这个比较简单,直接放一个线性布局管理器就可以了。

    1. "1.0" encoding="utf-8"?>
    2. "http://schemas.android.com/apk/res/android"
    3. android:id="@+id/mainlayout"
    4. android:layout_width="match_parent"
    5. android:layout_height="match_parent"
    6. android:orientation="vertical" >

    现在只剩下Activity

    1. package com.example.sqliteproject;
    2. import java.util.List;
    3. import java.util.Map;
    4. import android.app.Activity;
    5. import android.os.Bundle;
    6. import android.view.Gravity;
    7. import android.widget.AbsListView;
    8. import android.widget.AbsListView.OnScrollListener;
    9. import android.widget.LinearLayout;
    10. import android.widget.LinearLayout.LayoutParams;
    11. import android.widget.ListView;
    12. import android.widget.SimpleAdapter;
    13. import android.widget.TextView;
    14. import com.example.sqliteopenhelper.MySQLiteOpenHelper;
    15. public class SQLiteActivity extends Activity {
    16. MySQLiteOpenHelper helper=null;
    17. ListView listView = null;
    18. LinearLayout mainLayout = null;
    19. int currentPage = 1;
    20. int lineSize = 5;
    21. int count = 0;
    22. int pageSize = 1;//总页数
    23. int lastItem = 0;//保存最后一个记录点
    24. SimpleAdapter adapter = null;
    25. TextView tv = null;//底部信息
    26. LinearLayout loadLayout = null;//底部提示框布局
    27. List> list = null;
    28. LayoutParams lp= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    29. @Override
    30. protected void onCreate(Bundle savedInstanceState) {
    31. super.onCreate(savedInstanceState);
    32. super.setContentView(R.layout.listview_main);
    33. this.mainLayout=(LinearLayout) super.findViewById(R.id.mainlayout);
    34. this.loadLayout=new LinearLayout(this);
    35. this.tv = new TextView(this);
    36. tv.setText("数据加载中,请稍后。。。");
    37. tv.setGravity(Gravity.CENTER);
    38. tv.setTextSize(30);
    39. loadLayout.addView(this.tv,this.lp);
    40. loadLayout.setGravity(Gravity.CENTER);
    41. this.showAllData();
    42. }
    43. private void showAllData(){
    44. this.helper = new MySQLiteOpenHelper(this);
    45. this.listView = new ListView(this);
    46. DH10Cursor cursor = new DH10Cursor(this.helper.getWritableDatabase());//获得一个查询操作对象
    47. this.count = cursor.getCount();//取得总数据笔数
    48. this.list = cursor.find(currentPage, lineSize);
    49. this.adapter = new SimpleAdapter(this, list, R.layout.listview_item_layout,
    50. new String[]{"id","name","birthday"},new int[]{R.id.id,R.id.name,R.id.birthday});//实例化适配器对象
    51. SQLiteActivity.this.listView.addFooterView(SQLiteActivity.this.loadLayout);
    52. this.listView.setAdapter(adapter);
    53. this.listView.setOnScrollListener(new OnScrollListener() {
    54. @Override
    55. public void onScrollStateChanged(AbsListView view, int scrollState) {
    56. if(SQLiteActivity.this.lastItem==SQLiteActivity.this.adapter.getCount()//表示当前记录已经在最底部
    57. &&SQLiteActivity.this.currentPagethis.pageSize//当前页要小于总页数
    58. &&scrollState==OnScrollListener.SCROLL_STATE_IDLE//屏幕不再滚动
    59. ){
    60. SQLiteActivity.this.currentPage++;
    61. SQLiteActivity.this.listView.setSelection(SQLiteActivity.this.lastItem);//设置显示位置
    62. SQLiteActivity.this.listView.addFooterView(SQLiteActivity.this.loadLayout);
    63. SQLiteActivity.this.appendData();
    64. }
    65. }
    66. @Override
    67. public void onScroll(AbsListView view, int firstVisibleItem,
    68. int visibleItemCount, int totalItemCount) {
    69. SQLiteActivity.this.lastItem = firstVisibleItem+visibleItemCount-1;
    70. }
    71. });
    72. mainLayout.addView(this.listView);
    73. if(this.count%this.lineSize==0){
    74. this.pageSize = this.count/this.lineSize;
    75. }else{
    76. this.pageSize = this.count/this.lineSize+1;
    77. }
    78. }
    79. private void appendData(){//更新数据方法
    80. DH10Cursor cursor = new DH10Cursor(this.helper.getWritableDatabase());
    81. List> newData = cursor.find(currentPage, lineSize);
    82. this.list.addAll(newData);
    83. this.adapter.notifyDataSetChanged();//适配器重新加载集合数据
    84. }
    85. }

    以上就是整个实现下滑屏幕实现分页加载数据的程序。

    事务的处理是针对数据库而已,以后的开发当中,只要针对增删改,都需要用事务进行处理。

    1. public void insert(String name,String birthday){
    2. SQLiteDatabase db = super.getWritableDatabase();//取得数据库操作对象
    3. db.beginTransaction();//开启事务
    4. try{
    5. ContentValues cv = new ContentValues();
    6. cv.put("name", name);
    7. cv.put("birthday", birthday);
    8. db.insert(TABLENAME, null, cv);//进行新增操作
    9. db.setTransactionSuccessful();//正确执行,否则回滚
    10. }catch(Exception e){
    11. db.endTransaction();//事务关闭
    12. db.close();
    13. }
    14. }

    1.3 小结

    (1)SQLite数据库是一个专门用于嵌入式设备的数据库;

    (2)SQLite支持SQL语句的操作;

    (3)可以使用SQLiteOpenHelper类完成数据库的操作;

    (4)所有的查询数据使用Cursor进行接收;

  • 相关阅读:
    几个实用的MySQL内置函数使用方法
    基于PHP实现微信客服欢迎语发送
    Flask框架——项目可安装化
    自动驾驶系列(六)——谈谈车载人机交互技术
    DQL where查询
    搭建Windows7 openssh服务器 jenkins slave 就可以自动起来了
    总结tab栏切换实现的方法,以及增加滚动实现tab栏切换的效果
    I Pa?sWorD
    【教育创新,智慧引领】璞公英教学平台精准引领个性化教育!
    我的AI存储实践及思考
  • 原文地址:https://blog.csdn.net/weixin_41830242/article/details/132717434