You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:34:12