Android学习(三)

Fragment 碎片

碎片的简单用法

  1. 新建一个左侧碎片布局 起名为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>
    
  2. 新建一个右侧碎片布局 起名为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>
    
  3. 新建一个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布局动态加载进来。
    
  4. 新建一个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);
        }
    }
    
  5. 然后修改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>
    

使用标签在布局中添加碎片,使用android:name属性来显示知名要添加的碎片完整类名,这样最简单的碎片Demo就做好了。

动态添加碎片

  1. 其他步骤与上一个例子相同只需要在activity_main中修改将fragment修改成FrameLayout

    <FrameLayout
        android:id="@+id/right_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
    
  2. 修改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步:

  1. 创建待添加的碎片实例
  2. 获取FragmentManager,在活动中可以直接通过getSupportFragment()方法得到
  3. 开启一个事物,通过beginTransaction()方法开启。
  4. 向容器内添加或替换碎片, 一般使用replace()方法实现, 需要传入容器的id和待添加的碎片实例
  5. 提交事务,条用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()——碎片已激活

碎片被激活后,有两种情况:

  1. 用户点击返回键或者碎片被移除/替换——onPause()——onStop()——onDestoryView()——onDestory()——onDetach()——碎片被销毁
  2. 当碎片被添加到返回栈,然后被移除/替换——onPause()——onStop()——onDestoryView()——从返回栈中回到上一个碎片——重新执行onCreateView()方法

碎片的状态和回调

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

使用限定符

判断程序应该使用单页模式还是双页模式需要借助 限定符来实现。

  1. 我们修改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>
    
  2. 在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为单位),然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加在一个布局,屏幕宽度小于这个值的化就加载另一个布局。

  1. 在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 ---- 一个简易新闻应用。

  1. 添加 RecyclerView 的依赖库 在app/build.gradle中添加

    implementation 'com.android.support:recyclerview-v7:26.0.0-alpha1'
    
  2. 新建一个欣慰的实体类 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;
        }
    }
    
  3. 新建布局文件 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>
    

    新闻的布局主要可以分为两个部分, 头部分显示新闻标题,正文部分显示新闻内容,中间用一条细线隔开,

  4. 然后创建一个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()方法 这个方法用于设置新闻标题和内容

  5. 这样我们就把新闻内容的碎片和布局都创建好了。但是他们都是在双页模式中使用的,我们如果要在单页模式中也使用的话,还需要再创建一个活动, 起名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布局的内容自动加了进来

  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_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()方法将新闻的标题和内容传入, 就可以吧数据先输出来了。

  7. 新建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>
    
  8. 新建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是指在尾部进行缩略

  9. 修改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>
    

    在单页模式下,只会加载一个新闻标题的碎片

  10. 新建一个 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>
    
  11. 然后创建一个用于展示新闻列表的地方 新建一个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来判断当前是双页模式还是单页模式。

  12. 现在已经完成了绝大部分的工作, 但是还剩下一个最重要的一点就是在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布局为单页模式。
            }
        }
    }
    
  13. 向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布局为单页模式。
            }
        }
    }
    

    这样就可以完成了。





posted @ 2018-11-26 16:21  Zbuter  阅读(152)  评论(0编辑  收藏  举报