第二行代码学习笔记——第四章:手机平板要兼容——探究碎片

本章要点

作为一名专业的Android开发人员,能够同时兼容手机和平板的开发时我们必须要做到的事情。


4.1 碎片是什么

碎片(Fragment)是一种可以嵌套在活动当中的UI片段,它能让程序更加合理和充分的利用大屏幕的控件。


4.2 碎片的使用方式

开始我们的碎片之旅,创建FragmentTest项目。

4.2.1 碎片的简易用法

最简单碎片,在一个活动中添加两个碎片,并让这两个碎片平分活动控件。

新建左碎片布局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/btn"
        android:text="Button"
        android:layout_gravity="center_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

新建右侧碎片,right_fragment.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="This is right fragment"/>
</LinearLayout>

新建LeftFragment类和RrightFragment类,并让它们继承自support-v4包下的Fragment,重写onCreateView()来加载布局。
LeftFragment,代码如下:

public class LeftFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.left_fragment, container, false);
        return view;
    }
}

RightFragment,代码如下:

public class RightFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.right_fragment, container, false);
        return view;
    }
}

修改activity_main.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">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.example.hjw.fragmenttest.LeftFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>

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


</LinearLayout>

属性:
android:name 指明添加碎片的类名(包名+类名)。

运行效果:
tablet1

4.2.2 动态添加碎片

新建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:background="#ffff00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

创建AnotherRightFragment类,代码如下:

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

修改activity_main.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">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.example.hjw.fragmenttest.LeftFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:id="@+id/right_layout"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="match_parent"></FrameLayout>

</LinearLayout>

实现动态添加Fragment,修改MainActivity中的代码如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn= (Button) findViewById(R.id.btn);
        btn.setOnClickListener(this);
        replaceFragmetn(new RightFragment());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn:
                replaceFragmetn(new AnotherRightFragment());
                break;
            default:
                break;z
        }
    }

    public void replaceFragmetn(Fragment fragment){
        FragmentManager fragmentManager=getSupportFragmentManager();
        FragmentTransaction transaction=fragmentManager.beginTransaction();
        transaction.replace(R.id.right_layout,fragment);
        transaction.commit();
    }
}

动态添加布局5步骤:

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

重启,点击一下按钮,效果图:
df

4.2.3 在碎片中模拟返回栈

模仿类似于返回栈的效果好,Back返回上一个碎片。
FragmentTransaction提供了addToBackStack()方法,一般传入null。修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

重新运行程序,点击按钮将AnotherRightFragment添加到活动中,按下Back键,会RightFragment,再按下Back键,RightFragment也会消失,再按下Back键,程序才会退出。

4.2.4 碎片与活动之间进行通信

活动中调用碎片的方法,FragmentManager提供了一个类似于findFragmentById()的方法,从布局中获取碎片的实例,代码如下:

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

碎片中调用活动的方法,调用getActivity()方法来得到和当前碎片相关联的活动,代码如下:

MainActivity activity = (MainActivity) getActivity();

这样,碎片就可以调用活动中的方法了,当碎片需要使用Context对象时,也可以使用getActivity()方法,因为获取到的活动本身就是一个Context对象。


4.3 碎片的生命周期

碎片自己的生命周期。

4.3.1 碎片的状态和回调

碎片和活动一样,生命周期会有4中状态:

  1. 运行状态
    当一个碎片时可见的,并且它关联的活动正处于运行状态时,该对片也处于运行状态。
  2. 暂停状态
    当一个活动进入暂停状态时,与它相关联的可见碎片就会进入到暂停状态。
  3. 停止状态
    当一个活动进入停止状态时,与它相关联的碎片就会进入停止状态,或调用FragmentTransaction的revome(),replace()将碎片从活动中移除, 如果在提交事务之前调用addToBackStack()方法,碎片也会进入停止状态。
  4. 销毁状态
    碎片总是依附于活动而存在的,当活动销毁时,与它关联的碎片也会销毁状态。或调用FragmentTransaction的revome(),replace()将碎片从活动中移除, 如果在提交事务之前并没有调用addToBackStack()方法,这时的碎片就会进入销毁状态。

活动中的回调方法,碎片几乎都有,碎片还提供了一些附加的回调方法:

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

碎片完整的生命周期图:
fsmzq


4.3.2 体验碎片的生命周期

修改RightFragment中的代码,如下:

public class RightFragment extends Fragment {

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

运行程序,观察logcat打印信息:

ycsmzq

点击LeftFragment中的按钮,观察logcat打印信息:
lb

按下Back键,RightFragment重新回到运行状态,观察logcat打印信息:
b1f

再次按下Back键,观察logcat打印信息:
b2


4.4 动态加载布局的技巧

Android中动态加载布局的计技巧。

4.4.1 使用限定符

判断程序应该是单页还是双页模式:限定符(Qualifiers)。
修改FragmentTest项目中的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">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.example.hjw.fragmenttest.LeftFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

在res目录下layout_large文件夹,新建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">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.example.hjw.fragmenttest.LeftFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>

    <fragment
        android:id="@+id/right_fragment"
        android:name="com.example.hjw.fragmenttest.RightFragment"
        android:layout_width="0dp"
        android:layout_weight="3"
        android:layout_height="match_parent"/>

</LinearLayout>

large是一个限定符,修改Macitivty中的代码:replaceFragment()代码注掉。运行平板效果如下:
xgt

在启动一个手机模拟器,运行效果如下:
sjxgt

限定符的参数:
这里写图片描述
a

4.4.2 使用最小宽度限定符

最小限定符(Smallest-width-Qualifier)

在res目录新建layout-sw600dp文件夹,新建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">

<fragment
    android:id="@+id/left_fragment"
    android:name="com.example.hjw.fragmenttest.LeftFragment"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="match_parent"/>

<fragment
    android:id="@+id/right_fragment"
    android:name="com.example.hjw.fragmenttest.RightFragment"
    android:layout_width="0dp"
    android:layout_weight="3"
    android:layout_height="match_parent"/>

</LinearLayout>

屏幕宽度大于600dp,会加载layout-600dp中的布局,小于600加载默认布局。


4.5 碎片的最佳实践——一个简易版的新闻应用

新闻应用

新建FragmentBestPractice项目。
添加依赖:

compile 'com.android.support:recyclerview-v7:24.2.1'

新建News实体类:

public class News {

    private String title;
    private String content;


    public void setTitle(String title) {
        this.title = title;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }
}

新建布局文件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:id="@+id/visibility_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_news_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="10dp"
            android:textSize="20sp" />

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

        <TextView
            android:id="@+id/tv_news_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
    </LinearLayout>

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

</RelativeLayout>

新建NewsContentFragment类加载news_content_frag布局,继承自Fragment,代码如下:

public class NewsContentFragment extends Fragment {


    private View view;
    private TextView tv_news_title,tv_news_content;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        view = LayoutInflater.from(getContext()).inflate(R.layout.news_content_frag,container,false);
        return view;
    }

    public void refresh(String newsTitle,String newsContent){
        View visibilityLayout = view.findViewById(R.id.visibility_layout);
        visibilityLayout.setVisibility(View.VISIBLE);
        tv_news_title= (TextView) view.findViewById(R.id.tv_news_title);
        tv_news_content= (TextView) view.findViewById(R.id.tv_news_content);
        tv_news_title.setText(newsTitle);  //刷新新闻的标题
        tv_news_content.setText(newsContent); //刷新新闻的头部
    }
}

单页模式使用,创建NewsContentActivity,指定布局名为news_content,引入NewsContentFragment布局,修改布局文件如下:

<?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">

    <fragment
        android:id="@+id/news_content_fragment"
        android:name="com.example.hjw.fragmentbestpractice.NewsContentFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

修改NewsContentActivity 中的代码,如下:

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

        Intent intent = getIntent();
        String news_title = intent.getStringExtra("news_title"); //获取新闻的标题
        String news_content = intent.getStringExtra("news_content"); //获取新闻的内容
        NewsContentFragment newsContentFragment= (NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
        newsContentFragment.refresh(news_title,news_content); //刷新NewsContentFragment界面
    }
}

新建news_title_frag.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.support.v7.widget.RecyclerView
        android:id="@+id/news_title_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

新闻标题的子布局,新建news_item.xml作为RecyclerView中子项的布局,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_news_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="end"
    android:paddingBottom="15dp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:paddingTop="15dp"
    android:singleLine="true"
    android:textSize="18sp" />

新建NewsTitleFragment作为展示新闻列表的碎片,onCreateView加载news_title_frag.xml布局,代码如下:

public class NewsTitleFragment extends Fragment{

    private boolean isTowPane;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = LayoutInflater.from(getContext()).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_layout)!=null){
            isTowPane=true;  //可以找到news_content_layout布局,为双页
        }else{
            isTowPane=false; //找不到news_content_layout布局,为单页
        }

}

接下来我们修改main_activity.xml,单页模式中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/news_title_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.example.hjw.fragmentbestpractice.NewsTitleFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


</FrameLayout>

新建layout-600dp文件夹,新建main_activity.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="horizontal">

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.example.hjw.fragmentbestpractice.NewsTitleFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <FrameLayout
        android:id="@+id/news_content_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3">

        <fragment
            android:id="@+id/news_content_fragment"
            android:name="com.example.hjw.fragmentbestpractice.NewsContentFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>

</LinearLayout>

接下来在NewsTitleFragment中通过RecyclerView展示新闻列表,在NewsTitleFragment中新建内部类NewsAdapter作为RecyclerView的适配器,代码如下所示:

public class NewsTitleFragment extends Fragment{

    private boolean isTowPane;

    ......

    class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{

        private List<News> mData;

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

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item,parent,false);
            final ViewHolder holder=new ViewHolder(view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    News news = mData.get(holder.getAdapterPosition());
                    if (isTowPane){
                        //如果是双页,刷新NewsContentFragment中的内容
                        NewsContentFragment newsContentFragment= (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
                        newsContentFragment.refresh(news.getTitle(),news.getContent());
                    }else{
                        //如果是单页,直接启动NewsContentActivity
                        NewsContentActivity.actionStart(getContext(),news.getTitle(),news.getContent());
                    }
                }
            });
            return holder;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            News news = mData.get(position);
            holder.tv_news_title.setText(news.getTitle());
        }

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

        class ViewHolder extends RecyclerView.ViewHolder{
            TextView tv_news_title;
            public ViewHolder(View itemView) {
                super(itemView);
                tv_news_title= (TextView) itemView.findViewById(R.id.tv_news_title);
            }
        }

    }

}

通过onCreateViewHolder方法中注册的点击事件,获取到点击项News的实例,通过isTwoPane判断是单页还是双页,更新里面的数据,修改NewsTitleFragment中的代码如下:

public class NewsTitleFragment extends Fragment{

    private boolean isTowPane;
    private RecyclerView news_title_recycler_view;
    List<News> newsList=new ArrayList<>();
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.news_title_frag, container, false);
        news_title_recycler_view = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
        LinearLayoutManager layoutManager=new LinearLayoutManager(getContext());
        news_title_recycler_view.setLayoutManager(layoutManager);
        NewsAdapter adapter = new NewsAdapter(getNews());
        news_title_recycler_view.setAdapter(adapter);
        return view;
    }


    public List<News> getNews() {
        for (int i = 1; i <= 50; i++) {
            News news=new News();
            news.setTitle("This is news title "+i);
            news.setContent(getRandomLengthContent("This is news content"+i+"."));
            newsList.add(news);
        }
        return newsList;
    }

    private String getRandomLengthContent(String content) {
        Random random=new Random();
        int length = random.nextInt(20)+1;
        StringBuilder builder=new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(content);
        }
        return builder.toString();
    }

    ...

}

运行效果:
单页模式的运行效果:
d1
点击子选项跳转:
d2

双页模式的运行效果图:
s


4.5 小结与点评

本章我们了解碎片的基本概念,以及使用场景,掌握了碎片的常用方法,学习了碎片的生命周期,以及动态加载布局。

发布了18 篇原创文章 · 获赞 28 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/JiangWeiHu/article/details/70917633