BasicSample项目说明
2023-07-30 11:31 ttylinux 阅读(25) 评论(0) 编辑 收藏 举报整个示例项目,两个Fragment,ProductListFragment和ProductFragment,一个MainActivity。在MainActivity里面展示的是ProductListFragment,点击每个Item,
会进入相应的ProductFragment。
相关技术点说明:
LiveData应用:
LiveData的应用,当LiveData包装的数据发生变化的时候,更新List
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private void subscribeUi(LiveData<List<ProductEntity>> liveData) { // Update the list when the data changes liveData.observe(getViewLifecycleOwner(), myProducts -> { if (myProducts != null ) { mBinding.setIsLoading( false ); mProductAdapter.setProductList(myProducts); } else { mBinding.setIsLoading( true ); } // espresso does not know how to wait for data binding's loop so we execute changes // sync. mBinding.executePendingBindings(); }); } |
保存LiveData的状态数据,并且根据状态数据的变化,更新LiveData:
Transformations.switchMap和SavedStateHandle的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public ProductListViewModel( @NonNull Application application, @NonNull SavedStateHandle savedStateHandle) { super (application); mSavedStateHandler = savedStateHandle; mRepository = ((BasicApp) application).getRepository(); // Use the savedStateHandle.getLiveData() as the input to switchMap, // allowing us to recalculate what LiveData to get from the DataRepository // based on what query the user has entered //LiveData<String> query,数据发生变化,就会触发执行向数据库查询的动作,将查询结果添加到已有的LiveData中 mProducts = Transformations.switchMap( savedStateHandle.getLiveData( "QUERY" , null ), (Function<CharSequence, LiveData<List<ProductEntity>>>) query -> { if (TextUtils.isEmpty(query)) { return mRepository.getProducts(); } return mRepository.searchProducts( "*" + query + "*" ); }); } public void setQuery(CharSequence query) { // Save the user's query into the SavedStateHandle. // This ensures that we retain the value across process death // and is used as the input into the Transformations.switchMap above //保存在SavedStateHandler的数据,即使进程销毁重建都会存在 mSavedStateHandler.set(QUERY_KEY, query); } |
实际的数据获取实现,封装在DataRepository mRepository;中。
RecyclerView - DiffUtil 局部刷新
输入两个数据集合,计算两个数据集合的差异化,然后根据差异化结果更新列表。差异化的计算过程是在UI线程中进行的。
如果数据量巨大,可以在异步线程更新,使用AsyncListDiffer和ListAdapter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public void setProductList( final List<? extends Product> productList) { if (mProductList == null ) { mProductList = productList; notifyItemRangeInserted( 0 , productList.size()); } else { DiffUtil.DiffResult result = DiffUtil.calculateDiff( new DiffUtil.Callback() { @Override public int getOldListSize() { return mProductList.size(); } @Override public int getNewListSize() { return productList.size(); } @Override public boolean areItemsTheSame( int oldItemPosition, int newItemPosition) { return mProductList.get(oldItemPosition).getId() == productList.get(newItemPosition).getId(); } @Override public boolean areContentsTheSame( int oldItemPosition, int newItemPosition) { Product newProduct = productList.get(newItemPosition); Product oldProduct = mProductList.get(oldItemPosition); return newProduct.getId() == oldProduct.getId() && TextUtils.equals(newProduct.getDescription(), oldProduct.getDescription()) && TextUtils.equals(newProduct.getName(), oldProduct.getName()) && newProduct.getPrice() == oldProduct.getPrice(); } }); mProductList = productList; result.dispatchUpdatesTo( this ); } } |
回调函数方法的含义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public abstract static class Callback { /** * 旧数据 size */ public abstract int getOldListSize(); /** * 新数据 size */ public abstract int getNewListSize(); /** * DiffUtil 调用判断两个 itemview 对应的数据对象是否一样. 由于 DiffUtil 是对两个不同数据集合的对比, 所以比较对象引用肯定是不行的, 一般会使用 id 等具有唯一性的字段进行比较. * @param oldItemPosition 旧数据集合中的下标 * @param newItemPosition 新数据集合中的下标 * @return True 返回 true 即判断两个对象相等, 反之则是不同的两个对象. */ public abstract boolean areItemsTheSame( int oldItemPosition, int newItemPosition); /** * Diffutil 调用判断两个相同对象之间的数据是否不同. 此方法仅会在 areItemsTheSame() 返回 true 的情况下被调用. * * @param oldItemPosition 旧数据集合中的下标 * @param newItemPosition 新数据集合中用以替换旧数据集合数据项的下标 * @return True 返回 true 代表 两个对象的数据相同, 反之则有差别. */ public abstract boolean areContentsTheSame( int oldItemPosition, int newItemPosition); /** * 当 areItemsTheSame() 返回true areContentsTheSame() 返回 false 时, 此方法将被调用, 来完成局部刷新功能. */ @Nullable public Object getChangePayload( int oldItemPosition, int newItemPosition); } |
DataBinding,数据绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | <layout xmlns:android= "http://schemas.android.com/apk/res/android" xmlns:app= "http://schemas.android.com/apk/res-auto" > <data> <variable name= "product" type= "com.example.android.persistence.model.Product" /> <variable name= "callback" type= "com.example.android.persistence.ui.ProductClickCallback" /> </data> <androidx.cardview.widget.CardView android:layout_width= "match_parent" android:layout_height= "wrap_content" android:minHeight= "@dimen/product_item_min_height" android:onClick= "@{() -> callback.onClick(product)}" android:orientation= "horizontal" android:layout_marginStart= "@dimen/item_horizontal_margin" android:layout_marginEnd= "@dimen/item_horizontal_margin" app:cardUseCompatPadding= "true" > <RelativeLayout android:layout_marginStart= "@dimen/item_horizontal_margin" android:layout_marginEnd= "@dimen/item_horizontal_margin" android:layout_width= "match_parent" android:layout_height= "wrap_content" > <TextView android:id= "@+id/name" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:contentDescription= "@string/cd_product_name" android:text= "@{product.name}" /> <TextView android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:layout_alignParentEnd= "true" android:layout_marginEnd= "5dp" android:text= "@{@string/product_price(product.price)}" /> <TextView android:layout_width= "match_parent" android:layout_height= "wrap_content" android:layout_below= "@id/name" android:text= "@{product.description}" /> </RelativeLayout> </androidx.cardview.widget.CardView> </layout> |
实例化Binding,为Binding注入相应的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Override @NonNull public ProductViewHolder onCreateViewHolder( @NonNull ViewGroup parent, int viewType) { ProductItemBinding binding = DataBindingUtil .inflate(LayoutInflater.from(parent.getContext()), R.layout.product_item, parent, false ); binding.setCallback(mProductClickCallback); return new ProductViewHolder(binding); } @Override public void onBindViewHolder( @NonNull ProductViewHolder holder, int position) { holder.binding.setProduct(mProductList.get(position)); //立即刷新数据 holder.binding.executePendingBindings(); } |
=================================================================================================================================
数据层,创建三个数据表,ProductFtsEntity,ProductEntity,CommentEntity。然后声明Dao,通过Dao来获取数据库中的数据,与数据库交互。
Dao:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Dao public interface CommentDao { @Query ( "SELECT * FROM comments where productId = :productId" ) LiveData<List<CommentEntity>> loadComments( int productId); @Query ( "SELECT * FROM comments where productId = :productId" ) List<CommentEntity> loadCommentsSync( int productId); @Insert (onConflict = OnConflictStrategy.REPLACE) void insertAll(List<CommentEntity> comments); } |
应用@TypeConverter,在声明Entity的时候,使用属性类型不是基本类型时,是复杂的数据类型时,比如是Date类型时,需要
定义TypeConverter,这样数据库才知道如何存储该数据。
定义TypeConverter需要实现两个方法,一个是从基础数据获取复杂数据的方法,一个是复杂数据转换为基础数据的方法,比如对于类型Date:
1 2 3 4 5 6 7 8 9 10 11 12 | public class Converters { @TypeConverter public static Date fromTimestamp(Long value) { return value == null ? null : new Date(value); } @TypeConverter public static Long dateToTimestamp(Date date) { return date == null ? null : date.getTime(); } } |
数据库实例,设计为一个单例,使用单例模式:
单例模式,设计为线程安全的,首先检测是否为null,二次检测,加锁,判断是否为null,如果为null,
则在当前线程创建数据库实例。
1 2 3 4 5 6 7 8 9 10 11 | public static AppDatabase getInstance( final Context context, final AppExecutors executors) { if (sInstance == null ) { synchronized (AppDatabase. class ) { if (sInstance == null ) { sInstance = buildDatabase(context.getApplicationContext(), executors); sInstance.updateDatabaseCreated(context.getApplicationContext()); } } } return sInstance; } |
通过Room.databaseBuilder,builder模式构建数据库实例。
在构建的时候,添加回调方法,首次创建的时候,数据库创建成功,则在第三方线程为数据库插入数据,也就是在onCreate方法里面执行。
添加数据库升级策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * Build the database. {@link Builder#build()} only sets up the database configuration and * creates a new instance of the database. * The SQLite database is only created when it's accessed for the first time. */ private static AppDatabase buildDatabase( final Context appContext, final AppExecutors executors) { return Room.databaseBuilder(appContext, AppDatabase. class , DATABASE_NAME) .addCallback( new Callback() { @Override public void onCreate( @NonNull SupportSQLiteDatabase db) { super .onCreate(db); executors.diskIO().execute(() -> { // Add a delay to simulate a long-running operation addDelay(); // Generate the data for pre-population AppDatabase database = AppDatabase.getInstance(appContext, executors); List<ProductEntity> products = DataGenerator.generateProducts(); List<CommentEntity> comments = DataGenerator.generateCommentsForProducts(products); insertData(database, products, comments); // notify that the database was created and it's ready to be used database.setDatabaseCreated(); }); } }) .addMigrations(MIGRATION_1_2) .build(); } |
数据库升级策略,添加Migrations,如下,从版本1升级到版本2,执行如下SQL语句:
如果不存在productFts数据库表,则创建它;并且从products表查询数据来填充productFts数据表。
1 2 3 4 5 6 7 8 9 10 11 12 13 | private static final Migration MIGRATION_1_2 = new Migration( 1 , 2 ) { @Override public void migrate( @NonNull SupportSQLiteDatabase database) { database.execSQL( "CREATE VIRTUAL TABLE IF NOT EXISTS `productsFts` USING FTS4(" + "`name` TEXT, `description` TEXT, content=`products`)" ); database.execSQL( "INSERT INTO productsFts (`rowid`, `name`, `description`) " + "SELECT `id`, `name`, `description` FROM products" ); } }; |
版权声明:
作者:ttylinux
本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架