这里编写一个兼容手机和平板的实践程序。
首先在app/builtd.gradle中添加依赖:
- dependencies {
- implementation 'androidx.core:core-ktx:1.3.2'
- implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation 'com.google.android.material:material:1.3.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
- implementation 'androidx.recyclerview:recyclerview:1.2.0'
- testImplementation 'junit:junit:4.+'
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
- }
新建一个News类:
class News(val title: String, val content: String)
新建布局文件news_content_frag.xml:
- "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"/>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="#000"/>
-
- <TextView
- android:id="@+id/newsContent"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:padding="15dp"
- android:textSize="18sp"/>
-
- LinearLayout>
-
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:layout_alignParentLeft="true"
- android:background="#000"/>
-
- RelativeLayout>
上面的布局主要分为两个部分,头部部分显示新闻标题,正文部分显示新闻内容,中间使用一条水平方向的细线分割。然后还使用垂直方向的细线在双页模式时将左右两侧的新闻内容分割开。细线是通过View,并设置宽高和背景色实现。
并且在上面的内容中,新闻内容的布局是不可见的,因为在双页模式下,如果没有选中新闻列表中的任何一条新闻,就不应该显示新闻内容布局。
然后新建NewsContentFragment类:
- class NewsContentFragment:Fragment() {
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.news_content_frag, container, false)
- }
-
- fun refresh(title: String, content: String) {
- contentLayout.visibility = View.VISIBLE
- newsTitle.text = title
- newsContent.text = content
- }
- }
上面在onCreateView方法中加载了刚刚创建的布局,然后提供了refresh方法,用于显示标题和内容,不过显示之前首先应该将其布局设置为可见。
上面是双页模式下的布局,而如果要在单页模式下使用,就需要再创建一个NewsContentActivity,布局名称为activity_news_content,然后修改布局代码:
- "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">
-
- <fragment
- android:id="@+id/newsContentFrag"
- android:name="com.example.fragmentbestpractice.NewsContentFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- LinearLayout>
同时修改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)
- 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 = newsContentFrag as NewsContentFragment
- fragment.refresh(title, content)
- }
- }
- }
在上面的代码中,onCreate方法通过Intent获取到了传入的标题和内容,然后调用refresh方法,将该标题和内容传入,以显示其内容。
然后创建news_title_frag.xml布局以显示右侧的列表:
- "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>
既然是RecyclerView,就需要新建news_item布局作为RecyclerView子项的布局:
- "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"/>
上面的布局中只有一个TextView,其中android:ellipsize用于设定当文本内容超出控件宽度时文本的缩略方式,这里指定为end表示在尾部进行缩略。
然后新建NewsTitleFragment作为展示该列表的Fragment:
- class NewsTitleFragment:Fragment() {
- private var isTwoPane = false
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.news_title_frag, container, false)
- }
-
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
- isTwoPane = activity?.findViewById
(R.id.newsContentLayout) != null - }
- }
但很显然,之前的代码中并没有newsContentLayout这个id,并且该id和双页模式的判定有关,因此需要使该id的View只有在双页模式中才会出现。
这里需要借助之前提到的限定符。首先修改activity_main.xml中的代码:
- "1.0" encoding="utf-8"?>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/newsTitleLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <fragment
- android:id="@+id/newsTitleFrag"
- android:name="com.example.fragmentbestpractice.NewsTitleFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- FrameLayout>
上面的代码会在单页模式下只加载新闻标题的Fragment。
然后新建layout-sw600dp文件夹,在该文件夹下新建activity_main.xml:
- "1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <fragment
- android:id="@+id/newsTitleFrag"
- android:name="com.example.fragmentbestpractice.NewsTitleFragment"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1" />
-
- <FrameLayout
- android:id="@+id/newsContentLayout"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="3" >
-
- <fragment
- android:id="@+id/newsContentFrag"
- android:name="com.example.fragmentbestpractice.NewsContentFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
- FrameLayout>
-
- LinearLayout>
可以看出,在双页模式下存在两个Fragment,并将新闻内容放在一个FrageLayout布局下,而该布局id正是newsContentLayout,因此能找到该id就是双页模式。
之后就是在NewsTitleFragment中通过RecyclerView展示列表,同样修改NewsTitleFragment代码:
- class NewsTitleFragment:Fragment() {
- private var isTwoPane = false
-
- inner class NewsAdapter(val newsList: List
):RecyclerView.Adapter() { - inner class ViewHolder(view: View):RecyclerView.ViewHolder(view) {
- val newsTitle:TextView = view.findViewById(R.id.newsTitle)
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsAdapter.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.absoluteAdapterPosition]
- if(isTwoPane) {
- val fragment = newsContentFrag as NewsContentFragment
- fragment.refresh(news.title, news.content)
- } else {
- NewsContentActivity.actionStart(parent.context, news.title, news.content)
- }
- }
-
- return holder
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val news = newsList[position]
- holder.newsTitle.text = news.title
- }
-
- override fun getItemCount() = newsList.size
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.news_title_frag, container, false)
- }
-
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
- isTwoPane = activity?.findViewById
(R.id.newsContentLayout) != null - }
- }
上面只是为RecyclerView创建了适配器,不过这里是将适配器写为了内部类的形式,以便可以直接访问NewsTitleFragment中的变量。
而在onCreateViewHolder的点击事件注册中,首先获取News实例,然后判断双页还是单页模式,如果是单页模式,就启动新的Activity显示新闻内容,而如果是双页模式,则更新NewsContentFragment中的数据。
最后向RecyclerView中填充数据,修改NewsTitleFragment:
- class NewsTitleFragment:Fragment() {
- private var isTwoPane = false
-
- inner class NewsAdapter(val newsList: List
):RecyclerView.Adapter() { - inner class ViewHolder(view: View):RecyclerView.ViewHolder(view) {
- val newsTitle:TextView = view.findViewById(R.id.newsTitle)
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsAdapter.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.absoluteAdapterPosition]
- if(isTwoPane) {
- val fragment = newsContentFrag as NewsContentFragment
- fragment.refresh(news.title, news.content)
- } else {
- NewsContentActivity.actionStart(parent.context, news.title, news.content)
- }
- }
-
- return holder
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val news = newsList[position]
- holder.newsTitle.text = news.title
- }
-
- override fun getItemCount() = newsList.size
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.news_title_frag, container, false)
- }
-
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
- isTwoPane = activity?.findViewById
(R.id.newsContentLayout) != null -
- val layoutManager = LinearLayoutManager(activity)
- newsTitleRecyclerView.layoutManager = layoutManager
- val adapter = NewsAdapter(getNews())
- newsTitleRecyclerView.adapter = adapter
- }
-
- private fun getNews():List
{ - val newsList = ArrayList
() - 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()
- }
- }
上述代码只是生成了新闻内容。
运行程序结果为:
这样也就实现了手机和平板的效果适配。