低级安卓工程师之路(五)

RecyclerView

ListView不使用技巧提升运行效率的话,性能就会很差。而且只能实现数据纵向滚动的效果,无法实现横向滚动效果。

RecyclerView是一个增强版的ListView,不仅可以轻松实现ListView同样的效果,还优化了ListView的不足之处。

RecyclerView的基本用法

导入依赖

implementation 'androidx.recyclerview:recyclerview:1.0.0'

修改avtivity_main.xml的代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

  <androidx.recyclerview.widget.RecyclerView
      android:id="@+id/recyclerView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>

</LinearLayout>

注意:由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写出来.

图片参考之前的ListView项目中资源,下面为RecyclerView准备一个适配器,新建FruitAdapter类,让这个类继承自RecyclerView.Adapter,并且将泛型指定为FruitAdapter.ViewHolder. 其中ViewHolder是FruitAdapter中的内部类

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private List<Fruit> mFruitList;


    @Override
    public ViewHolder onCreateViewHolder( ViewGroup viewGroup, int i) {
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.fruit_item,viewGroup,false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder( ViewHolder viewHolder, int i) {
           Fruit fruit =mFruitList.get(i);
           viewHolder.friutImage.setImageResource(fruit.getImageId());
           viewHolder.fruitName.setText(fruit.getName());
    }

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

    static  class ViewHolder extends RecyclerView.ViewHolder
    {
        ImageView  friutImage;
        TextView fruitName;
        public ViewHolder(View view)
        {
            super(view);
            friutImage = (ImageView)view.findViewById(R.id.fruitImage);
            fruitName = (TextView)view.findViewById(R.id.fruitName);

        }
    }
    public  FruitAdapter(List<Fruit> fruitList)
    {
        mFruitList=fruitList;
    }

}

这是RecyclerView适配器标准的写法,首先定义了一个内部类,ViewHolder,继承自RecyclerView.ViewHolder。然后ViewHolder的主构造函数中要传入一个View参数,这个参数通常就是RecyclerView子项的最外层布局,这样可以通过findViewById()方法获取布局中的ImageView和TextIView的实例。

FruitAdapter中也有一个构造函数,用于把要展示的数据源传进来。

FruitAdapter继承自RecyclerView.Adapter,所以必须重写onCreateViewHolder(),onBindViewHolder () 和getItemCount()这三个方法。

onCreateViewHolder()用于创建ViewHolder实例,并把加载出来的布局传入构造函数当中,最后将ViewHolder的实例返回。

onBindViewHolder () 方法用于对RecylcerView子项的数据赋值,会在每个子项被滚动到屏幕内的时候执行。这里通过position参数得到当前项的Fruit实例,然后将数据设置到ViewHolder的ImageView和TextView中即可。

getItemCount()方法用于告诉RecyclerView一共有多少子项,直接返回数据源的长度即可。

修改MainActivity中的代码

public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruits()
    {
        for (int i = 0 ;i<2 ; i++)
        {
            Fruit apple = new Fruit("Apple",R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana",R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange",R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon",R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear",R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape",R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple",R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry",R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry",R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango",R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }
}

使用initFruits()方法,用于初始化所有的水果数据。 在oncreate()方法中先创建了一个LinearLayoutManager对象,并将它设置到RecyclerView当中。layoutManager用于指定RecyclerView的布局方式,这里LinearLayoutManager是线性布局的意思。 然后创建FruitAdapter的实例,并将数据传入FruitAdapter的构造函数中,最后调用RecyclerView的setAdapter()方法来完成适配器的设置。

这里用RecyclerView实现了ListView一样的效果,代码虽然没有明显减少,但是逻辑更加清晰。

RecyclerView实现横向滚动和瀑布流布局

用RecyclerView实现横向滚动,首先对fruit_item布局进行修改,因为目前这个布局里面的元素是水平排列的,适用于纵向滚动的场景,所以要实现横向滚动的话,应该把friut_item里的元素改成垂直排列才比较合理。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="80dp"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"/>

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"
        />

</LinearLayout>

这里将LinerLayout改成垂直方向排列,并将宽度设置为80dp,这里将宽度指定为固定值是因为水果文字的长度有长有短,非常不美观,如果使用match_parent的话,就会导致宽度过长,一个子项就会占满整个屏幕。 然后将ImageView和TextView都设置成了在布局中水平居中的位置,并且使用layout_marginTop属性让文字和图片保持一定的距离。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

调整LinearLayoutManager的setOrientation()方法设置布局的排列方向。默认是纵向排列的,这里传入
LinearLayoutManager.HORIZONTAL表示让布局横向排列。

ListView的布局排列是由自身去管理的,而RecyclerView把这个工作交给了LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同的排列方式的布局了。

除了LinearLayoutManager之外,RecyclerView还给我们提供了GridLayoutManager和StaggerdGridLayoutManager这两种内置的布局排列方式。GridLayoutManager实现网格布局,StaggerdGridLayoutManager可以用于实现瀑布流布局。这里我们实现瀑布流布局。

修改fruit_item_xml中的代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    >

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"/>

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp"
        />

</LinearLayout>

将LinerLayout的宽度设置为match_parent,因为瀑布流的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。 使用layout_margin属性来让子项之间互留一点间距,这样不至于所有子项都紧贴在一起。由于等下要将文字长度变长,所有这里将TextView的对齐属性改为居左对齐,这样观赏性更好。

修改MainActivity中的代码

public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        StaggeredGridLayoutManager layoutManager = new
                StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
        
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruits()
    {
        for (int i = 0 ;i<2 ; i++)
        {
            Fruit apple = new Fruit(getRandomLenthName("Apple"),R.drawable.apple_pic);
            fruitList.add(apple);

            Fruit banana = new Fruit(getRandomLenthName("Banana"),R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit(getRandomLenthName("Orange"),R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new 
            Fruit(getRandomLenthName("Watermelon"),R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit(getRandomLenthName("Pear"),R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit(getRandomLenthName("Grape"),R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new 
            Fruit(getRandomLenthName("Pineapple"),R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new 
            Fruit(getRandomLenthName("Strawberry"),R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(getRandomLenthName("Cherry"),R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit(getRandomLenthName("Mango"),R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }
    private String getRandomLenthName(String name)
    {
        Random random = new Random();
        int length = random.nextInt(20) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0 ; i< length; i++)
        {
            builder.append(name);
        }
        return builder.toString();
    }

}

在onCreate()方法中,我们创建了一个StaggeredGridLayoutManager 的实例。StaggeredGridLayoutManager的构造函数接收两个参数,第一个参数用于指定布局的列数,传入3 ,代表会把布局分为3列;第二个参数指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列,最后将创建好的实例设置到RecyclerView当中就可以了。
为了更好的观测到瀑布流布局的效果,所以采用小技巧将各子项的高度设置的不一致。这里用getRandomLengthNmae()这个方法,用Random对象,来创造一个1到20之间的随机数,将参数传递进来的字符串随机重复几遍。在initFruit()方法中,每个水果的名字都改成调用getRandomLengthName()来生成,这样能保证水果名字的长短比较大,子项的高度也就有了差异

RecyclerView的点击事件

RecyclerView并有提供类似于setOnItemClickListener()这样的注册器监听器的放啊,而是需要给子项具体的View去注册点击事件. 实际上,ListView在点击事件上的处理并不人性化,setOnItemClickListener()方法注册的是子项的点击事件,但是如果想点击子项某个具体的某个按钮,虽然ListView能做到,但是实现起来相对麻烦。RecyclerVIew干脆直接摒弃了子项点击事件的监听器,所有点击事件都有具体的View去注册,就没有这个困扰了

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private List<Fruit> mFruitList;

    static  class ViewHolder extends RecyclerView.ViewHolder
    {
        View fruitView;
        ImageView  friutImage;
        TextView fruitName;
        public ViewHolder(View view)
        {
            super(view);
            fruitView = view;
            friutImage = (ImageView)view.findViewById(R.id.fruitImage);
            fruitName = (TextView)view.findViewById(R.id.fruitName);

        }
    }
    @Override
    public ViewHolder onCreateViewHolder( ViewGroup viewGroup, int i) {
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.fruit_item,viewGroup,false);


        final  ViewHolder holder = new ViewHolder(view);
        holder.fruitView.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int position = holder.getAdapterPosition();
                        Fruit fruit = mFruitList.get(position);
                        Toast.makeText(v.getContext(),"you cliked view"+fruit.getName(),
                                Toast.LENGTH_SHORT).show();
                    }
                }
        );
        holder.friutImage.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int position = holder.getAdapterPosition();
                        Fruit fruit = mFruitList.get(position);
                        Toast.makeText(v.getContext(),"you cliked image"+fruit.getName(),
                                Toast.LENGTH_SHORT).show();
                    }
                }
        );

        return holder;
    }

    @Override
    public void onBindViewHolder( ViewHolder viewHolder, int i) {
           Fruit fruit =mFruitList.get(i);
           viewHolder.friutImage.setImageResource(fruit.getImageId());
           viewHolder.fruitName.setText(fruit.getName());
    }

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

    public  FruitAdapter(List<Fruit> fruitList)
    {
        mFruitList=fruitList;
    }

}

在ViewHolder中添加了fruitView变量来保存子项最外层布局的实例,然后在onCreateVIewHolder()方法中注册点击事件就可以了。这里分别为最外层布局和ImageView都注册了点击事件,RecyclerView可以轻松实现子项任意控件或布局的点击事件。这里两个点击事件先获取了用户点击的position,然后通过position拿到相应的Fruit实例,再使用Toast分别弹出两种不同的内容以示区别。

编写精美的聊天界面

修改activity_main.xml中的代码

<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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/msg_recycler_view"

        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/input_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Type somthing here"
            android:maxLines="2" />
        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="send" />

    </LinearLayout>
</LinearLayout>

放置一个RecyclerView用来显示聊天的信息内容,放置一个EditText用于输入消息,放置一个Button用于发送消息。

创建实体类 Msg

public class Msg {
    public static final int TYPE_RECEIVED=0;
    public  static final  int TYPE_SEND= 1;
    private String content;
    private int type;

    public Msg(String content,int type)
    {
        this.content = content;
        this.type = type;
    }

    public String getContent() {
        return content;
    }

    public int getType() {
        return type;
    }
}

Msg类中只有两个字段,content表示消息的内容,type表示消息的类型。 消息类型有两种,TYPE_RECEIVED表示一条收到的消息,TYPE_SENT表示这是一条发出的消息。

RecyclerView子项的布局,新建msg_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    >

    <LinearLayout
        android:id="@+id/left_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:background="@drawable/message_left">

        <TextView
            android:id="@+id/left_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#fff"/>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/right_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/message_right"
        >

        <TextView
            android:id="@+id/right_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"/>
    </LinearLayout>


</LinearLayout>

这里让收到的消息居左对齐,发出的消息居右对齐。这里不用担心收到的消息和发出的消息都在同一个布局里面,里面可以根据消息的类型,来决定隐藏和显示哪种消息就可以了。

创建RecyclerView的适配器类,新建MsgAdapter。

public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> {

    private List<Msg>mMsgList;

    static class ViewHolder extends RecyclerView.ViewHolder{
        LinearLayout leftLayout;
        LinearLayout rightLayout;
        TextView letfMsg;
        TextView rightMsg;
        public ViewHolder(View view)
        {
            super(view);
            leftLayout= (LinearLayout)view.findViewById(R.id.left_layout);
            rightLayout = (LinearLayout)view.findViewById(R.id.right_layout);
            letfMsg = (TextView)view.findViewById(R.id.left_msg);
            rightMsg = (TextView) view.findViewById(R.id.right_msg);
        }
    }

    public MsgAdapter(List<Msg>msgList)
    {
        mMsgList = msgList;
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate
                (R.layout.msg_item,viewGroup,false);
                
        return new ViewHolder(view);

    }
    
    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
       Msg msg = mMsgList.get(i);
       if(msg.getType()==Msg.TYPE_RECEIVED)
       {//如果是收到消息,则显示左边的消息布局,将右边的消息布局隐藏
           viewHolder.leftLayout.setVisibility(View.VISIBLE);
           viewHolder.rightLayout.setVisibility(View.GONE);
           viewHolder.letfMsg.setText(msg.getContent());

       }
       else if(msg.getType()==Msg.TYPE_SEND)
       {//如果是发送消息,则显示右边的消息布局,将左边的消息布局隐藏
           viewHolder.rightLayout.setVisibility(View.VISIBLE);
           viewHolder.leftLayout.setVisibility(View.GONE);
           viewHolder.rightMsg.setText(msg.getContent());
       }
    }

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

}

前面是RecyclerView的基本用法,不过这里在onBindViewHolder()方法中增加了对消息类型的判断,如果这条消息是收到的,则显示左边的消息布局,如果这条消息是发出的,则显示右边的消息布局。

public class MainActivity extends AppCompatActivity {

    private List<Msg>msgList = new ArrayList<>();
    private EditText inputText;
    private Button send;
    private RecyclerView msgRecyclerView;
    private MsgAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initMsg();
        inputText = (EditText) findViewById(R.id.input_text);
        send = (Button)findViewById(R.id.send);
        msgRecyclerView= (RecyclerView)findViewById(R.id.msg_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        msgRecyclerView.setLayoutManager(layoutManager);
        adapter = new MsgAdapter(msgList);
        msgRecyclerView.setAdapter(adapter);
        send.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        String content = inputText.getText().toString();
                        if(!"".equals(content))
                        {
                            Msg msg = new Msg(content,Msg.TYPE_SEND);
                            msgList.add(msg);
                            adapter.notifyItemInserted(msgList.size()-1);//当有
                            //新消息时,刷新RecyclerView中的显示
                            msgRecyclerView.scrollToPosition(msgList.size()-1);//将
                            //RecyclerView定位到最后一行
                            inputText.setText("");//清空输入框中的内容
                        }
                    }
                }
        );

    }
    private void initMsg()
    {
        Msg msg1 = new Msg("Hello Lixiaolei",Msg.TYPE_RECEIVED);
        msgList.add(msg1);
        Msg msg2 = new Msg("Hello Shenhaojie",Msg.TYPE_SEND);
        msgList.add(msg2);
        Msg msg3 = new Msg("Wo xiang xiao haha",Msg.TYPE_RECEIVED);
        msgList.add(msg3);
    }

}

在initMsg()方法中先初始化几条数据用于在RecyclerView中显示。在按钮的点击事中,获取了EditText的内容,如果内容不为空则创造出一个新的Msg对象,并把它插入到msgList列表中去,然后又调用了适配器的notifyItemInserted()方法,用于通知列表有新数据插入,这样新增的消息才能够在RecyclerView中显示。接着调用RecyclerView的scrollToPosition()方法,将显示的数据定位到最后一行,保证一定可以看到最后发出的一条消息。最后调用EditText的setText()方法将输入的内容清空。

Fragment

碎片(Fragment)是一种可以嵌入在活动中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用的非常广泛。碎片和活动类似,都能包含布局,有自己的生命周期。

简单使用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:background="#00ff00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_gravity="center_horizontal"
        android:text="this is right fragment"/>

</LinearLayout>

新建LeftFragment类,继承自Fragment。注意,这里继承的是androidx.fragment.app.Fragment,还有个一个是系统内置的anroid.app.Fragment 。由于要让碎片在所有Android系统版本中保持功能一致性,并且支持像Fragment中嵌套Fragment这种功能。所有要导androidx.fragment.app.Fragment。

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

    }
}

这里只重写Fragment的onCreateView()方法,然后在这个方法中通过LayoutInflater的inflate()方法将定义好的left_fragment布局动态加载进来,同样的方法,再创建RightFragment.

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

修改activity_main.xml

<!--name属性是静态引用Fragment类,
Layout属性是让布局立马显示在此布局上(layout属性可有可无)
id属性是必须要引用的,不加的话会报错-->

<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"
    android:orientation="horizontal"
    tools:context=".MainActivity">

   <fragment
       android:id="@+id/letf_fragment"
       android:layout_height="match_parent"
       android:name="com.example.fragementtest.LeftFragment"
       android:layout_width="0dp"
       android:layout_weight="1"
       />
    <fragment
        android:id="@+id/right_fragment"
        android:layout_width="0dp"
        android:name="com.example.fragementtest.RightFragment"
        android:layout_height="match_parent"
        android:layout_weight="1"

        />

</LinearLayout>

使用< Fragment> 标签在布局中添加碎片,这里需要通过android:name属性来显式指明要添加的碎片类名,注意一定要将类的包名也加上。(android:name属性指定了在布局中要实例化的Fragment。)

fragment的静态创建步骤:在要用到fragment的Activity所对应的XML文件中添加fragment控件并为其添加name属性(android:name=“包名.Fragment类名”)和id属性(id不加的话会在程序运行时出现闪退)。

动态添加Fragment

新建anoher_right_fragment

<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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_gravity="center_horizontal"
        android:text="this is another right fragment"/>

</LinearLayout>

anoher_right_fragment和right_fragmen的代码基本相同,只是将背景色改成了黄色,并修改了显示文字。新建AnotherRightFragment作为另一个右侧碎片

public class AnotherRightFragment extends Fragment {
    @Nullable
    @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;
    }
}

在onCreateVIew()方法中加载了刚刚创建的anoher_right_fragment布局,这样就准备好了另一个碎片。看下如何动态添加到活动中。修改activtiy_main.xml

<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"
    android:orientation="horizontal"
    tools:context=".MainActivity">

   <fragment
       android:id="@+id/letf_fragment"
       android:layout_height="match_parent"
       android:name="com.example.fragementtest.LeftFragment"
       android:layout_width="0dp"
       android:layout_weight="1"
       />
    <FrameLayout
        android:id="@+id/right_layout"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="match_parent">

    </FrameLayout>
</LinearLayout>

这里右边是一个FrameLayout,FrameLayout的所有控件默认都会摆放在布局的右上角。这里仅需要在布局里放入一个碎片,不需要任何定位,因此非常适合使用FrameLayout。

注意这里的letf_fragment由于是静态创建的,所以必须添加name属性。

修改MainActivity代码,实现动态添加碎片功能:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(this);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(this);
        replaceFragement(new RightFragment());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId())
        {
            case R.id.button1:
                replaceFragement(new AnotherRightFragment());
                break;
            case R.id.button2:
                    replaceFragement(new RightFragment());
                break;

                default:
                    break;
        }
    }
    private  void replaceFragement(Fragment fragment)
    {
        FragmentManager fragmentManager  = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.right_layout,fragment);
        transaction.commit();
    }

}

给左侧的两个按钮添加了点击事件,通过replaceFragement()方法动态碎片。首先是默认添加rigthFragment碎片到帧布局里面。当点击左侧buttton(yellow)的时候,会将AnotherRightFragment添加到帧布局中替换 rigthFragment碎片。

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

在Fragment中模拟返回栈

通过点击按钮向活动中动态添加碎片后,按下Back键程序直接退出了。如果想模仿类似返回栈的效果,按下Back键可以回到上一个碎片,该如何实现?

private  void replaceFragement(Fragment fragment)
    {
        FragmentManager fragmentManager  = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.right_layout,fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

FragmentTransaction 中提供一个 addToBackStack()方法,可以用于将一个事务添加到返回栈中。
这里在事务提交之前调用了FragmentTransaction的addToBackStack()方法,它可以接受一个名字用于描述返回栈的状态,一般传入null即可。 这样,点击按钮将碎片添加到活动中,按下Back键,发现程序没有退出。

Fragement和Activity之间进行通信

碎片虽然都嵌入在活动中显示,实际上关系没有那么亲密。可以看出,碎片和活动都是各自存在于一个独立的类当中的,它们之间没用明显的方式之间进行通信。

如果在活动中调用碎片的方法,或者在碎片中调用活动的方法,应该如何实现呢?

为了方便碎片和活动之间通信,FragmentManager提供了一个类似于findViewById()的方法,专门从布局文件中获取碎片的实例:

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

调用FragmentManager的findFragmentById()方法,可以在Activity中得到相应的Fragment的实例,然后就可以轻松调用Fragment里的方法了。

在Fragment中如何调用Activity中的方法呢?

在每个 Fragement中可以通过getActivity()方法来得到和当前Fragement相关联的Activity实例。

MainActivity activity = (MainActivity) getActivity();

有了活动实例后,在碎片中调用活动的方法就会变得轻而易举。另外当Fragment中需要使用Context对象时,也可以使用getActivity()方法,因为获取到的Activity本身就是一个Context对象。

碎片和碎片之间怎么通信呢?

思路很简单:首先在一个碎片中可以得到与它相关联的活动,然后再通过这个活动去获取另外一个碎片的实例,这样就实现了不同碎片之间的通信功能。

Fragment的生命周期

1 运行状态
当碎片可见,并且它所关联的活动正处于运行状态时,该碎片也处于运行状态。

2暂停状态
当一个活动进入暂停状态的时,(由于另一个未占满屏幕的活动被添加到了栈顶)与它相关联的可见碎片就会进入到暂停状态

3停止状态
当一个活动进入停止状态时,与它相关联的碎片就会进入到停止状态,或者通过调用FragmentTranscation的remove(),replace()方法将碎片从活动中移除,但如果在事务提交之前调用addToBackStack()方法,这时碎片也会进入到停止状态。总的来说,进入到停止状态的碎片对用户来说是完全不可见的,有可能被系统回收。

4销毁状态

碎片总是依附于活动存在的,因此活动被销毁的时候,与它相关联的碎片就会进入到销毁状态,或者通过调用FragmentTranscation的remove(),replace()方法将碎片从活动中移除,但如果在事务提交之前并没有调用addToBackStack()方法,这时碎片也会进入到销毁状态。

Fragement类中提供了一系列的回调方法,以覆盖碎片生命周期的每个环节。其中活动有了回调方法,碎片几乎全有。而且碎片还提供了一些附加的回调方法。

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

体验Fragment的生命周期

修改RigthtFragment中的代码

public class RightFragment extends Fragment {

    public  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(@NonNull LayoutInflater inflater, 
    @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view  = inflater.inflate(R.layout.right_fragment,container,false);
        Log.d(TAG,"onCreateView");
        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");
    }
}

在碎片中也可以通过onSaveInstanceState()方法保存数据的,因为进入停止状态的碎片有可能在系统内存不足的时候被回收。保存下来的数据在onCreate(),onCreateVIew()和onActivityCreated()这3个方法中都可以重新得到,它们中都有一个Bundle类型的saveInstanceState参数。(详情参考activity被回收了怎么保存数据)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值