一、前言
Android 在 Android 3.0(API 级别 11)中引入了 Fragment,主要目的是为大屏幕(如平板电脑)上更加动态和灵活的界面设计提供支持。由于平板电脑的屏幕尺寸远胜于手机屏幕尺寸,因而有更多空间可供组合和交换界面组件。
例如,新闻应用可以使用一个片段在左侧显示文章列表,使用另一个片段在右侧显示文章,两个片段并排显示在一个 Activity 中,每个片段都拥有自己的一套生命周期回调方法,并各自处理自己的用户输入事件。因此,用户无需使用一个 Activity 来选择文章,然后使用另一个 Activity 来阅读文章,而是可以在同一个 Activity 内选择文章并进行阅读,如下图中的平板电脑布局所示。
我们今天要实现的例子来自《第一行代码第三版》 Fragment 相关章节,只是原文是用 Kotlin 实现的,我用 Java 实现的。
二、实例展示
2.1、手机上效果
可以看到手机上新闻列表和新闻内容是在不同的界面的。
2.2、平板上效果
在平板上的话,左边是新闻列表,右边就是新闻内容界面了。我这里用的是夜神模拟器,将其调成平板模式就可以了。
三、主要实现步骤
3.1、新建新闻内容的布局文件 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" />
<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_alignParentStart
="true"
android
:background
="#000" />
</RelativeLayout
>
新闻内容的布局主要分为新闻标题和新闻内容两部分,并且需要设置布局不可见,因为再 pad 模式下,如果没有点击新闻列表,默认是不显示新闻内容的。
3.2、新建 NewsContentFragment 类
public class NewsContentFragment extends Fragment {
private LinearLayout contentLayout
;
private TextView newsTitle
;
private TextView newsContent
;
@Nullable
@Override
public View
onCreateView(@NonNull LayoutInflater inflater
, @Nullable ViewGroup container
, @Nullable Bundle savedInstanceState
) {
View view
= inflater
.inflate(R
.layout
.news_content_frag
, container
, false);
contentLayout
= view
.findViewById(R
.id
.contentLayout
);
newsTitle
= view
.findViewById(R
.id
.newsTitle
);
newsContent
= view
.findViewById(R
.id
.newsContent
);
return view
;
}
public void refresh(String title
, String content
) {
contentLayout
.setVisibility(View
.VISIBLE
);
newsTitle
.setText(title
);
newsContent
.setText(content
);
}
}
创建 NewsContentFragment 加载 news_content_frag.xml 布局,这样我们新闻内容的 Fragment 我们就创建好了。
3.3、新建新闻列表的布局文件 news_title_frag
<?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
>
我们这里显示新闻列表是用 RecyclerView 来实现的。
3.4、新建 NewsTitleFragment 类
public class NewsTitleFragment extends Fragment {
private boolean isTwoPane
= false;
private RecyclerView newsTitleRecyclerView
;
@Nullable
@Override
public View
onCreateView(@NonNull LayoutInflater inflater
, @Nullable ViewGroup container
, @Nullable Bundle savedInstanceState
) {
return inflater
.inflate(R
.layout
.news_title_frag
, container
, false);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState
) {
super.onActivityCreated(savedInstanceState
);
newsTitleRecyclerView
= getActivity().findViewById(R
.id
.newsTitleRecyclerView
);
if (getActivity().findViewById(R
.id
.newsContentLayout
) != null
) {
isTwoPane
= true;
}
LinearLayoutManager layoutManager
= new LinearLayoutManager(getActivity());
newsTitleRecyclerView
.setLayoutManager(layoutManager
);
NewsAdapter adapter
= new NewsAdapter(getNews(), getActivity());
newsTitleRecyclerView
.setAdapter(adapter
);
}
private List
<News> getNews() {
List
<News> newsList
= new ArrayList<>();
for (int i
= 1; i
<= 50; i
++) {
News news
= new News("This is news title " + i
, getRandomLengthString("This is news content" + i
+ "."));
newsList
.add(news
);
}
return newsList
;
}
private String
getRandomLengthString(String str
) {
int n
= new Random().nextInt(20) + 1;
StringBuilder sb
= new StringBuilder(str
);
for (int i
= 0; i
< n
; i
++) {
sb
.append(str
);
}
return sb
.toString();
}
class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private List
<News> newsList
;
private Context mContext
;
public NewsAdapter(List
<News> newsList
, Context mContext
) {
this.newsList
= newsList
;
this.mContext
= mContext
;
}
@NonNull
@Override
public NewsAdapter
.ViewHolder
onCreateViewHolder(@NonNull ViewGroup parent
, int viewType
) {
View view
= LayoutInflater
.from(mContext
).inflate(R
.layout
.news_item
, parent
, false);
final ViewHolder holder
= new ViewHolder(view
);
holder
.itemView
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v
) {
News news
= newsList
.get(holder
.getAdapterPosition());
if (isTwoPane
) {
NewsContentFragment fragment
= (NewsContentFragment
) getActivity().
getSupportFragmentManager().
findFragmentById(R
.id
.newsContentFrag
);
fragment
.refresh(news
.getTitle(), news
.getContent());
} else {
Intent intent
= new Intent(getActivity(), NewsContentActivity
.class);
intent
.putExtra("news_title", news
.getTitle());
intent
.putExtra("news_content", news
.getContent());
startActivity(intent
);
}
}
});
return holder
;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder
, int position
) {
News news
= newsList
.get(position
);
holder
.newsTitle
.setText(news
.getTitle());
}
@Override
public int getItemCount() {
return newsList
.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView newsTitle
;
public ViewHolder(@NonNull View itemView
) {
super(itemView
);
newsTitle
= itemView
.findViewById(R
.id
.newsTitle
);
}
}
}
}
这里把 RecyclerView 的适配器用内部类来实现了,并且写了一个随机生成新闻内容条数的方法。
3.5、新建适配 pad 的双页布局 activity_main
在跟 layout 同级的地方新建 layout-sw600dp 文件夹,这里的 sw 代表 smallwidth 的意思,当你的屏幕的绝对宽度大于 600dp 时,屏幕就会自动调用 layout-sw600dp 文件夹里面的布局,在这个文件夹下新建 activity_news,具体代码如下:
<?xml version
="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.zjgsu.fragmentdemo.news.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.zjgsu.fragmentdemo.news.NewsContentFragment"
android
:layout_width
="match_parent"
android
:layout_height
="match_parent" />
</FrameLayout
>
</LinearLayout
>
四、源码
源码已经上传至 github,大家直接下载就可以了。