当前位置: 首页 > news >正文

Android auncher3实现简单的负一屏功能

Android launcher3实现简单的负一屏功能

在这里插入图片描述

1.前言:

之前实现过Launcher3从凑提修改成单层,今天来讲解一下如何实现一个简单的负一屏功能,涉及的类如下,直接看代码。

2.NegativeScreenAdapter:

package com.example.negativescreendemo.adapter;import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;import com.example.negativescreendemo.ActionItem;
import com.example.negativescreendemo.CardItem;
import com.example.negativescreendemo.NewsItem;
import com.example.negativescreendemo.R;
import com.example.negativescreendemo.ScheduleItem;
import com.example.negativescreendemo.TodoItem;import java.util.List;/*** @author: njb* @date: 2025/8/20 21:19* @desc: 描述*/
public class NegativeScreenAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{private Context context;private List<CardItem> cardItems;private OnItemClickListener listener;// 卡片类型public enum CardType {ACTIONS, SCHEDULE, NEWS, TODO}public NegativeScreenAdapter(Context context, List<CardItem> cardItems) {this.context = context;this.cardItems = cardItems;}@NonNull@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {LayoutInflater inflater = LayoutInflater.from(context);switch (viewType) {case 0: // 快捷功能卡片View actionView = inflater.inflate(R.layout.item_action_card, parent, false);return new ActionCardViewHolder(actionView);case 1: // 日程卡片View scheduleView = inflater.inflate(R.layout.item_schedule_card, parent, false);return new ScheduleCardViewHolder(scheduleView);case 2: // 新闻卡片View newsView = inflater.inflate(R.layout.item_news_card, parent, false);return new NewsCardViewHolder(newsView);case 3: // 待办事项卡片View todoView = inflater.inflate(R.layout.item_todo_card, parent, false);return new TodoCardViewHolder(todoView);default:return null;}}@Overridepublic void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {CardItem item = cardItems.get(position);if (holder instanceof ActionCardViewHolder) {((ActionCardViewHolder) holder).title.setText(item.getTitle());ActionAdapter actionAdapter = new ActionAdapter(context, item.getActionItems());((ActionCardViewHolder) holder).recyclerView.setAdapter(actionAdapter);((ActionCardViewHolder) holder).recyclerView.setLayoutManager(new GridLayoutManager(context, 3));}else if (holder instanceof ScheduleCardViewHolder) {((ScheduleCardViewHolder) holder).title.setText(item.getTitle());ScheduleAdapter scheduleAdapter = new ScheduleAdapter(context, item.getScheduleItems());((ScheduleCardViewHolder) holder).recyclerView.setAdapter(scheduleAdapter);}else if (holder instanceof NewsCardViewHolder) {((NewsCardViewHolder) holder).title.setText(item.getTitle());NewsAdapter newsAdapter = new NewsAdapter(context, item.getNewsItems());((NewsCardViewHolder) holder).recyclerView.setAdapter(newsAdapter);}else if (holder instanceof TodoCardViewHolder) {((TodoCardViewHolder) holder).title.setText(item.getTitle());TodoAdapter todoAdapter = new TodoAdapter(context, item.getTodoItems());((TodoCardViewHolder) holder).recyclerView.setAdapter(todoAdapter);}// 设置点击事件holder.itemView.setOnClickListener(v -> {if (listener != null) {listener.onItemClick(position);}});}@Overridepublic int getItemCount() {return cardItems.size();}@Overridepublic int getItemViewType(int position) {CardType type = cardItems.get(position).getType();switch (type) {case ACTIONS: return 0;case SCHEDULE: return 1;case NEWS: return 2;case TODO: return 3;default: return 0;}}// 快捷功能卡片ViewHolderpublic static class ActionCardViewHolder extends RecyclerView.ViewHolder {TextView title;RecyclerView recyclerView;public ActionCardViewHolder(View itemView) {super(itemView);title = itemView.findViewById(R.id.card_title);recyclerView = itemView.findViewById(R.id.action_recycler);}}// 日程卡片ViewHolderpublic static class ScheduleCardViewHolder extends RecyclerView.ViewHolder {TextView title;RecyclerView recyclerView;public ScheduleCardViewHolder(View itemView) {super(itemView);title = itemView.findViewById(R.id.card_title);recyclerView = itemView.findViewById(R.id.schedule_recycler);}}// 新闻卡片ViewHolderpublic static class NewsCardViewHolder extends RecyclerView.ViewHolder {TextView title;RecyclerView recyclerView;public NewsCardViewHolder(View itemView) {super(itemView);title = itemView.findViewById(R.id.card_title);recyclerView = itemView.findViewById(R.id.news_recycler);}}// 待办事项卡片ViewHolderpublic static class TodoCardViewHolder extends RecyclerView.ViewHolder {TextView title;RecyclerView recyclerView;public TodoCardViewHolder(View itemView) {super(itemView);title = itemView.findViewById(R.id.card_title);recyclerView = itemView.findViewById(R.id.todo_recycler);}}// 快捷功能子项适配器public class ActionAdapter extends RecyclerView.Adapter<ActionAdapter.ViewHolder> {private Context context;private List<ActionItem> items;public ActionAdapter(Context context, List<ActionItem> items) {this.context = context;this.items = items;}@NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(context).inflate(R.layout.item_action, parent, false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {ActionItem item = items.get(position);holder.icon.setImageResource(item.getIconRes());holder.name.setText(item.getName());}@Overridepublic int getItemCount() {return items.size();}public class ViewHolder extends RecyclerView.ViewHolder {ImageView icon;TextView name;public ViewHolder(View itemView) {super(itemView);icon = itemView.findViewById(R.id.action_icon);name = itemView.findViewById(R.id.action_name);}}}// 日程子项适配器public class ScheduleAdapter extends RecyclerView.Adapter<ScheduleAdapter.ViewHolder> {private Context context;private List<ScheduleItem> items;public ScheduleAdapter(Context context, List<ScheduleItem> items) {this.context = context;this.items = items;}@NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(context).inflate(R.layout.item_schedule, parent, false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {ScheduleItem item = items.get(position);holder.time.setText(item.getTime());holder.title.setText(item.getTitle());holder.location.setText(item.getLocation());}@Overridepublic int getItemCount() {return items.size();}public class ViewHolder extends RecyclerView.ViewHolder {TextView time, title, location;public ViewHolder(View itemView) {super(itemView);time = itemView.findViewById(R.id.schedule_time);title = itemView.findViewById(R.id.schedule_title);location = itemView.findViewById(R.id.schedule_location);}}}// 新闻子项适配器public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {private Context context;private List<NewsItem> items;public NewsAdapter(Context context, List<NewsItem> items) {this.context = context;this.items = items;}@NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(context).inflate(R.layout.item_news, parent, false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {NewsItem item = items.get(position);holder.title.setText(item.getTitle());holder.source.setText(item.getSource());holder.time.setText(item.getTime());}@Overridepublic int getItemCount() {return items.size();}public class ViewHolder extends RecyclerView.ViewHolder {TextView title, source, time;public ViewHolder(View itemView) {super(itemView);title = itemView.findViewById(R.id.news_title);source = itemView.findViewById(R.id.news_source);time = itemView.findViewById(R.id.news_time);}}}// 待办事项子项适配器public class TodoAdapter extends RecyclerView.Adapter<TodoAdapter.ViewHolder> {private Context context;private List<TodoItem> items;public TodoAdapter(Context context, List<TodoItem> items) {this.context = context;this.items = items;}@NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(context).inflate(R.layout.item_todo, parent, false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {TodoItem item = items.get(position);holder.content.setText(item.getContent());holder.checkBox.setChecked(item.isCompleted());holder.checkBox.setOnCheckedChangeListener((buttonView, isChecked) ->item.setCompleted(isChecked));}@Overridepublic int getItemCount() {return items.size();}public class ViewHolder extends RecyclerView.ViewHolder {CheckBox checkBox;TextView content;public ViewHolder(View itemView) {super(itemView);checkBox = itemView.findViewById(R.id.todo_checkbox);content = itemView.findViewById(R.id.todo_content);}}}// 点击事件接口public interface OnItemClickListener {void onItemClick(int position);}public void setOnItemClickListener(OnItemClickListener listener) {this.listener = listener;}
}

3.ActionItem:

package com.example.negativescreendemo;/*** @author: njb* @date: 2025/8/20 21:28* @desc: 描述*/
public class ActionItem {private String name;private int iconRes;public ActionItem(String name, int iconRes) {this.name = name;this.iconRes = iconRes;}public String getName() {return name;}public int getIconRes() {return iconRes;}
}

4.卡片CardItem:

package com.example.negativescreendemo;import com.example.negativescreendemo.adapter.NegativeScreenAdapter;import java.util.List;/*** @author: njb* @date: 2025/8/20 21:21* @desc: 描述*/
public class CardItem {private NegativeScreenAdapter.CardType type;private String title;private List<ActionItem> actionItems;private List<ScheduleItem> scheduleItems;private List<NewsItem> newsItems;private List<TodoItem> todoItems;// 私有构造方法,防止直接实例化private CardItem() {}/*** 创建快捷操作卡片*/public static CardItem createActionCard(NegativeScreenAdapter.CardType type, String title, List<ActionItem> actionItems) {CardItem item = new CardItem();item.type = type;item.title = title;item.actionItems = actionItems;return item;}/*** 创建日程卡片*/public static CardItem createScheduleCard(NegativeScreenAdapter.CardType type, String title, List<ScheduleItem> scheduleItems) {CardItem item = new CardItem();item.type = type;item.title = title;item.scheduleItems = scheduleItems;return item;}/*** 创建新闻卡片*/public static CardItem createNewsCard(NegativeScreenAdapter.CardType type, String title, List<NewsItem> newsItems) {CardItem item = new CardItem();item.type = type;item.title = title;item.newsItems = newsItems;return item;}/*** 创建待办事项卡片*/public static CardItem createTodoCard(NegativeScreenAdapter.CardType type, String title, List<TodoItem> todoItems) {CardItem item = new CardItem();item.type = type;item.title = title;item.todoItems = todoItems;return item;}// Getterspublic NegativeScreenAdapter.CardType getType() {return type;}public String getTitle() {return title;}public List<ActionItem> getActionItems() {return actionItems;}public List<ScheduleItem> getScheduleItems() {return scheduleItems;}public List<NewsItem> getNewsItems() {return newsItems;}public List<TodoItem> getTodoItems() {return todoItems;}
}

5.NewItem:

package com.example.negativescreendemo;/*** @author: njb* @date: 2025/8/20 21:28* @desc: 描述*/
public class NewsItem {private String title;private String source;private String time;public NewsItem(String title, String source, String time) {this.title = title;this.source = source;this.time = time;}public String getTitle() {return title;}public String getSource() {return source;}public String getTime() {return time;}
}

6.ScheduleItem:

package com.example.negativescreendemo;/*** @author: njb* @date: 2025/8/20 21:28* @desc: 描述*/
public class ScheduleItem {private String time;private String title;private String location;public ScheduleItem(String time, String title, String location) {this.time = time;this.title = title;this.location = location;}public String getTime() {return time;}public String getTitle() {return title;}public String getLocation() {return location;}
}

7.TodoItem:

package com.example.negativescreendemo;/*** @author: njb* @date: 2025/8/20 21:28* @desc: 描述*/
public class TodoItem {private String content;private boolean isCompleted;public TodoItem(String content, boolean isCompleted) {this.content = content;this.isCompleted = isCompleted;}public String getContent() {return content;}public boolean isCompleted() {return isCompleted;}public void setCompleted(boolean completed) {isCompleted = completed;}
}

8.主界面布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/gray_50"tools:context=".NegativeScreenActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><!-- 顶部时间天气区域 --><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="16dp"android:orientation="vertical"><TextViewandroid:id="@+id/time_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="12:30"android:textColor="@color/black"android:textSize="48sp"android:textStyle="bold" /><TextViewandroid:id="@+id/date_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:text="星期一, 六月 15日"android:textColor="@color/gray_600"android:textSize="16sp" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="12dp"android:gravity="center"android:layout_gravity="center"android:orientation="horizontal"><ImageViewandroid:id="@+id/weather_icon"android:layout_width="24dp"android:layout_height="24dp"android:src="@drawable/ic_scan" /><TextViewandroid:id="@+id/weather_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="8dp"android:text="25°C 晴朗"android:textColor="@color/gray_600"android:textSize="16sp" /></LinearLayout></LinearLayout><!-- 卡片列表 --><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recycler_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingHorizontal="16dp"android:clipToPadding="false" /></LinearLayout>
</androidx.core.widget.NestedScrollView>

9.主界面测试代码:

package com.example.negativescreendemo;import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;import com.example.negativescreendemo.adapter.NegativeScreenAdapter;import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;/*** @author: njb* @date: 2025/8/20 21:18* @desc: 描述*/
public class NegativeScreenActivity extends AppCompatActivity {private RecyclerView recyclerView;private NegativeScreenAdapter adapter;private List<CardItem> cardItems;private TextView timeTextView;private TextView dateTextView;private TextView weatherTextView;private ImageView weatherIcon;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_negative_screen);// 初始化视图initViews();// 初始化数据initData();// 设置适配器adapter = new NegativeScreenAdapter(this, cardItems);recyclerView.setAdapter(adapter);recyclerView.setLayoutManager(new LinearLayoutManager(this));// 设置点击事件adapter.setOnItemClickListener(position -> {CardItem item = cardItems.get(position);Toast.makeText(NegativeScreenActivity.this,"点击了: " + item.getTitle(), Toast.LENGTH_SHORT).show();});// 更新时间updateTime();}private void initViews() {recyclerView = findViewById(R.id.recycler_view);timeTextView = findViewById(R.id.time_text);dateTextView = findViewById(R.id.date_text);weatherTextView = findViewById(R.id.weather_text);weatherIcon = findViewById(R.id.weather_icon);}private void initData() {// 模拟天气数据weatherTextView.setText("25°C 晴朗");// 初始化卡片数据cardItems = new ArrayList<>();// 添加快捷功能卡片List<ActionItem> actionItems = new ArrayList<>();actionItems.add(new ActionItem("扫一扫", R.drawable.ic_scan));actionItems.add(new ActionItem("付款", R.drawable.ic_pay));actionItems.add(new ActionItem("乘车码", R.drawable.ic_bus));actionItems.add(new ActionItem("健康码", R.drawable.ic_health));actionItems.add(new ActionItem("充电宝", R.drawable.ic_power_bank));actionItems.add(new ActionItem("更多", R.drawable.ic_more));cardItems.add(CardItem.createActionCard(NegativeScreenAdapter.CardType.ACTIONS, "快捷功能", actionItems));// 添加日程卡片List<ScheduleItem> scheduleItems = new ArrayList<>();scheduleItems.add(new ScheduleItem("9:30", "团队周会", "会议室A"));scheduleItems.add(new ScheduleItem("14:00", "客户拜访", "XX公司"));cardItems.add(CardItem.createScheduleCard(NegativeScreenAdapter.CardType.SCHEDULE, "今日日程", scheduleItems));// 添加新闻卡片List<NewsItem> newsItems = new ArrayList<>();newsItems.add(new NewsItem("最新科技突破:人工智能新进展", "科技日报", "10分钟前"));newsItems.add(new NewsItem("本地气温将持续升高,最高达35°C", "本地新闻", "30分钟前"));cardItems.add(CardItem.createNewsCard(NegativeScreenAdapter.CardType.NEWS, "热点新闻", newsItems));// 添加待办事项卡片List<TodoItem> todoItems = new ArrayList<>();todoItems.add(new TodoItem("完成项目报告", false));todoItems.add(new TodoItem("购买生日礼物", true));todoItems.add(new TodoItem("回复客户邮件", false));cardItems.add(CardItem.createTodoCard(NegativeScreenAdapter.CardType.TODO, "待办事项", todoItems));}private void updateTime() {// 更新时间和日期final Handler handler = new Handler(Looper.getMainLooper());Timer timer = new Timer();timer.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {handler.post(() -> {SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, MMMM d日", Locale.getDefault());String time = timeFormat.format(new Date());String date = dateFormat.format(new Date());timeTextView.setText(time);dateTextView.setText(date);});}}, 0, 60000); // 每分钟更新一次}
}

10.实现效果如下:

在这里插入图片描述

11.总结:

分层架构 + 模块化设计为核心,通过 “数据模型封装 - UI 适配渲染 - 界面逻辑控制” 三层结构,快速搭建 Launcher3 负一屏功能,核心是利用RecyclerView嵌套实现 “卡片容器 + 子项列表” 的灵活布局,同时保证界面交互与数据展示的解耦。

关键技术点

  • RecyclerView 多类型布局:通过getItemViewType()和不同ViewHolder,实现 4 类卡片的差异化展示,降低代码耦合。
  • RecyclerView 嵌套:外层RecyclerView(卡片列表)嵌套内层RecyclerView(卡片子项),结合GridLayoutManager(快捷功能 3 列)和LinearLayoutManager(其他卡片单列),满足不同布局需求。
  • 线程安全的 UI 更新:用Handler(Looper.getMainLooper())Timer的定时任务切换到主线程,避免子线程操作 UI 的异常。
  • 数据模型私有化构造CardItem通过私有构造 + 静态创建方法,强制规范卡片创建流程,避免错误数据赋值。

12.源码地址:

https://gitee.com/jackning_admin/negative-screen-demo

http://www.dtcms.com/a/343331.html

相关文章:

  • 基于YOLOv8-SEAttention与LLMs融合的农作物害虫智能诊断与防控决策系统
  • 运动数据采集如何帮助克里斯·凯尔飞跃迎面驶来的F1赛车
  • 基于IEEE-754浮点数格式的matlab仿真
  • Day24 目录遍历、双向链表、栈
  • Mac电脑 3D建模工具--犀牛Rhino
  • 【个人网络整理】NOIP / 省选 /NOI 知识点汇总
  • 视频孪生技术在城市政务数字化转型中的应用与价值探索
  • ES_映射
  • Nacos-10--认识Nacos中的Raft协议(Nacos强一致性的实现原理)
  • VirtualBox 安装 Ubuntu Server 系统及 Ubuntu 初始配置
  • 区块链联邦学习思路一
  • 14、软件实现与测试
  • 实践题:智能健康监测系统设计方案
  • centos下安装Nginx(搭建高可用集群)
  • 亚马逊产品排名提升策略:从传统运营到AI驱动的智能化突破
  • 《信任链:幽灵签名》
  • 近端策略优化 (PPO) 算法深度解析
  • 智能求职推荐系统
  • mfc140u.dll文件是什么?解决mfc140u.dll文件丢失的有效解决方法分享
  • 密码管理中明文密码与空密码的危害与预防
  • 民国悬爱网剧《春迟》腾讯独播,石雨晴担纲联合出品人与联合制片人
  • Redis 主从复制(重点理解流程和原理)
  • Windows下服务封装
  • mac电脑使用(windows转Mac用户)
  • Java多线程编程——基础篇
  • STM32输入捕获相位差测量技术详解(基于TIM1复位模式)
  • Nacos 深度指南:从入门到高可用集群部署
  • ES6 面试题及详细答案 80题 (01-05)-- 基础语法与变量声明
  • C++宏的高级用法与元编程技巧
  • 数据结构青铜到王者第一话---数据结构基本常识(2)