Fragment 碎片
碎片的简单用法
新建一个左侧碎片布局 起名为left_fragment.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="button"/> </LinearLayout>
新建一个右侧碎片布局 起名为right_fragment.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#00ff00"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:textSize="20sp" android:text="这是右侧碎片布局"/> </LinearLayout>
新建一个LeftFragment类,并让他继承自Fragment 并重写onCreateView()方法
public class LeftFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.left_fragment,container,false); } } 在这里使用了inflate()方法将刚才定义的left_fragment布局动态加载进来。
新建一个RightFragment类,并让他继承自Fragment并重写onCreateView()方法
public class RightFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.right_fragment,container,false); } }
然后修改activity_main中的布局代码。
<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/left_fragment" android:name="cn.zbuter.fragmentdemo.LeftFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"/> <fragment android:id="@+id/right_fragment" android:name="cn.zbuter.fragmentdemo.RightFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout>
使用
动态添加碎片
其他步骤与上一个例子相同只需要在activity_main中修改将fragment修改成FrameLayout
<FrameLayout android:id="@+id/right_layout" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"
修改MainActivity中的代码
public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { replaceFragment(new RightFragment()); } }); } private void replaceFragment(Fragment fragment){ FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.right_layout,fragment); // 点击按钮后这个layout可见 FrameLayout frameLayout = (FrameLayout)findViewById(R.id.right_layout); frameLayout.setVisibility(View.VISIBLE); //提交事务 transaction.commit(); } }
总结:
动态添加碎片主要分为5步:
- 创建待添加的碎片实例
- 获取FragmentManager,在活动中可以直接通过getSupportFragment()方法得到
- 开启一个事物,通过beginTransaction()方法开启。
- 向容器内添加或替换碎片, 一般使用replace()方法实现, 需要传入容器的id和待添加的碎片实例
- 提交事务,条用commit()方法来完成。
在碎片中模拟返回栈
如果想使用back键返回到上一个碎片 只需要使用FragmentTransaction中提供的addToBackStack()方法 就可以用于将一个事物添加到返回栈中, 修改MainActivity中的replaceFragment代码如下:
private void replaceFragment(Fragment fragment){
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout,fragment);
transaction.addToBackStack(null);
// 点击按钮后这个layout可见
FrameLayout frameLayout = (FrameLayout)findViewById(R.id.right_layout);
frameLayout.setVisibility(View.VISIBLE);
//提交事务
transaction.commit();
}
碎片和活动之间进行通信
如果想要在活动中调用碎片的方法,或者在碎片中调用活动里面的方法就要使用FragmentManage中提供的类似于findViewById()的方法findFragmentById()这个方法专门用于从布局文件中获取碎片的实例。
LeftFragment leftFragment = (LeftFragment) getSupportFragmentManager().findFragmentById(R.id.left_fragment);
每个碎片都可以使用getActivity()方法来获取到活动本身。就是一个Context对象。
碎片的完整生命周期
添加一个碎片——onAttach()——onCreate()——onCreateView()——onActivityCreated()——onStart()——onResume()——碎片已激活
碎片被激活后,有两种情况:
- 用户点击返回键或者碎片被移除/替换——onPause()——onStop()——onDestoryView()——onDestory()——onDetach()——碎片被销毁
- 当碎片被添加到返回栈,然后被移除/替换——onPause()——onStop()——onDestoryView()——从返回栈中回到上一个碎片——重新执行onCreateView()方法
碎片的状态和回调
onAttach() 当碎片和活动建立关联的时候调用。
onCreateView() 当碎片创建视图(加载布局)时调用。
onActivityCreated() 确保与碎片相关联的活动一定已经创建完毕的时候调用。
onDestroyView() 当与碎片关联的视图被移除的时候调用。
onDetach() 当碎片和活动接触关联的时候调用。
使用限定符
判断程序应该使用单页模式还是双页模式需要借助 限定符来实现。
我们修改activity_main中的代码。将多余的代码都删除掉,只留下一个左侧碎片并让他不满整个父布局,
<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/left_fragment" android:name="cn.zbuter.fragmentdemo.LeftFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"/> </LinearLayout>
在res目录下新建layout-large文件夹 并在这个文件夹下新建一个布局 也叫activity_main.xml 代码如下:
<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/left_fragment" android:name="cn.zbuter.fragmentdemo.LeftFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"/> <fragment android:id="@+id/right_fragment" android:name="cn.zbuter.fragmentdemo.RightFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"/> </LinearLayout>
在layout/activity_main.xml中只包含了一个碎片 这就是单页模式
在layout-large/activity_main.xml中包含了两个碎片 即双页模式
其中large就是一个限定符,那些屏幕被认为是large的设备就会自动加载layout-large文件夹下的布局,而屏幕晓的设备还是会加载layout文件夹下的布局
然后把MainActivity中的replaceFragement()方法里面的代码注释掉,并运行程序 就可以看到 在平板上运行就是双页模式,在手机上运行就是单页模式。
限定符参考表
屏幕大小方面
small 代表提供给小屏幕设备的资源
normal 代表提供给中等屏幕设备的资源
large 代表提供给大屏幕设备的资源
xlarge 代表提供给超大屏幕设备的资源
屏幕特征
分辨率:
ldpi 代表提供给低分辨率设备的资源(120dpi以下)
mdpi 代表提供给中等分辨率设备的资源(120dpi-160dpi)
hdpi 代表提供给高分辨率设备的资源(160dpi-240dpi)
xhdpi 代表提供给超高分辨率设备的资源(240dpi-320dpi)
xxhdpi 代表提供给超超高分辨率设备的资源(320dpi-480dpi)
方向
land 提供给横屏设备的
port 提供给竖屏设备的资源
使用最小宽度限定符:
最小宽度限定符允许我们对屏幕的宽度制定一个最小值(以dp为单位),然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加在一个布局,屏幕宽度小于这个值的化就加载另一个布局。
在res目录下新建 layout-sw600dp 文件夹, 然后在这个文件夹下新建一个activity_main.xml 与 layout-large/activity_main的代码相同。
<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/left_fragment" android:name="cn.zbuter.fragmentdemo.LeftFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"/> <fragment android:id="@+id/right_fragment" android:name="cn.zbuter.fragmentdemo.RightFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"/> </LinearLayout>
这就意味这,当程序运行在屏幕宽度大于等于600dp的设备上时 会加载layout-sw600dp/activity_main布局 当程序运行在屏幕宽度小于600dp的设备上时则仍然加载默认的layout/activity_main布局。
碎片的小Demo ---- 一个简易新闻应用。
添加 RecyclerView 的依赖库 在app/build.gradle中添加
implementation 'com.android.support:recyclerview-v7:26.0.0-alpha1'
新建一个欣慰的实体类 News 代码如下
public class News { private String title; private String content; public News() { } public News(String title, String content) { this.title = title; this.content = 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; } }
新建布局文件 news_content_frag.xml 用于作为新闻的内容布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/visibility_layout" android:orientation="vertical" android:visibility="invisible" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/news_title" 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/news_content" android:layout_weight="1" android:padding="15dp" android:textSize="18sp" android:layout_width="match_parent" android:layout_height="0dp" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:layout_alignParentLeft="true" android:background="#000"/> </RelativeLayout>
新闻的布局主要可以分为两个部分, 头部分显示新闻标题,正文部分显示新闻内容,中间用一条细线隔开,
然后创建一个NewsContentFragment类 继承 Fragment
public class NewsContentFragment extends Fragment { private View view; @Nullable @Override public View onCreateView(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 visibilityLayout = view.findViewById(R.id.visibility_layout); visibilityLayout.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); //刷新新闻的内容 } }
在onCreateView()方法里加载了一个刚刚创建的news_content_frag布局,接下来由提供了一个refresh()方法 这个方法用于设置新闻标题和内容
这样我们就把新闻内容的碎片和布局都创建好了。但是他们都是在双页模式中使用的,我们如果要在单页模式中也使用的话,还需要再创建一个活动, 起名NewsContentActivity 然后修改activity_news_content.xml中的代码
<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/news_content_frament" android:name="cn.zbuter.newsdemo.NewsContentFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
在布局中引用NewsContentFragment,这样就详单与把news_content_frag布局的内容自动加了进来
修改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_tilte", newsTitle); intent.putExtra("news_content",newsContent); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_news_content); // 获取传入参数 String newsTitle = getIntent().getStringExtra("new_title"); String newsContent = getIntent().getStringExtra("news_content"); NewsContentFragment newsContentFragment = (NewsContentFragment)getSupportFragmentManager().findFragmentById(R.id.news_content_frament); newsContentFragment.refresh(newsTitle,newsContent); //刷新界面 } }
在onCreate()方法中通过Intent获取到了传入的新闻标题和新闻内容,然后调用FragmentManager的findFragmentById()方法得到了NewsContentFragment的实例,接着调用他的refresh()方法将新闻的标题和内容传入, 就可以吧数据先输出来了。
新建news_title_frag.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/news_title_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
新建news_item.xml
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/news_title" 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"/>
ellipsize 用于设定当文本内容超出控件宽度时,文本的缩略方式,这里的end是指在尾部进行缩略
修改activity_main中的代码:
<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/news_title_fragment" android:name="cn.zbuter.newsdemo.NewsTitleFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
在单页模式下,只会加载一个新闻标题的碎片
新建一个 layout-land/activity_main 文件来适配横屏模式
<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/news_title_fragment" android:name="cn.zbuter.newsdemo.NewsTitleFragment" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" /> <FrameLayout android:id="@+id/news_content_layout" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="3"> <fragment android:id="@+id/news_content_frament" android:name="cn.zbuter.newsdemo.NewsContentFragment" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout> </LinearLayout>
然后创建一个用于展示新闻列表的地方 新建一个NewsTitleFragment作为展示新闻列表的碎片,代码如下:
public class NewsTitleFragment extends Fragment { private boolean isTwoPane; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view ; view = inflater.inflate(R.layout.news_title_frag, container,false); return view; } public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if(getActivity().findViewById(R.id.news_content_layout) != null){ // flag isTwoPane = true; // 可以找到newscontent_layout布局时为双页模式。 }else{ isTwoPane = false; // 不能找到newscontent_layout布局为单页模式。 } } }
onActivityCreated()方法通过在活动中能否找到一个id为news_content_layout的View来判断当前是双页模式还是单页模式。
现在已经完成了绝大部分的工作, 但是还剩下一个最重要的一点就是在NewTitleFragement中通过RecyclerView将新闻列表战术出来。 我们在NewsTitleFragment中新建一个内部类NewsAdapter来作为RecyclerView的适配器
public class NewsTitleFragment extends Fragment { private boolean isTwoPane; class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{ private List<News> mNewsList class ViewHolder extends RecyclerView.ViewHolder{ TextView newsTitleText; public ViewHolder(View itemView) { super(itemView); newsTitleText = (TextView) itemView.findViewById(R.id.news_title); } } public NewsAdapter(List<News> newsList){ mNewsList = newsList; } @Override public ViewHolder onCreateViewHolder(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 = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_frament); newsContentFragment.refresh(news.getTitle(),news.getContent()); }else{ //如果是单页模式则直接启动NewsContentActivity NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent()); } } }); return holder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { News news = mNewsList.get(position); holder.newsTitleText.setText(news.getTitle()); } @Override public int getItemCount() { return mNewsList.size(); } } public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view ; view = inflater.inflate(R.layout.news_title_frag, container,false); return view; } public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if(getActivity().findViewById(R.id.news_content_layout) != null){ // flag isTwoPane = true; // 可以找到newscontent_layout布局时为双页模式。 }else{ isTwoPane = false; // 不能找到newscontent_layout布局为单页模式。 } } }
向RecyclerView中填充数据, 修改NewsTitleFragment中的代码:
public class NewsTitleFragment extends Fragment { private boolean isTwoPane; class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{ private List<News> mNewsList; class ViewHolder extends RecyclerView.ViewHolder{ TextView newsTitleText; public ViewHolder(View itemView) { super(itemView); newsTitleText = (TextView) itemView.findViewById(R.id.news_title); } } public NewsAdapter(List<News> newsList){ mNewsList = newsList; } @Override public ViewHolder onCreateViewHolder(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 = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_frament); newsContentFragment.refresh(news.getTitle(),news.getContent()); }else{ //如果是单页模式则直接启动NewsContentActivity NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent()); } } }); return holder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { News news = mNewsList.get(position); holder.newsTitleText.setText(news.getTitle()); } @Override public int getItemCount() { return mNewsList.size(); } } public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view ; view = inflater.inflate(R.layout.news_title_frag, container,false); RecyclerView newsTitleRecyclerView = view.findViewById(R.id.news_title_recycler_view); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity()); newsTitleRecyclerView.setLayoutManager(linearLayoutManager); NewsAdapter newsAdapter = new NewsAdapter(getNews()); newsTitleRecyclerView.setAdapter(newsAdapter); return view; } private List<News> getNews(){ List<News> newsList= new ArrayList<>(); for(int i=1; i<=50; i++){ News news = new News(); news.setTitle("这是新闻标题"+ i); news.setContent(getRandomLengthContent("这是新闻内容"+ 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(); } public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if(getActivity().findViewById(R.id.news_content_layout) != null){ // flag isTwoPane = true; // 可以找到newscontent_layout布局时为双页模式。 }else{ isTwoPane = false; // 不能找到newscontent_layout布局为单页模式。 } } }
这样就可以完成了。