第二行代码学习笔记——第四章:手机平板要兼容——探究碎片

来源:互联网 发布:网络媒介素养 名词解释 编辑:程序博客网 时间:2024/05/25 19:57

本章要点

作为一名专业的Android开发人员,能够同时兼容手机和平板的开发时我们必须要做到的事情。


4.1 碎片是什么

碎片(Fragment)是一种可以嵌套在活动当中的UI片段,它能让程序更加合理和充分的利用大屏幕的控件。


4.2 碎片的使用方式

开始我们的碎片之旅,创建FragmentTest项目。

4.2.1 碎片的简易用法

最简单碎片,在一个活动中添加两个碎片,并让这两个碎片平分活动控件。

新建左碎片布局left_fragment.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">    <Button        android:id="@+id/btn"        android:text="Button"        android:layout_gravity="center_horizontal"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>

新建右侧碎片,right_fragment.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:background="#00ff00"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:textSize="20sp"        android:text="This is right fragment"/></LinearLayout>

新建LeftFragment类和RrightFragment类,并让它们继承自support-v4包下的Fragment,重写onCreateView()来加载布局。
LeftFragment,代码如下:

public class LeftFragment extends Fragment {    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        View view = LayoutInflater.from(getContext()).inflate(R.layout.left_fragment, container, false);        return view;    }}

RightFragment,代码如下:

public class RightFragment extends Fragment {    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        View view = LayoutInflater.from(getContext()).inflate(R.layout.right_fragment, container, false);        return view;    }}

修改activity_main.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:layout_width="match_parent"    android:layout_height="match_parent">    <fragment        android:id="@+id/left_fragment"        android:name="com.example.hjw.fragmenttest.LeftFragment"        android:layout_width="0dp"        android:layout_weight="1"        android:layout_height="match_parent"/>    <fragment        android:id="@+id/right_fragment"        android:name="com.example.hjw.fragmenttest.RightFragment"        android:layout_width="0dp"        android:layout_weight="1"        android:layout_height="match_parent"/></LinearLayout>

属性:
android:name 指明添加碎片的类名(包名+类名)。

运行效果:
tablet1

4.2.2 动态添加碎片

新建another_right_fragment.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:background="#ffff00"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView        android:textSize="20sp"        android:text="This is another right fragment"        android:layout_gravity="center_horizontal"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>

创建AnotherRightFragment类,代码如下:

public class AnotherRightFragment extends Fragment {    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        View view = LayoutInflater.from(getContext()).inflate(R.layout.another_right_fragment, container, false);        return view;    }}

修改activity_main.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:layout_width="match_parent"    android:layout_height="match_parent">    <fragment        android:id="@+id/left_fragment"        android:name="com.example.hjw.fragmenttest.LeftFragment"        android:layout_width="0dp"        android:layout_weight="1"        android:layout_height="match_parent"/>    <FrameLayout        android:id="@+id/right_layout"        android:layout_weight="1"        android:layout_width="0dp"        android:layout_height="match_parent"></FrameLayout></LinearLayout>

实现动态添加Fragment,修改MainActivity中的代码如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    private Button btn;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn= (Button) findViewById(R.id.btn);        btn.setOnClickListener(this);        replaceFragmetn(new RightFragment());    }    @Override    public void onClick(View v) {        switch (v.getId()){            case R.id.btn:                replaceFragmetn(new AnotherRightFragment());                break;            default:                break;z        }    }    public void replaceFragmetn(Fragment fragment){        FragmentManager fragmentManager=getSupportFragmentManager();        FragmentTransaction transaction=fragmentManager.beginTransaction();        transaction.replace(R.id.right_layout,fragment);        transaction.commit();    }}

动态添加布局5步骤:

  1. 创建待添加碎片的实例。
  2. 获取FragmentManager,在活动中直接通过调用getSupportFragmentManager()得到。
  3. 开启一个事务,通过beginTransaction()开启。
  4. 向容器添加或替换布局,一般使用replace()实现,需要传入容器的id和待添加布局的实例。
  5. 提交事务,commit()。

重启,点击一下按钮,效果图:
df

4.2.3 在碎片中模拟返回栈

模仿类似于返回栈的效果好,Back返回上一个碎片。
FragmentTransaction提供了addToBackStack()方法,一般传入null。修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    ......    public void replaceFragmetn(Fragment fragment){        FragmentManager fragmentManager=getSupportFragmentManager();        FragmentTransaction transaction=fragmentManager.beginTransaction();        transaction.replace(R.id.right_layout,fragment);        transaction.addToBackStack(null);        transaction.commit();    }}

重新运行程序,点击按钮将AnotherRightFragment添加到活动中,按下Back键,会RightFragment,再按下Back键,RightFragment也会消失,再按下Back键,程序才会退出。

4.2.4 碎片与活动之间进行通信

活动中调用碎片的方法,FragmentManager提供了一个类似于findFragmentById()的方法,从布局中获取碎片的实例,代码如下:

RightFragment rightFragment = (RightFragment) getSupportFragmentManager().findFragmentById(R.id.right_fragment);

碎片中调用活动的方法,调用getActivity()方法来得到和当前碎片相关联的活动,代码如下:

MainActivity activity = (MainActivity) getActivity();

这样,碎片就可以调用活动中的方法了,当碎片需要使用Context对象时,也可以使用getActivity()方法,因为获取到的活动本身就是一个Context对象。


4.3 碎片的生命周期

碎片自己的生命周期。

4.3.1 碎片的状态和回调

碎片和活动一样,生命周期会有4中状态:

  1. 运行状态
    当一个碎片时可见的,并且它关联的活动正处于运行状态时,该对片也处于运行状态。
  2. 暂停状态
    当一个活动进入暂停状态时,与它相关联的可见碎片就会进入到暂停状态。
  3. 停止状态
    当一个活动进入停止状态时,与它相关联的碎片就会进入停止状态,或调用FragmentTransaction的revome(),replace()将碎片从活动中移除, 如果在提交事务之前调用addToBackStack()方法,碎片也会进入停止状态。
  4. 销毁状态
    碎片总是依附于活动而存在的,当活动销毁时,与它关联的碎片也会销毁状态。或调用FragmentTransaction的revome(),replace()将碎片从活动中移除, 如果在提交事务之前并没有调用addToBackStack()方法,这时的碎片就会进入销毁状态。

活动中的回调方法,碎片几乎都有,碎片还提供了一些附加的回调方法:

  • onAttach()。 碎片和活动建立关联的时候调用。
  • onCreateView()。 碎片创建视图(加载布局)时调用。
  • onActivityCreated()。 确保与碎片相关联的活动一定已经创建完毕调用。
  • onDestroyView()。 当与碎片相关联的视图被移除的时候调用。
  • onDetach()。 当碎片与活动解除关联的时候调用。

碎片完整的生命周期图:
fsmzq


4.3.2 体验碎片的生命周期

修改RightFragment中的代码,如下:

public class RightFragment extends Fragment {    private static final String TAG = "RightFragment";    @Override    public void onAttach(Context context) {        super.onAttach(context);        Log.d(TAG, "onAttach: ");    }    @Override    public void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Log.d(TAG, "onCreate: ");    }    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        Log.d(TAG, "onCreateView: ");        View view = LayoutInflater.from(getContext()).inflate(R.layout.right_fragment, container, false);        MainActivity activity = (MainActivity) getActivity();        return view;    }    @Override    public void onActivityCreated(@Nullable Bundle savedInstanceState) {        super.onActivityCreated(savedInstanceState);        Log.d(TAG, "onActivityCreated: ");    }    @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: ");    }}

运行程序,观察logcat打印信息:

ycsmzq

点击LeftFragment中的按钮,观察logcat打印信息:
lb

按下Back键,RightFragment重新回到运行状态,观察logcat打印信息:
b1f

再次按下Back键,观察logcat打印信息:
b2


4.4 动态加载布局的技巧

Android中动态加载布局的计技巧。

4.4.1 使用限定符

判断程序应该是单页还是双页模式:限定符(Qualifiers)。
修改FragmentTest项目中的activity.main.xml文件,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <fragment        android:id="@+id/left_fragment"        android:name="com.example.hjw.fragmenttest.LeftFragment"        android:layout_width="match_parent"        android:layout_height="match_parent"/></LinearLayout>

在res目录下layout_large文件夹,新建activity.main.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <fragment        android:id="@+id/left_fragment"        android:name="com.example.hjw.fragmenttest.LeftFragment"        android:layout_width="0dp"        android:layout_weight="1"        android:layout_height="match_parent"/>    <fragment        android:id="@+id/right_fragment"        android:name="com.example.hjw.fragmenttest.RightFragment"        android:layout_width="0dp"        android:layout_weight="3"        android:layout_height="match_parent"/></LinearLayout>

large是一个限定符,修改Macitivty中的代码:replaceFragment()代码注掉。运行平板效果如下:
xgt

在启动一个手机模拟器,运行效果如下:
sjxgt

限定符的参数:
这里写图片描述
a

4.4.2 使用最小宽度限定符

最小限定符(Smallest-width-Qualifier)

在res目录新建layout-sw600dp文件夹,新建activity_main.xml中的文件:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><fragment    android:id="@+id/left_fragment"    android:name="com.example.hjw.fragmenttest.LeftFragment"    android:layout_width="0dp"    android:layout_weight="1"    android:layout_height="match_parent"/><fragment    android:id="@+id/right_fragment"    android:name="com.example.hjw.fragmenttest.RightFragment"    android:layout_width="0dp"    android:layout_weight="3"    android:layout_height="match_parent"/></LinearLayout>

屏幕宽度大于600dp,会加载layout-600dp中的布局,小于600加载默认布局。


4.5 碎片的最佳实践——一个简易版的新闻应用

新闻应用

新建FragmentBestPractice项目。
添加依赖:

compile 'com.android.support:recyclerview-v7:24.2.1'

新建News实体类:

public class News {    private String title;    private String content;    public void setTitle(String title) {        this.title = title;    }    public void setContent(String content) {        this.content = content;    }    public String getTitle() {        return title;    }    public String getContent() {        return content;    }}

新建布局文件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/visibility_layout"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:orientation="vertical">        <TextView            android:id="@+id/tv_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/tv_news_content"            android:layout_width="match_parent"            android:layout_height="0dp"            android:layout_weight="1" />    </LinearLayout>    <View        android:layout_width="1dp"        android:layout_height="match_parent"        android:layout_alignParentLeft="true"        android:background="#000" /></RelativeLayout>

新建NewsContentFragment类加载news_content_frag布局,继承自Fragment,代码如下:

public class NewsContentFragment extends Fragment {    private View view;    private TextView tv_news_title,tv_news_content;    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        view = LayoutInflater.from(getContext()).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);        tv_news_title= (TextView) view.findViewById(R.id.tv_news_title);        tv_news_content= (TextView) view.findViewById(R.id.tv_news_content);        tv_news_title.setText(newsTitle);  //刷新新闻的标题        tv_news_content.setText(newsContent); //刷新新闻的头部    }}

单页模式使用,创建NewsContentActivity,指定布局名为news_content,引入NewsContentFragment布局,修改布局文件如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <fragment        android:id="@+id/news_content_fragment"        android:name="com.example.hjw.fragmentbestpractice.NewsContentFragment"        android:layout_width="match_parent"        android:layout_height="match_parent"/></LinearLayout>

修改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);        Intent intent = getIntent();        String news_title = intent.getStringExtra("news_title"); //获取新闻的标题        String news_content = intent.getStringExtra("news_content"); //获取新闻的内容        NewsContentFragment newsContentFragment= (NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);        newsContentFragment.refresh(news_title,news_content); //刷新NewsContentFragment界面    }}

新建news_title_frag.xml布局,用于显示新闻的标题列表的布局,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <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作为RecyclerView中子项的布局,代码如下:

<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/tv_news_title"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:ellipsize="end"    android:paddingBottom="15dp"    android:paddingLeft="10dp"    android:paddingRight="10dp"    android:paddingTop="15dp"    android:singleLine="true"    android:textSize="18sp" />

新建NewsTitleFragment作为展示新闻列表的碎片,onCreateView加载news_title_frag.xml布局,代码如下:

public class NewsTitleFragment extends Fragment{    private boolean isTowPane;    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        View view = LayoutInflater.from(getContext()).inflate(R.layout.news_title_frag, container, false);        return view;    }    @Override    public void onActivityCreated(@Nullable Bundle savedInstanceState) {        super.onActivityCreated(savedInstanceState);        if (getActivity().findViewById(R.id.news_content_layout)!=null){            isTowPane=true;  //可以找到news_content_layout布局,为双页        }else{            isTowPane=false; //找不到news_content_layout布局,为单页        }}

接下来我们修改main_activity.xml,单页模式中的代码如下:

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/news_title_layout"    android:layout_width="match_parent"    android:layout_height="match_parent">    <fragment        android:id="@+id/news_title_fragment"        android:name="com.example.hjw.fragmentbestpractice.NewsTitleFragment"        android:layout_width="match_parent"        android:layout_height="match_parent"/></FrameLayout>

新建layout-600dp文件夹,新建main_activity.xml布局,双页模式代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="horizontal">    <fragment        android:id="@+id/news_title_fragment"        android:name="com.example.hjw.fragmentbestpractice.NewsTitleFragment"        android:layout_width="0dp"        android:layout_height="match_parent"        android:layout_weight="1" />    <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_fragment"            android:name="com.example.hjw.fragmentbestpractice.NewsContentFragment"            android:layout_width="match_parent"            android:layout_height="match_parent" />    </FrameLayout></LinearLayout>

接下来在NewsTitleFragment中通过RecyclerView展示新闻列表,在NewsTitleFragment中新建内部类NewsAdapter作为RecyclerView的适配器,代码如下所示:

public class NewsTitleFragment extends Fragment{    private boolean isTowPane;    ......    class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{        private List<News> mData;        public NewsAdapter(List<News> mData) {            this.mData = mData;        }        @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 = mData.get(holder.getAdapterPosition());                    if (isTowPane){                        //如果是双页,刷新NewsContentFragment中的内容                        NewsContentFragment newsContentFragment= (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);                        newsContentFragment.refresh(news.getTitle(),news.getContent());                    }else{                        //如果是单页,直接启动NewsContentActivity                        NewsContentActivity.actionStart(getContext(),news.getTitle(),news.getContent());                    }                }            });            return holder;        }        @Override        public void onBindViewHolder(ViewHolder holder, int position) {            News news = mData.get(position);            holder.tv_news_title.setText(news.getTitle());        }        @Override        public int getItemCount() {            return mData.size();        }        class ViewHolder extends RecyclerView.ViewHolder{            TextView tv_news_title;            public ViewHolder(View itemView) {                super(itemView);                tv_news_title= (TextView) itemView.findViewById(R.id.tv_news_title);            }        }    }}

通过onCreateViewHolder方法中注册的点击事件,获取到点击项News的实例,通过isTwoPane判断是单页还是双页,更新里面的数据,修改NewsTitleFragment中的代码如下:

public class NewsTitleFragment extends Fragment{    private boolean isTowPane;    private RecyclerView news_title_recycler_view;    List<News> newsList=new ArrayList<>();    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        View view = LayoutInflater.from(getContext()).inflate(R.layout.news_title_frag, container, false);        news_title_recycler_view = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);        LinearLayoutManager layoutManager=new LinearLayoutManager(getContext());        news_title_recycler_view.setLayoutManager(layoutManager);        NewsAdapter adapter = new NewsAdapter(getNews());        news_title_recycler_view.setAdapter(adapter);        return view;    }    public List<News> getNews() {        for (int i = 1; i <= 50; i++) {            News news=new News();            news.setTitle("This is news 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();    }    ...}

运行效果:
单页模式的运行效果:
d1
点击子选项跳转:
d2

双页模式的运行效果图:
s


4.5 小结与点评

本章我们了解碎片的基本概念,以及使用场景,掌握了碎片的常用方法,学习了碎片的生命周期,以及动态加载布局。

1 0