为了解决平板和手机之间的视觉效果而产生,比如一些界面在手机上看起来非常美观,但在平板电脑上看上去就可能会有控件被过分拉长、元素之间空隙过大等情况。
碎片(Fragment)是一种可以嵌入在活动当中的UI片段,他能让程序更加合理和充分地利用大屏幕空间,因而在平板上应用得非常广泛。
在手机中排放一个新闻列表,点击一个标题时,就打开另一个界面显示新闻详情信息。

在平板中,则使用两个碎片,将新闻列表界面和新闻详情页面分别放在两个碎片中,屏幕也就可以被充分利用起来了。

首先创建一个平板模拟器

(1)新建左侧布局left_fragment.xml,右侧同样

(2)接着新建一个LeftFragment类,右侧同样,将button换成TextView
(3)修改activity_main.xml中的代码
(4)结果如下所示

上面已经学会了在布局文件中添加碎片的方法,我们可以在程序运行时动态地添加到活动中。根据具体情况来动态地添加碎片,可以将程序界面定制的更加多样化。
(1) 在上面的基础上,首先新建一个another_right_fragment.xml,并只是将上面的right_fragment.xml改变颜色
android:background=“#EED5B7”
(2) 新建一个AnthorRightFragment类,代码如下:
public class AnthorRightFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view =inflater.inflate(R.layout.another_right_fragment,container,false);
return view;
}
}
(3) 在活动.xml主页面中修改写文档部分,代码如下:
(4) 在FragmentLayout中添加内容,从而实现动态添加碎片的功能。修改MainActivity代码。
动态添加碎片分为五步:
1:创建待添加的碎片实例
2:获取 FragmentManager,并调用getSupportFragmentManager()
3:开启一个事务,并调用beginTransaction()开启
4:向容器添加或替换碎片,调用.replace()方法实现
5:commit()提交事务
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button =(Button) findViewById(R.id.button);//点击左边的按钮,右边响应
button.setOnClickListener(this);
replaceFragment(new RightFragment());
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.button:
replaceFragment(new AnthorRightFragment());
break;
default:
break;
}
}
private void replaceFragment(Fragment fragment){
FragmentManager fragmentManager =getSupportFragmentManager();
FragmentTransaction transaction =fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout,fragment);//向容器内添加或替换碎片
transaction.commit();//提交事务
}
}
(5)结果如图所示,点击Button后,会变为右图所示

按back键回到上一个碎片,修改MainActivity,添加一个 transaction.addToBackStack(null);
private void replaceFragment(Fragment fragment){
FragmentManager fragmentManager =getSupportFragmentManager();
FragmentTransaction transaction =fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout,fragment);//向容器内添加或替换碎片
transaction.addToBackStack(null);
transaction.commit();//提交事务
}
在活动中得到相应的碎片实例:调用FragmentManager的findFragmentById();方法
碎片中调用活动里的方法:MainActivity activity =(MainActivity)getActivity();
(1) 运行状态
(2) 暂停状态
(3) 停止状态:进入停止状态的碎片对用户是完全不可见的,有可能被系统回收
(4) 销毁状态

碎片的回调
onAttach().当碎片和活动建立关联的时候调用
onCreateView().为碎片创建视图(加载布局)时调用
onActivityCreated().确保与碎片相关联的活动已创建完毕时调用
onDestroyView().当与碎片相关联的视图被移除的时候调用
onDetach(). 当碎片和活动解除关联的时候调用
修改RightFragment中的代码,如图所示:
public static final String TAG = “RightFragment”;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
Log.d(TAG, "onAttach: 11");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate: ");
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart: ");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView: ");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
@Override
public void onDetach() {
super.onDetach();
Log.d(TAG, "onDetach: ");
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.d(TAG, "onCreateView: ");
View view =inflater.inflate(R.layout.right_fragment,container,false);
return view;
}
运行程序后,在打印日志的代码中能看到打印信息。

点击button按钮后,RightFragment就会进入销毁状态。

点击back键后,重新回到了运行状态。

再次按下Back退出程序,打印的信息如图所示。

平板显示两页内容,但手机只能显示一页内容,如何判断使用双页模式还是单页模式呢?
(1) 首先在activity_main.xml中,修改left页面的代码,让他充满整个父布局。
android:layout_width=“match_parent”
(2) 新建layout-large文件夹,并新建activity_main.xml。代码如下。
(3) 其中large就是一个限定符,那些屏幕被认为是large的设备就会自动加载layout-large文件夹下的布局,而屏幕小的设备则会加载layout下的布局。

Android常见限定符

large限定符解决了单双页的问题,那么large到底指多大呢?
可以在res目录下新建layout-sw600dp文件夹,并新建activity_main.xml布局。
这就是说,当程序运行屏幕宽度大于600dp时候,会自动加载这个布局。
(1) 首先在app/build.gradle中添加依赖库
implementation ‘androidx.recyclerview:recyclerview:1.2.1’
(2) 新建News类
public class News {
private String title; //新闻标题
private String content;//新闻内容
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
(3) 新建布局文件news_content_frag.xml,用于作为新闻内容的布局:
布局主要分为两部分,头部部分显示新闻标题,正文部分显示新闻内容,中间再使用一条细线(View)分隔开。
(4) 新建NewsContentFragment类,继承自Fragment。
在onCreateView()方法,加载了news_content_frag布局。 refresh()方法是用于将新闻的标题和内容显示在界面上的。
public class NewsContentFragment extends Fragment {
private View view;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.news_content_frag, container, false);
return view;
}
public void refresh(String newsTitle, String newsContent) {
View visibiltyLayout = view.findViewById(R.id.visibility_layout);
visibiltyLayout.setVisibility(View.VISIBLE);
TextView newsTitleText = (TextView) view.findViewById(R.id.news_title);
TextView newsContentText = (TextView) view.findViewById(R.id.news_content);
newsTitleText.setText(newsTitle);//刷新新闻标题
newsContentText.setText(newsContent);//刷新新闻内容
}
}
(5) 上面所示,都是在双页模式中使用的,如果想在单页模式上使用的话,需要再创建一个活动,NewsContentActivity类,并将布局名称指定成news_content.xml.
(6) 在NewsContentActivity类中,修改代码
public class NewsContentActivity extends AppCompatActivity {
public static void actionStart(Context context,String newsTitle,String newsContent){
Intent intent =new Intent(context,NewsContentActivity.class);
intent.putExtra("news_title",newsTitle);
intent.putExtra("news_content",newsContent);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_content);
String newsTitle = getIntent().getStringExtra("news_title");//获取传入的新闻标题
String newsContent =getIntent().getStringExtra("news_content");//获取传入的新闻内容
NewsContentFragment newsContentFragment =(NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
newsContentFragment.refresh(newsTitle,newsContent);//刷新newsContentFragment界面
}
}
(7) 创建新闻列表布局,新建news_title_frag.xml,里面只有一个用于显示新闻列表的RecyclerView。
android:layout_height=“match_parent”
android:id=“@+id/news_title_recycler_view”/>
(8) 新建news_item.xml作为recyclerView子项布局
(9) 新建NewsTitleFragment类,作为展示新闻列表的地方。
package com.example.fragmentbestpractice;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class NewsTitleFragment extends Fragment {
private boolean isTwoPane;
class NewsAdapter extends RecyclerView.Adapter {
private List mNewsList;
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
News news = mNewsList.get(holder.getAdapterPosition());
if (isTwoPane) {
NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
newsContentFragment.refresh(news.getTitle(), news.getContent());
} else {
NewsContentActivity.actionStart(getActivity(), news.getTitle(), news.getContent());
}
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
News news = mNewsList.get(position);
holder.newsTitleText.setText(news.getTitle());
}
@Override
public int getItemCount() {
return mNewsList.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView newsTitleText;
public ViewHolder(@NonNull View view) {
super(view);
newsTitleText = (TextView) view.findViewById(R.id.news_title);
}
}
public NewsAdapter(List newsList) {
mNewsList = newsList;
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.news_title_frag, container, false);
RecyclerView newsTitleRecyclerView = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
newsTitleRecyclerView.setLayoutManager(layoutManager);
NewsAdapter adapter = new NewsAdapter(getNews());
newsTitleRecyclerView.setAdapter(adapter);
return view;
}
private List getNews() {
List newsList = new ArrayList<>();
for (int i = 1; i <= 50; i++) {
News news = new News();
news.setTitle("title~~~~~~~" + i);
news.setContent(getRandomLengthContent("this is news content" + i + "."));
newsList.add(news);
}
return newsList;
}
private String getRandomLengthContent(String content) {
Random random = new Random();
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(content);
}
return builder.toString();
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.news_content_layout) != null) {
isTwoPane = true;
} else {
isTwoPane = false;
}
}
}
(10) 修改activity_main.xml中的代码
(11) 在layout-sw600dp文件夹中,新建一个activity_main.xml文件。
(12) 在NewsTitleFragment中新增适配器。
下图为手机新闻

下图为平板新闻
