Fragment和RecyclerView混用

1. 参考:《Android编程权威指南(第2版)》编写

Fragment编写

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_android


照上图编写一个用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。

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_xml_02

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变成了启动文件,运行效果如下:

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_xml_03

RecyclerView编写

也是照着《Android编程权威指南》中的图编写:

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_android_04


图中的意思是CrimeLab中有一个ArrayList,ArrayList中存了Crime,书中还说CrimeLab是单例模式,为了省事,就不创建这个类了。

碎片已经写好,内容只有一个,现在想要显示多个,肯定要把一个换成多个,这里用到RecyclerView,前面的“一个”是FoodFragment.javafragment_food.xml,现在要多个再创建两个相似的文件:

1. 创建FoodListFragment和fragment_food_list

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_android_05

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();
    }
}

效果如下:

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_android_06


刚才写的FoodActivity没用了,Fragment相当于一个Activity用于加载一个界面,但是最终显示还是要通过Activity加载进FrameLayout中进行显示,多了一层。

2. 改写《第一行代码(第二版)》3.7编写精美聊天界面

文中并没有使用Fragment的方法。整个架构大致如下:

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_android_07


在些基础上要加上一个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>

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_android_08


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>

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_java_09


两个图片资源,不知道这样能不能用。

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_android_10


nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_xml_11


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();
        }
    }
}

nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_xml_12


nestedscrollview嵌套多个recyclerview 滑动不了 recyclerview嵌套fragment_xml_13


现在的架构与前面的区别不大,可以看出FramentList把MainActivity替换掉了,因为Frament与Activity功能非常近似,然后用MainActivity调用方法把FragmentLIst装载进FragmeLayout再显示。