第九章:实战项目 - 新闻阅读器
项目概述
我们将开发一个新闻阅读应用,包含以下功能:
- 新闻列表展示
- 新闻详情页
- 收藏功能
- 夜间模式
- 缓存支持
项目结构
app/
├── data/
│ ├── api/ # 网络请求接口
│ ├── db/ # 本地数据库
│ ├── model/ # 数据模型
│ └── repository/ # 数据仓库
├── di/ # 依赖注入
├── ui/
│ ├── news/ # 新闻列表
│ ├── detail/ # 新闻详情
│ └── favorite/ # 收藏列表
└── utils/ # 工具类
数据模型定义
// 新闻数据模型
@Entity(tableName = "news")
data class News(
@PrimaryKey
val id: String,
val title: String,
val content: String,
val imageUrl: String,
val publishTime: Long,
var isFavorite: Boolean = false
)
// API 接口定义
interface NewsApi {
@GET("news/list")
suspend fun getNewsList(): List<News>
@GET("news/{id}")
suspend fun getNewsDetail(@Path("id") newsId: String): News
}
数据仓库实现
class NewsRepository @Inject constructor(
private val newsApi: NewsApi,
private val newsDao: NewsDao,
private val context: Context
) {
// 获取新闻列表,支持缓存
fun getNewsList() = flow {
// 先加载缓存
emit(Resource.Loading(newsDao.getAllNews()))
try {
// 请求网络数据
val remoteNews = newsApi.getNewsList()
// 更新缓存
newsDao.insertAll(remoteNews)
// 发送最新数据
emit(Resource.Success(newsDao.getAllNews()))
} catch (e: Exception) {
emit(Resource.Error("Failed to fetch news", newsDao.getAllNews()))
}
}
}
UI 实现
新闻列表页面
@AndroidEntryPoint
class NewsListFragment : Fragment() {
private val viewModel: NewsViewModel by viewModels()
private lateinit var binding: FragmentNewsListBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = NewsAdapter { news ->
// 导航到详情页
findNavController().navigate(
NewsListFragmentDirections.actionListToDetail(news.id)
)
}
binding.recyclerView.adapter = adapter
// 观察数据变化
viewModel.newsList.observe(viewLifecycleOwner) { state ->
when (state) {
is Resource.Success -> {
binding.progressBar.isVisible = false
adapter.submitList(state.data)
}
is Resource.Loading -> {
binding.progressBar.isVisible = true
}
is Resource.Error -> {
binding.progressBar.isVisible = false
// 显示错误提示
}
}
}
}
}
新闻列表项布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="@+id/newsImage"
android:layout_width="100dp"
android:layout_height="70dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/newsTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/newsImage"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/newsTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
通过这个实战项目,我们可以学习到:
- 如何合理组织项目结构
- 数据层、业务层、展示层的分离
- 各种 Android 组件的实际应用
- 界面布局的实现
- 网络请求与本地存储的结合
学习结语
从 Web 前端到 Android 开发的转型之路虽然充满挑战,但也充满机遇。通过这本教程,我们了解了从开发环境搭建、布局设计,到网络请求、数据存储等 Android 开发的核心概念。你会发现很多 Web 开发中的经验都能在 Android 开发中得到运用,比如组件化思维、状态管理、网络通信等。
作为开发者,保持学习的心态很重要。Android 技术生态在不断发展,建议你:
- 持续关注 Android 开发的最新动态
- 多阅读优秀的开源项目源码
- 在实践中不断总结和提升
记住,每个优秀的 Android 开发者都是从第一行代码开始的。希望这本教程能够帮助你在 Android 开发的道路上走得更远。