Android Room多对多关联:RecyclerView复选框实现网站收藏与取消
实现点击RecyclerView复选框添加/取消收藏的多对多关联方案
嘿,我来帮你搞定这个收藏功能的实现!首先咱们得把Room的多对多关联捋清楚,然后一步步实现复选框的点击逻辑。
1. 补全并修正表结构
多对多关联需要三张表:网站表、收藏夹表、以及关联两者的交叉表(用来记录哪些网站属于哪个收藏夹)。
网站表(WebSites)
你已经写了这个实体,保持不变:
@Entity public class WebSites { @PrimaryKey(autoGenerate = true) public int id; public String website_head; public WebSites(@NonNull String website_head) { this.website_head = website_head; } }
收藏夹表(Favorites)
补全这个实体,用来表示不同的收藏夹(比如“常用收藏”“工作收藏”):
@Entity public class Favorites { @PrimaryKey(autoGenerate = true) public int favoriteId; public String favoriteName; // 收藏夹名称 public Favorites(String favoriteName) { this.favoriteName = favoriteName; } }
交叉关联表(WebsiteFavoriteCrossRef)
这是多对多的核心,用来记录网站和收藏夹的关联关系:
@Entity(primaryKeys = {"websiteId", "favoriteId"}) public class WebsiteFavoriteCrossRef { public int websiteId; public int favoriteId; public WebsiteFavoriteCrossRef(int websiteId, int favoriteId) { this.websiteId = websiteId; this.favoriteId = favoriteId; } }
2. 创建关联数据类(用于查询关联数据)
我们需要创建数据类来让Room能查询到“某个收藏夹下的所有网站”或者“某个网站所属的所有收藏夹”:
收藏夹带网站列表
public class FavoriteWithWebsites { @Embedded public Favorites favorite; @Relation( parentColumn = "favoriteId", entityColumn = "id", associateBy = @Junction(WebsiteFavoriteCrossRef.class) ) public List<WebSites> websites; }
网站带所属收藏夹列表
public class WebsiteWithFavorites { @Embedded public WebSites website; @Relation( parentColumn = "id", entityColumn = "favoriteId", associateBy = @Junction(WebsiteFavoriteCrossRef.class) ) public List<Favorites> favorites; }
3. 编写Dao层接口
创建三个Dao接口,分别处理网站、收藏夹、关联关系的增删查:
WebSitesDao
@Dao public interface WebSitesDao { @Insert void insertWebsite(WebSites website); // 获取所有网站(如果需要实时更新,改成返回LiveData) @Query("SELECT * FROM WebSites") List<WebSites> getAllWebsites(); // 检查指定网站是否在目标收藏夹中 @Query("SELECT EXISTS(SELECT 1 FROM WebsiteFavoriteCrossRef WHERE websiteId = :websiteId AND favoriteId = :favoriteId)") boolean isWebsiteInFavorite(int websiteId, int favoriteId); }
FavoritesDao
@Dao public interface FavoritesDao { @Insert void insertFavorite(Favorites favorite); @Query("SELECT * FROM Favorites WHERE favoriteId = :favoriteId") Favorites getFavoriteById(int favoriteId); }
WebsiteFavoriteCrossRefDao
@Dao public interface WebsiteFavoriteCrossRefDao { @Insert void insertCrossRef(WebsiteFavoriteCrossRef crossRef); @Delete void deleteCrossRef(WebsiteFavoriteCrossRef crossRef); }
4. 创建Room数据库实例
把上面的实体和Dao关联起来,用单例模式创建数据库:
@Database(entities = {WebSites.class, Favorites.class, WebsiteFavoriteCrossRef.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { public abstract WebSitesDao webSitesDao(); public abstract FavoritesDao favoritesDao(); public abstract WebsiteFavoriteCrossRefDao crossRefDao(); // 单例实现,避免重复创建数据库实例 private static volatile AppDatabase INSTANCE; public static AppDatabase getInstance(Context context) { if (INSTANCE == null) { synchronized (AppDatabase.class) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "app_database") .build(); } } } return INSTANCE; } }
5. RecyclerView适配器实现复选框逻辑
在适配器中处理复选框的点击,完成添加/取消收藏的操作:
public class WebsiteAdapter extends RecyclerView.Adapter<WebsiteAdapter.WebsiteViewHolder> { private List<WebSites> websiteList; private AppDatabase db; private int targetFavoriteId; // 当前操作的收藏夹ID(比如默认收藏夹ID=1) public WebsiteAdapter(List<WebSites> websiteList, AppDatabase db, int targetFavoriteId) { this.websiteList = websiteList; this.db = db; this.targetFavoriteId = targetFavoriteId; } @NonNull @Override public WebsiteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_website, parent, false); return new WebsiteViewHolder(view); } @Override public void onBindViewHolder(@NonNull WebsiteViewHolder holder, int position) { WebSites website = websiteList.get(position); holder.tvWebsiteHead.setText(website.website_head); // 子线程查询数据库,设置复选框初始状态 new Thread(() -> { boolean isInFavorite = db.webSitesDao().isWebsiteInFavorite(website.id, targetFavoriteId); holder.itemView.post(() -> holder.cbFavorite.setChecked(isInFavorite)); }).start(); // 处理复选框点击事件 holder.cbFavorite.setOnCheckedChangeListener((buttonView, isChecked) -> { new Thread(() -> { WebsiteFavoriteCrossRef crossRef = new WebsiteFavoriteCrossRef(website.id, targetFavoriteId); if (isChecked) { // 添加收藏:插入关联记录 db.crossRefDao().insertCrossRef(crossRef); } else { // 取消收藏:删除关联记录 db.crossRefDao().deleteCrossRef(crossRef); } }).start(); }); } @Override public int getItemCount() { return websiteList.size(); } // 更新列表数据(配合LiveData使用) public void updateData(List<WebSites> newList) { websiteList = newList; notifyDataSetChanged(); } static class WebsiteViewHolder extends RecyclerView.ViewHolder { TextView tvWebsiteHead; CheckBox cbFavorite; public WebsiteViewHolder(@NonNull View itemView) { super(itemView); tvWebsiteHead = itemView.findViewById(R.id.tv_website_head); cbFavorite = itemView.findViewById(R.id.cb_favorite); } } }
6. 在Activity/Fragment中初始化并使用
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化数据库实例 AppDatabase db = AppDatabase.getInstance(this); // 先插入默认收藏夹(如果不存在) new Thread(() -> { if (db.favoritesDao().getFavoriteById(1) == null) { db.favoritesDao().insertFavorite(new Favorites("默认收藏")); } }).start(); // 获取网站列表并设置RecyclerView RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); WebsiteAdapter adapter = new WebsiteAdapter(new ArrayList<>(), db, 1); recyclerView.setAdapter(adapter); // 子线程加载网站数据 new Thread(() -> { List<WebSites> websites = db.webSitesDao().getAllWebsites(); runOnUiThread(() -> adapter.updateData(websites)); }).start(); } }
优化建议
- 用
LiveData替代普通List,实现数据变化时自动更新UI,避免手动调用updateData - 可以使用
Coroutine来简化子线程操作,代码更简洁易维护 - 如果是单用户单收藏夹场景,可以简化表结构,但多对多的设计扩展性更强,后续支持多收藏夹会很方便
内容的提问来源于stack exchange,提问作者Jogma
相关产品推荐
相关产品推荐

