Fragment实践(一个简易版的新闻应用)
- 前提准备(view binding)
参考链接 - 创建FragmentBestPractice工程(build.gradle中添加)
android{
...
buildFeatures {
viewBinding true
}
}
dependencies {
...
implementation 'androidx.recyclerview:recyclerview:1.3.0'
}
- 新闻内容的布局(news_content_frag.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="invisible">
<TextView
android:id="@+id/newsTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp"
android:textSize="20sp" />
<TextView
android:id="@+id/newsContent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="15dp"
android:textSize="18sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black" />
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:background="@color/black" />
</RelativeLayout>
<!--新闻布局主要分为两部分,其中右边部分的标题和正文通过用view标签实现的细线分隔开,
整体的左边部分的细线是为了在双页模式时将左侧的新闻列表和新闻内容分隔开-->
<!--此外,要将新闻内容的布局通过 android:visibility="invisible" 设置为不可见,因为在双页模式下,
如果没有选中新闻列表中的任何一条新闻,就不应该显示新闻内容的布局-->
- 新闻实体类(News)
//title表示新闻标题,content表示新闻内容
class News(val title:String,val content: String) {
}
- 新闻内容的Fragment(NewsContentFragment)
class NewsContentFragment : Fragment() {
private var binding :NewsContentFragBinding ?=null
private val binding2 get()=binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding= NewsContentFragBinding.inflate(inflater,container,false)
return binding2.root
}
//通过refresh方法,将新闻内容显示到布局上
fun refresh(title:String,content:String){
binding2.contentLayout.visibility=View.VISIBLE //需要将刚才设置成隐藏的显示出来
binding2.newsTitle.text=title //刷新新闻的标题
binding2.newsContent.text=content //刷新新闻内容
}
}
- activity的新闻内容布局(activity_news_content)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/newsContentFrag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.fragmentbestpractice.NewsContentFragment"
tools:layout="@layout/news_content_frag" />
</LinearLayout>
- activity的新闻内容类(NewsContentFragment)
class NewsContentActivity : AppCompatActivity() {
companion object {
fun actionStart(context: Context, title: String, content: String) {
val intent = Intent(context, NewsContentActivity::class.java).apply {
putExtra("news_title", title)
putExtra("news_content", content)
}
context.startActivity(intent)
}
}
//编写actionStart方法的好处就是可以清晰知道需要传递的数据,
//以及简化了启动方法直接通过NewsContentActivity.actionStart()来启动即可
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_news_content)
val title = intent.getStringExtra("news_title")//获取传入的新闻标题
val content = intent.getStringExtra("news_content")//获取传入的新闻内容
if (title != null && content != null) {
val fragment =
supportFragmentManager.findFragmentById(R.id.newsContentFrag) as NewsContentFragment
fragment.refresh(title,content)
}
}
}
- 新闻列表布局(news_title_frag.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/newsTitleRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
- 新闻列表布局的子布局(news_item.xml)
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/newsTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textSize="18sp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
/>
<!--android:padding表示给控件的周围加上补白
android:maxLines设置为 1 表示这个TextView只能单行
android:ellipsize用于设定当文本内容超过文本宽度时文本的缩略方式 end 表示在尾部进行省略
-->
- 新闻列表的Fragment类(NewsTitleFragment)
@Suppress("DEPRECATION")
class NewsTitleFragment : Fragment() {
private var isTwoPane = false
private var binding: NewsTitleFragBinding? = null
private val binding2 get() = binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = NewsTitleFragBinding.inflate(inflater, container, false)
return binding2.root
}
@Deprecated("Deprecated in Java")
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
isTwoPane = activity?.findViewById<View>(R.id.newsContentLayout) != null
val layoutManager=LinearLayoutManager(activity)
binding2.newsTitleRecyclerView.layoutManager=layoutManager
val adapter=NewsAdapter(getNews())
binding2.newsTitleRecyclerView.adapter=adapter
}
private fun getNews():List<News>{
val newsList=ArrayList<News>()
for(i in 1..50){
val news=News("This is news title $i",getRandomLengthString("This is news content $i"))
newsList.add(news)
}
return newsList
}
private fun getRandomLengthString(str:String) :String{
val n=(1..20).random()
val builder=StringBuilder()
repeat(n){
builder.append(str)
}
return builder.toString()
}
inner class NewsAdapter(val newsList: List<News>) :
RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val newsTitle: TextView = view.findViewById(R.id.newsTitle)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.news_item, parent, false)
val holder = ViewHolder(view)
holder.itemView.setOnClickListener {
val news = newsList[holder.adapterPosition]
if (isTwoPane) {
val fragment =
fragmentManager?.findFragmentById(R.id.newsContentFrag) as NewsContentFragment
fragment.refresh(news.title, news.content)
} else {
println("---执行前---")
NewsContentActivity.actionStart(parent.context, news.title, news.content)
println("---执行后---")
}
}
return holder
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val news = newsList[position]
holder.newsTitle.text = news.title
}
override fun getItemCount() = newsList.size
}
}
- 手机模式下的主布局(activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/newsTitleLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/newsTitleFrag"
android:name="com.example.fragmentbestpractice.NewsTitleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/news_title_frag" />
</FrameLayout>
- 平板模式下的主布局(新建layout-sw600dp下的activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/newsTitleLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/newsTitleFrag"
android:name="com.example.fragmentbestpractice.NewsTitleFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
tools:layout="@layout/news_title_frag" />
<FrameLayout
android:id="@+id/newsContentLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/newsContentFrag"
android:name="com.example.fragmentbestpractice.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/news_content_frag" />
</FrameLayout>
</LinearLayout>
首先会报错Configurations for activity_main.xml must agree on the root element
上面的问题解决后在手机模式下会报空指针异常,平板模式下没有问题
原因并未找到,也希望各位大佬知道原因后评论区告知小弟
解决方法(仅实现功能)
- 最后我采用的方法是将activity_news_content.xml这个布局直接移除,可以看到里面的代码采用的就是news_content_frag.xml的代码
- 并修改了NewsContentActivity中的代码如下
class NewsContentActivity : AppCompatActivity() {
companion object {
fun actionStart(context: Context, title: String, content: String) {
val intent = Intent(context, NewsContentActivity::class.java).apply {
putExtra("news_title", title)
putExtra("news_content", content)
}
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//在此直接显示news_content_frag布局
val binding=NewsContentFragBinding.inflate(layoutInflater)
setContentView(binding.root)
val title = intent.getStringExtra("news_title")
val content = intent.getStringExtra("news_content")
if (title != null && content != null) {
//在此直接修改了布局中的内容,不再通过调用fragment中的refresh方法
binding.contentLayout.visibility=View.VISIBLE
binding.newsTitle.text=title
binding.newsContent.text=content
}
}
}
最后(找到错误)
- 关于这个小demo,平板模式下显示新闻内容,是直接在主activity布局中通过刷新新闻内容的fragment来实现内容的变更,而在手机模式下是通过intent切换到一个新的activity后再根据其布局中的fragment来实现内容的变更,大致思路其实是一致的,实在是想不明白是哪里空指针了
- 最终解决发现的问题还是出在了activity_news_content.xml这个布局文件里面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NewsContentActivity"
>
<!--<androidx.fragment.app.FragmentContainerView
android:id="@+id/newsContentFrag"
android:name="com.example.fragmentbestpractice.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/news_content_frag" />-->
<fragment
android:id="@+id/newsContentFrag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.fragmentbestpractice.NewsContentFragment"/>
</LinearLayout>
<!--书上使用的标签是fragment,但是编译器中使用此标签会报错,根据编译器的提示最终改为了以上写法,
修改过来即可解决报错问题-->
- 最后提醒两点,如果是通过class直接新建的activity别忘记在AndroidManifest.xml中注册!!!不要自作主张修改书上的代码!!!