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);
msgRecyclerView.scrollToPosition(msgList.size()-1);
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被回收了怎么保存数据)
|