Fragment和RecyclerView混用
1. 参考:《Android编程权威指南(第2版)》编写
Fragment编写
照上图编写一个用Food写成的,图中入口是CrimeActivity
,从下向上编写:
1. 新建一个Food类:
Food.java
package com.example.foodtest;
public class Food {
private String name;
private int image;
public Food(String name, int image) {
this.name = name;
this.image = image;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImage() {
return image;
}
public void setImage(int image) {
this.image = image;
}
}
2. 编写fragment_food.xml,也就是要显示的一个food的一行布局。
<?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="wrap_content"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/food_image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/ic_launcher_background"
/>
<TextView
android:id="@+id/food_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:layout_marginLeft="100dp"
android:text="food"
/>
</LinearLayout>
3. 编写FoodFragment用来加载fragment_food.xml
FoodFragment.java
package com.example.foodtest;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class FoodFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_food, container, false);
}
}
4. 把MainActivity.java和activity_main.xml重命名为FoodActivity.java和activity_food.xml。
5. 编写activity_food.xml,让其内部有Fragment容器
activity_food.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</FrameLayout>
6. 编写FoodActivity.java
FoodActivity.java
package com.example.foodtest;
import androidx.fragment.app.Fragment;
public class FoodActivity extends SingleFragmentActivity{
@Override
protected Fragment createFragment() {
return new FoodFragment();
}
}
package com.example.foodtest;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
public abstract class SingleFragmentActivity extends FragmentActivity {
protected abstract Fragment createFragment();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*加载FrameLayout容器布局*/
setContentView(R.layout.activity_food);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container);
if (fragment == null)
{
fragment = createFragment();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
}
为什么写一个SingleFragmentActivit,可以看另一篇:Fragment碎片
因为把MainActivity改成了FoodActivity,所以FoodActivity变成了启动文件,运行效果如下:
RecyclerView编写
也是照着《Android编程权威指南》中的图编写:
图中的意思是CrimeLab中有一个ArrayList,ArrayList中存了Crime,书中还说CrimeLab是单例模式,为了省事,就不创建这个类了。
碎片已经写好,内容只有一个,现在想要显示多个,肯定要把一个换成多个,这里用到RecyclerView,前面的“一个”是FoodFragment.java
与fragment_food.xml
,现在要多个再创建两个相似的文件:
1. 创建FoodListFragment和fragment_food_list
2. 编写fragment_food_list.xml
考虑一下这个文件应该写什么?多个food应该写RecyclerView。
fragment_food_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/food_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</androidx.recyclerview.widget.RecyclerView>
记得在app/build.gradle文件中导入依赖
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
这样我们的FoodList就可以放在RecyclerView中了。
3. 编写FoodListFragment.java
这个文件的作用是显示RecyclerView。
FoodListFragment.java
package com.example.foodtest;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class FoodListFragment extends Fragment {
private RecyclerView mRecyclerView;
private FoodAdapter mFoodAdapter;
private List<Food> initFoods(){
List<Food> foods = new ArrayList<>();
for (int i = 0; i < 50; i++) {
foods.add(new Food("apple" + i, R.mipmap.ic_launcher));
}
return foods;
}
/*FoodListFragment布局显示*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
/*加载到Recycler布局*/
View view = inflater.inflate(R.layout.fragment_food_list, container, false);
/*找到RecyclerView*/
mRecyclerView = view.findViewById(R.id.food_recycler_view);
/*线性样式*/
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
mRecyclerView.setLayoutManager(layoutManager);
/*适配*/
mFoodAdapter = new FoodAdapter(initFoods());
mRecyclerView.setAdapter(mFoodAdapter);
return view;
}
private class FoodHolder extends RecyclerView.ViewHolder{
public ImageView mImageView;
public TextView mTextView;
public FoodHolder(View itemView) {
super(itemView);
mImageView = itemView.findViewById(R.id.food_image);
mTextView = itemView.findViewById(R.id.food_name);
}
}
private class FoodAdapter extends RecyclerView.Adapter<FoodHolder>{
List<Food> mFoods;
public FoodAdapter(List<Food> foods) {
mFoods = foods;
}
@Override
public FoodHolder onCreateViewHolder( ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
/*每一个要显示的内容在是fragment_food.xml中*/
View view = layoutInflater.inflate(R.layout.fragment_food, parent, false);
FoodHolder foodHolder = new FoodHolder(view);
return foodHolder;
}
@Override
public void onBindViewHolder( FoodListFragment.FoodHolder holder, int position) {
Food food = mFoods.get(position);
holder.mImageView.setImageResource(food.getImage());
holder.mTextView.setText(food.getName());
}
@Override
public int getItemCount() {
return mFoods.size();
}
}
}
这里把List放在这个类中了。RecyclerView部分可以参考另一篇:RecyclerView
4. 编写FoodListActivity.java, 用于启动。记得在AndroidManifest.xml中把此类作为启动类。
FoodListActivity.java
package com.example.foodtest;
import androidx.fragment.app.Fragment;
public class FoodListActivity extends SingleFragmentActivity{
@Override
protected Fragment createFragment() {
/*加载FoodListFrgament中的布局,
FoodListFragment布局中装载了RecyclerView,
RecyclerView中又装载了fragment_food布局*/
return new FoodListFragment();
}
}
效果如下:
刚才写的FoodActivity没用了,Fragment相当于一个Activity用于加载一个界面,但是最终显示还是要通过Activity加载进FrameLayout中进行显示,多了一层。
2. 改写《第一行代码(第二版)》3.7编写精美聊天界面
文中并没有使用Fragment的方法。整个架构大致如下:
在些基础上要加上一个Fragment,Fragment就是相当于一层小Activity,所以用MainActivity来显示Fragment,Fragment中显示RecyclerView即可。
先把不需要改变的文件列出:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#d8e0e8"
>
<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"
android:orientation="horizontal"
>
<EditText
android:id="@+id/input_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something here"
android:maxLines="2"
/>
<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"
/>
</LinearLayout>
</LinearLayout>
Msg.java
public class Msg {
public static final int TYPE_RECEIVED = 0;
public static final int TYPE_SENT = 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_item.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="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="@mipmap/message_left"
>
<!--margin-->
<!--Describes a padding to be applied along the edges inside a box.-->
<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="@mipmap/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>
两个图片资源,不知道这样能不能用。
MsgAdapter.java
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> {
List<Msg> mMsgs;
public MsgAdapter(List<Msg> msgs) {
mMsgs = msgs;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
/*获得打气筒*/
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
/*装载布局*/
View view = layoutInflater.inflate(R.layout.msg_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(MsgAdapter.ViewHolder holder, int position) {
Msg msg = mMsgs.get(position);
/*如果是接收,就显示对方的消息*/
if (msg.getType() == Msg.TYPE_RECEIVED){
holder.leftLayout.setVisibility(View.VISIBLE);
holder.rightLayout.setVisibility(View.GONE);
holder.leftMsg.setText(msg.getContent());
}else if (msg.getType() == Msg.TYPE_SENT){
holder.rightLayout.setVisibility(View.VISIBLE);
holder.leftLayout.setVisibility(View.GONE);
holder.rightMsg.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgs.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
LinearLayout leftLayout;
LinearLayout rightLayout;
TextView leftMsg;
TextView rightMsg;
public ViewHolder(View itemView) {
super(itemView);
leftLayout = itemView.findViewById(R.id.left_layout);
rightLayout = itemView.findViewById(R.id.right_layout);
leftMsg = itemView.findViewById(R.id.left_msg);
rightMsg = itemView.findViewById(R.id.right_msg);
}
}
}
现在要加上Fragment,先搞个布局:
fragment_msg.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" >
</FrameLayout>
有了布局,中间要有内容,这个内容就是原来的MainActivity中显示的内容,只不过这里最后要放在FrameLayout容器里了。内容通过Fragment子类来加载。
FragmentList.java
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class FragmentList extends Fragment {
List<Msg> mMsgs = new ArrayList<>();
TextView inputText;
Button send;
private void initMsgs(){
Msg msg1 = new Msg("Hello guy.", Msg.TYPE_RECEIVED);
mMsgs.add(msg1);
Msg msg2 = new Msg("Hello. Who is that?", Msg.TYPE_SENT);
mMsgs.add(msg2);
Msg msg3 = new Msg("This is Tom. Nice talking to you.", Msg.TYPE_RECEIVED);
mMsgs.add(msg3);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_main, container, false);
initMsgs();
inputText = view.findViewById(R.id.input_text);
send = view.findViewById(R.id.send);
RecyclerView recyclerView = view.findViewById(R.id.msg_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
MsgAdapter msgAdapter = new MsgAdapter(mMsgs);
recyclerView.setAdapter(msgAdapter);
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String content = inputText.getText().toString();
if (!"".equals(content))
{
Msg msg = new Msg(content, Msg.TYPE_SENT);
mMsgs.add(msg);
msgAdapter.notifyItemChanged(mMsgs.size() - 1);
recyclerView.scrollToPosition(mMsgs.size() - 1);
inputText.setText("");
}
}
});
return view;
}
}
最后通过MainActivity通过FrameLayout把Fragment加进来
MainActivy.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
/**
* 加载一个FrameLayout布局,把一个Fragment装进去
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_msg);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container);
if (fragment == null)
{
/*要装FragmentList,其中是一个activity_main(内容是RecyclerView和LinearLayout)*/
fragment = new FragmentList();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
}
现在的架构与前面的区别不大,可以看出FramentList把MainActivity替换掉了,因为Frament与Activity功能非常近似,然后用MainActivity调用方法把FragmentLIst装载进FragmeLayout再显示。