Android开发 入门篇(三) - 碎片


在进行安卓开发的时候不仅需要针对手机,还要针对平板进行适配,而平板的屏幕尺寸普遍比手机要大得多,因此软件UI在手机上适配的话有可能在平板上不适配,会带来视觉上较大的差异。
自动Android 3引入碎片的概念,能更好地在平板上显示UI。
出自《第一行代码(第二版)》,用于自己学(chao)习(shu)记录

碎片

碎片是一种可以嵌入到活动中地UI片段,可以让程序更加合理地利用大屏幕的空间。碎片和活动有些类似,都有着自己的布局和生命周期,可以看作是迷你型的活动。
举个栗子,一个新闻软件,使用RecyclerView控件显示新闻标题列表,点击标题后会跳转到新闻详情,使用了两个活动,如图所示
image-1
而如果直接放到平板上,标题列表会横向拉伸,很不美观,较好的设计是将标题列表和详情分别放到两个碎片中,这样就看起来会好很多
image-2

使用

新建两个碎片布局

分别编写left_fragment.xml和right_fragment.xml两个布局

filename: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/Button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="按钮"
        />
</LinearLayout>


filename:right_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"
    android:background="#AAAA00">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/TextView1"
        android:layout_gravity="center_horizontal"
        android:text="这是一个TextView"/>
</LinearLayout>

右边碎片是个屎黄色背景。
然后编写碎片的类,LeftFragment类和RightFragment类,需要继承Fragment类。继承android.support.v4.app.Fragment而不是android.app.Fragment,因为如果使用系统内置的有些低版本会无法使用一些功能,而且v4库在gradle中已经引入过了。
这里只是重写了onCreateView()方法,在其中动态加载布局即可。

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

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

然后在主活动布局文件中添加碎片控件,通过android:name来指定类名

filename:first_layout.xml
<?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:layout_width="0dp"
        android:id="@+id/fragment1"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:name="com.example.k.androidpractice_1.LeftFragment"
        />
    <fragment
        android:id="@+id/fragment2"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:name="com.example.k.androidpractice_1.RightFragment"
        />
</LinearLayout>

这时候新建一个平板虚拟机
image-3
运行程序,效果如图
image-4

动态添加碎片

碎片的强大之处在于可以在程序运行的时候动态添加到活动中。
新建andther_right_fragment.xml

filename: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:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/TextView2"
        android:layout_gravity="center_horizontal"
        android:text="这是另一个碎片"/>
</LinearLayout>

然后再新建一个AnotherRightFragment.java,继承v4库的Fragment,同样重写onCreateView()方法,动态加载布局

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

修改first_layout.xml,更换右边的碎片控件为FrameLayout,这个布局所有控件默认放置左上角,是最简单的布局,在这里用比较适合。

filename:first_layout.xml
<?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:layout_width="0dp"
        android:id="@+id/fragment1"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:name="com.example.k.androidpractice_1.LeftFragment"
        />
    <FrameLayout
        android:id="@+id/FrameLayout1"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        />
</LinearLayout>

随后修改FristActivity.java,给Button添加了一个监听,在onCreate()中添加RightFragment类,监听Button,点击按钮切换到AnotherRightFragment碎片类。
在replaceFragment中,切换碎片需要五个步骤

  • 创建待添加的碎片实例
  • 获取FragmentManagement实例,通过getSupportFragmentManager()方法获取
  • 开启事务,调用beginTransaction()方法实现
  • 向容器内添加或替换碎片,通过replace()方法实现,第一个参数为容器id,第二个参数为待添加或替换碎片实例
  • 调用commit()方法实现事务的提交

这样就可以实现碎片的切换了。

filename:FirstActivity.java
   protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button MyButton=findViewById(R.id.Button1);
        MyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                replaceFragment(new AnotherRightFragment());
            }
        });
        replaceFragment(new RightFragment());
    }
    private void replaceFragment(Fragment fragment){
        FragmentManager fragmentManager=getSupportFragmentManager();
        FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.FrameLayout1,fragment);
        fragmentTransaction.commit();
    }

这时候再次运行程序,点击Button可以发现右边的碎片成功切换了。
image-5

碎片返回栈

但是此时按返回键的时候会发现程序退出了,此时需要模拟活动的返回栈,即按返回后回到上一个碎片。
可以通过FgmentTranscration中的addToBackStack()方法,将一个事务添加到返回栈中。
修改FirstActivity.java代码,添加fragmentTransaction.addToBackStack(null);即可

。。。
private void replaceFragment(Fragment fragment){
        FragmentManager fragmentManager=getSupportFragmentManager();
        FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.FrameLayout1,fragment);
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.commit();
    }
。。。

现在再运行程序以后,点击Button切换了碎片,按返回键后先返回到上一个碎片,然后再按一次才退出。

碎片的通信

碎片和活动是处于独立的类中,相互不直接影响,没有直接通信的方式,如果想在活动中调用碎片或者在碎片中调用活动,FragmentManager提供了方法。
类似findViewById()的findFragmentById()方法用于从布局文件中获取碎片实例,如RightFragmet rightFragment=(RightFragment)getSupportFragmentManager().findFragmentById(R.id.right_Fragment);
在碎片中调用与之关联的活动:MainActivity activity=(Activity)getActivity();。如果碎片中需要使用到context对象的时候,也可以使用这个方法,因为Activity本身就是一个context。
所以碎片之间的通信就可以实现了,在碎片中调用与之关联的活动,在活动中获取另一个碎片的实例,进行通信。

碎片生命周期

活动的生命周期中有四个状态:运行状态、暂停状态、停止状态和销毁状态。同样碎片也有这四个运行状态

  • 运行状态:碎片可见,且关联活动和碎片均处于运行状态
  • 暂停状态:活动进入暂停状态时(如另一个未占满整个屏幕的活动处于栈顶),与之关联的可见碎片进入暂停状态
  • 停止状态:活动进入停止状态的时候,与之关联的碎片也进入停止状态。或者调用FragmentTransaction的remove()、replace()方法将碎片从活动中移除。若在事务提交之前调用addToBackStack()方法,碎片也会进入停止状态。停止状态时完全不可见的,还有可能被系统回收。
  • 销毁状态:活动被销毁的时候与之关联的碎片也被销毁了。如果在事务提交之前没有调用addToBackStack()方法,碎片也会进入销毁状态。

活动中有的回调方法碎片中几乎都有,碎片中还有一些额外的方法:

  • onAttach():碎片与活动关联的时候调用
  • onCreateView():为碎片创建视图(加载布局)的时候调用
  • onActivityCreated():与碎片关联的活动创建完毕的时候调用
  • onDestroyView():与碎片关联的视图被移除的时候调用
  • onDetch():碎片与活动解除关联的时候调用

如下图
image-6

动态加载布局的技巧

动态加载碎片的功能很强大,其实只是在布局文件中的添加和替换操作,如果可以根据屏幕分辨率或大小决定加载什么布局用户体验会更好。

限定符

在使用平板的时候很多应用都是横屏双页的,手机则是单页,如何判断应该是双页还是单页呢,需要使用到限定符了。
修改layout文件夹下的first_layout.xml,只留下左边的一个碎片

<?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:layout_width="match_parent"
        android:id="@+id/LeftFragment"
        android:layout_height="match_parent"
        android:name="com.example.k.androidpractice_1.LeftFragment"
        />

</LinearLayout>

新建文件夹layout-large,新建first_layout.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:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:id="@+id/LeftFragment"
        android:name="com.example.k.androidpractice_1.LeftFragment"
        />
    <fragment
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:name="com.example.k.androidpractice_1.RightFragment"
        android:id="@+id/RightFragment"
        />
</LinearLayout>

会根据设备自动选择是单页还是双页,但是这里可能是我手机太大了?他还认为我是双叶,然后给我都显示出来了,目前还不知道是为什么,讲道理手机应该只显示按钮的
image-7
image-8

一般的限定符
  • small - 小屏幕设备 - ldpi - 低分辨率设备(120dpi以下)
  • normal - 中等屏幕设备 - mdpi - 中分辨率设备(120dpi~160dpi)
  • large - 大屏幕设备 - hdpi - 高分辨率设备(160dpi~240dpi)
  • xlarge - 超大屏幕设备 - xhdpi - 超高分辨率设备(240dpi~320dpi)
  • xxhdpi - 超超高分辨率设备(320dpi~480dpi)
  • land - 横屏设备
  • port - 竖屏设备
最小限定符

文件夹名字为layout-sw600dp的时候,当设备屏幕宽度大于等于600dp的时候会加载这个文件夹中的布局。

实战 - 简易版新闻应用

需要使用到RecyclerView控件,先添加好依赖
compile ‘com.android.support:recyclerview-v7:24.2.1’
编写一个新闻类News.java

filename:News.java
public class News {
    private String Title;
    private String Content;
    public String getTitle(){
        return Title;
    }
    public String getContent(){
        return Content;
    }
    public void setTitle(String Title){
        this.Title=Title;
    }
    public void setContent(String Content){
        this.Content=Content;
    }
}

然后新建新闻碎片的布局文件news_conteng_frag.xml

filename: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:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/visiablity_layout"
        android:visibility="invisible"
        android:orientation="vertical">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/NewsTitle"
            android:gravity="center"
            android:padding="10dp"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#000"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:id="@+id/NewsContent"
            android:layout_weight="1"
            android:padding="15dp"/>

    </LinearLayout>
    <View
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:background="#000"/>

</RelativeLayout>

这里主要有两部分,一个是新闻标题,另一个是新闻内容,View是用来画线的,这个RelativeLayout就是左边的标题列表,第二个View是列表右边的竖线,更美观。
新建一个碎片类NewsContentFragment,动态加载碎片布局,然后获取标题和内容的TextView,设置对应的新闻标题和正文

filename:NewsContentFragment.java
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.visiablity_layout);
        VisibilityLayout.setVisibility(View.VISIBLE);
        TextView TitleTextView=view.findViewById(R.id.NewsTitle);
        TextView ContentTextView=view.findViewById(R.id.NewsContent);
        TitleTextView.setText(NewsTitle);
        ContentTextView.setText(NewsContent);
    }
}

上面这个是双页模式用的,现在编写单页模式的布局文件,新建活动NewsContentActivity,布局文件为news_content

filename:news_content.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"
    tools:context="com.example.k.androidpractice_1.NewsContentActivity">
    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/news_content_fragment"
        android:name="com.example.k.androidpractice_1.NewsContentFragment"/>
</LinearLayout>

修改NewsContentActivity.java内容,通过actionStart(…)方法可以很好的接收参数并且启动活动,在onCreate()中,先从Intent中获取到新闻的标题和正文,随后通过id获取碎片实例,刷新标题和正文。

filename:NewsContentActivity.java
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);
    }
}

新建一个布局文件用于显示新闻列表,news_title_frag.xml

filename:news_title_frag.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/TitlrRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>

编写子项布局news_item.xml,只有一个TextView,android:ellipsize就是说一行显示不下的时候省略

filename:news_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/news_title"
    android:ellipsize="end"
    android:padding="10dp"
/>

现在需要展示新闻列表的碎片了,新建NewsTitleFragment类,就是把RecyclerView控件动态加载进来了,然后在onActivityCreated()方法中通过对news_content_frag碎片的获取判断当前是双页模式还是单页模式。

filename:NewsTitleFragment.java
public class NewsTitleFragment extends Fragment{
    private boolean IsTwo;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view=inflater.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_fragment)!=null)
            IsTwo=true;
        else
            IsTwo=false;
    }
}

之后修改主布局文件first_layout.xml内容

filename:first_layout.xml
<?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:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/news_title_frag"
        android:name="com.example.k.androidpractice_1.NewsTitleFragment"/>

</LinearLayout>

然后新建一个文件夹layout-sw600dp,新建first_layout.xml,两个碎片,第一个碎片是标题列表,第二个碎片是新闻正文。

filename:layout-sw600dp/first_layout.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:layout_height="match_parent"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:id="@+id/news_title_frag"
        android:name="com.example.k.androidpractice_1.NewsTitleFragment"/>
    <FrameLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        >
        <fragment
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="com.example.k.androidpractice_1.NewsContentFragment"
            android:id="@+id/news_content_frag"/>
    </FrameLayout>
</LinearLayout>

然后就是最麻烦的了,给标题列表添加适配器,好是好麻烦!!!

filename:NewsTitleFragment.java
package com.example.k.androidpractice_1;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

/**
 * Created by kang on 2020/1/30.
 */

public class NewsTitleFragment extends Fragment{
    boolean IsTwo;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view=inflater.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_fragment)!=null)
            IsTwo=true;
        else
            IsTwo=false;
    }
    class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{
        private List<News> MyNewsList;
        class ViewHolder extends RecyclerView.ViewHolder{
            TextView MyTextView;
            public ViewHolder(View view){
                super(view);
                MyTextView=view.findViewById(R.id.news_title);
            }
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.news_items,parent,false);
            final ViewHolder holder=new ViewHolder(view);
            view.setOnClickListener(new View.OnClickListener(){

                @Override
                public void onClick(View v) {
                    News New=MyNewsList.get(holder.getAdapterPosition());
                    if (IsTwo){
                        NewsContentFragment newsContentFragment=(NewsContentFragment)getFragmentManager().findFragmentById(R.id.news_content_fragment);
                        newsContentFragment.refresh(New.getTitle(),New.getContent());
                    }else{
                        NewsContentActivity.actionStart(getActivity(),New.getTitle(),New.getContent());
                    }
                }
            });
            return holder;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            News New=MyNewsList.get(position);
            holder.MyTextView.setText(New.getTitle());
        }

        @Override
        public int getItemCount() {
            return MyNewsList.size();
        }


        public NewsAdapter(List<News> MyNewList){
            this.MyNewsList=MyNewList;
        }
    }
}

再次修改NewsTitleFragment类中的onCreateView()方法,添加getNews()方法,为了添加新闻信息,没什么大用

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.news_title_frag,container,false);
        RecyclerView NewTitleRecyclerView=view.findViewById(R.id.TitlrRecyclerView);
        LinearLayoutManager linearLayoutManager=new LinearLayoutManager(getActivity());
        NewTitleRecyclerView.setLayoutManager(linearLayoutManager);
        NewsAdapter Adapter=new NewsAdapter(getNews());
        NewTitleRecyclerView.setAdapter(Adapter);
        return view;
    }
    private List<News> getNews(){
        List<News> NewsList=new ArrayList<>();
        for (int i=0;i<50;i++){
            News New=new News();
            New.setTitle("this is title - "+i);
            New.setContent("this is title - "+i+"this is title - "+i+"this is title - "+i+"this is title - "+i+"this is title - "+i+"this is title - "+i);
            NewsList.add(New);
        }
        return NewsList;
    }

之后运行就可以看到结果了,平板上看是这样子的。
image-9

OK,THANKS FOR READING.BYE BYE~

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值